Skip to content

Commit 45c6a3f

Browse files
authored
Server refactor (#31)
1 parent 6eba655 commit 45c6a3f

20 files changed

+698
-798
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
### Changed
9+
- More BC-breaking changes in the Server
810

911
## [0.4.0] - 2019-03-17
1012
### Changed

example/cars_server.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,10 @@ Future<HttpServer> createServer(InternetAddress addr, int port) async {
4949
final controller = CarsController(
5050
{'companies': companies, 'cities': cities, 'models': models});
5151

52-
final router = StandardRouter(Uri.parse('http://localhost:$port'));
52+
final urlDesign = StandardURLDesign(Uri.parse('http://localhost:$port'));
5353

54-
final jsonApiServer = JsonApiServer(router, controller);
54+
final jsonApiServer =
55+
JsonApiServer(urlDesign, controller, StandardDocumentBuilder(urlDesign));
5556

5657
final httpServer = await HttpServer.bind(addr, port);
5758

Lines changed: 156 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,179 +1,226 @@
11
import 'dart:async';
22

33
import 'package:json_api/document.dart';
4-
import 'package:json_api/src/server/contracts/controller.dart';
5-
import 'package:json_api/src/server/numbered_page.dart';
4+
import 'package:json_api/server.dart';
5+
import 'package:json_api/src/server/request_target.dart';
66
import 'package:uuid/uuid.dart';
77

88
import 'dao.dart';
99

1010
class CarsController implements JsonApiController {
11-
final Map<String, DAO> dao;
11+
final Map<String, DAO> _dao;
1212

13-
CarsController(this.dao);
13+
CarsController(this._dao);
1414

1515
@override
16-
Future fetchCollection(FetchCollectionRequest r) async {
17-
if (!dao.containsKey(r.type)) {
18-
return r.errorNotFound([JsonApiError(detail: 'Unknown resource type')]);
19-
}
20-
final page = NumberedPage.fromQueryParameters(r.uri.queryParameters,
21-
total: dao[r.type].length);
22-
return r.sendCollection(
23-
dao[r.type]
16+
Future<void> fetchCollection(
17+
ControllerRequest<CollectionTarget, void> request,
18+
FetchCollectionResponse response) async {
19+
if (!_dao.containsKey(request.target.type)) {
20+
return response
21+
.errorNotFound([JsonApiError(detail: 'Unknown resource type')]);
22+
}
23+
final page = NumberedPage.fromQueryParameters(request.uri.queryParameters,
24+
total: _dao[request.target.type].length);
25+
return response.sendCollection(Collection(
26+
_dao[request.target.type]
2427
.fetchCollection(offset: page.offset)
25-
.map(dao[r.type].toResource),
26-
page: page);
28+
.map(_dao[request.target.type].toResource),
29+
page: page));
2730
}
2831

2932
@override
30-
Future fetchRelated(FetchRelatedRequest r) {
31-
if (!dao.containsKey(r.type)) {
32-
return r.errorNotFound([JsonApiError(detail: 'Unknown resource type')]);
33-
}
34-
final res = dao[r.type].fetchByIdAsResource(r.id);
33+
Future<void> fetchRelated(ControllerRequest<RelatedTarget, void> request,
34+
FetchRelatedResponse response) async {
35+
if (!_dao.containsKey(request.target.type)) {
36+
return response
37+
.errorNotFound([JsonApiError(detail: 'Unknown resource type')]);
38+
}
39+
final res =
40+
_dao[request.target.type].fetchByIdAsResource(request.target.id);
3541
if (res == null) {
36-
return r.errorNotFound([JsonApiError(detail: 'Resource not found')]);
42+
return response
43+
.errorNotFound([JsonApiError(detail: 'Resource not found')]);
3744
}
3845

39-
if (res.toOne.containsKey(r.relationship)) {
40-
final id = res.toOne[r.relationship];
41-
final resource = dao[id.type].fetchByIdAsResource(id.id);
42-
return r.sendResource(resource);
46+
if (res.toOne.containsKey(request.target.relationship)) {
47+
final id = res.toOne[request.target.relationship];
48+
final resource = _dao[id.type].fetchByIdAsResource(id.id);
49+
return response.sendResource(resource);
4350
}
4451

45-
if (res.toMany.containsKey(r.relationship)) {
46-
final resources = res.toMany[r.relationship]
47-
.map((id) => dao[id.type].fetchByIdAsResource(id.id));
48-
return r.sendCollection(resources);
52+
if (res.toMany.containsKey(request.target.relationship)) {
53+
final resources = res.toMany[request.target.relationship]
54+
.map((id) => _dao[id.type].fetchByIdAsResource(id.id));
55+
return response.sendCollection(Collection(resources));
4956
}
50-
return r.errorNotFound([JsonApiError(detail: 'Relationship not found')]);
57+
return response
58+
.errorNotFound([JsonApiError(detail: 'Relationship not found')]);
5159
}
5260

5361
@override
54-
Future fetchResource(FetchResourceRequest r) {
55-
if (!dao.containsKey(r.type)) {
56-
return r.errorNotFound([JsonApiError(detail: 'Unknown resource type')]);
57-
}
58-
final res = dao[r.type].fetchByIdAsResource(r.id);
62+
Future<void> fetchResource(ControllerRequest<ResourceTarget, void> request,
63+
FetchResourceResponse response) async {
64+
if (!_dao.containsKey(request.target.type)) {
65+
return response
66+
.errorNotFound([JsonApiError(detail: 'Unknown resource type')]);
67+
}
68+
final res =
69+
_dao[request.target.type].fetchByIdAsResource(request.target.id);
5970
if (res == null) {
60-
return r.errorNotFound([JsonApiError(detail: 'Resource not found')]);
71+
return response
72+
.errorNotFound([JsonApiError(detail: 'Resource not found')]);
6173
}
62-
final fetchById = (Identifier _) => dao[_.type].fetchByIdAsResource(_.id);
74+
final fetchById = (Identifier _) => _dao[_.type].fetchByIdAsResource(_.id);
6375

6476
final children = res.toOne.values
6577
.map(fetchById)
6678
.followedBy(res.toMany.values.expand((_) => _.map(fetchById)));
6779

68-
return r.sendResource(res, included: children);
80+
return response.sendResource(res, included: children);
6981
}
7082

7183
@override
72-
Future fetchRelationship(FetchRelationshipRequest r) {
73-
if (!dao.containsKey(r.type)) {
74-
return r.errorNotFound([JsonApiError(detail: 'Unknown resource type')]);
75-
}
76-
final res = dao[r.type].fetchByIdAsResource(r.id);
84+
Future<void> fetchRelationship(
85+
ControllerRequest<RelationshipTarget, void> request,
86+
FetchRelationshipResponse response) async {
87+
if (!_dao.containsKey(request.target.type)) {
88+
return response
89+
.errorNotFound([JsonApiError(detail: 'Unknown resource type')]);
90+
}
91+
final res =
92+
_dao[request.target.type].fetchByIdAsResource(request.target.id);
7793
if (res == null) {
78-
return r.errorNotFound([JsonApiError(detail: 'Resource not found')]);
94+
return response
95+
.errorNotFound([JsonApiError(detail: 'Resource not found')]);
7996
}
8097

81-
if (res.toOne.containsKey(r.relationship)) {
82-
final id = res.toOne[r.relationship];
83-
return r.sendToOne(id);
98+
if (res.toOne.containsKey(request.target.relationship)) {
99+
final id = res.toOne[request.target.relationship];
100+
return response.sendToOne(id);
84101
}
85102

86-
if (res.toMany.containsKey(r.relationship)) {
87-
final ids = res.toMany[r.relationship];
88-
return r.sendToMany(ids);
103+
if (res.toMany.containsKey(request.target.relationship)) {
104+
final ids = res.toMany[request.target.relationship];
105+
return response.sendToMany(ids);
89106
}
90-
return r.errorNotFound([JsonApiError(detail: 'Relationship not found')]);
107+
return response
108+
.errorNotFound([JsonApiError(detail: 'Relationship not found')]);
91109
}
92110

93111
@override
94-
Future deleteResource(DeleteResourceRequest r) {
95-
if (!dao.containsKey(r.type)) {
96-
return r.errorNotFound([JsonApiError(detail: 'Unknown resource type')]);
97-
}
98-
final res = dao[r.type].fetchByIdAsResource(r.id);
112+
Future<void> deleteResource(ControllerRequest<ResourceTarget, void> request,
113+
DeleteResourceResponse response) async {
114+
if (!_dao.containsKey(request.target.type)) {
115+
return response
116+
.errorNotFound([JsonApiError(detail: 'Unknown resource type')]);
117+
}
118+
final res =
119+
_dao[request.target.type].fetchByIdAsResource(request.target.id);
99120
if (res == null) {
100-
return r.errorNotFound([JsonApiError(detail: 'Resource not found')]);
121+
return response
122+
.errorNotFound([JsonApiError(detail: 'Resource not found')]);
101123
}
102-
final dependenciesCount = dao[r.type].deleteById(r.id);
124+
final dependenciesCount =
125+
_dao[request.target.type].deleteById(request.target.id);
103126
if (dependenciesCount == 0) {
104-
return r.sendNoContent();
127+
return response.sendNoContent();
105128
}
106-
return r.sendMeta({'dependenciesCount': dependenciesCount});
129+
return response.sendMeta({'dependenciesCount': dependenciesCount});
107130
}
108131

109-
Future createResource(CreateResourceRequest r) async {
110-
if (!dao.containsKey(r.type)) {
111-
return r.errorNotFound([JsonApiError(detail: 'Unknown resource type')]);
132+
@override
133+
Future<void> createResource(
134+
ControllerRequest<CollectionTarget, Resource> request,
135+
CreateResourceResponse response) async {
136+
if (!_dao.containsKey(request.target.type)) {
137+
return response
138+
.errorNotFound([JsonApiError(detail: 'Unknown resource type')]);
112139
}
113-
if (r.type != r.resource.type) {
114-
return r.errorConflict([JsonApiError(detail: 'Incompatible type')]);
140+
if (request.target.type != request.payload.type) {
141+
return response
142+
.errorConflict([JsonApiError(detail: 'Incompatible type')]);
115143
}
116144

117-
if (r.resource.hasId) {
118-
if (dao[r.type].fetchById(r.resource.id) != null) {
119-
return r
145+
if (request.payload.hasId) {
146+
if (_dao[request.target.type].fetchById(request.payload.id) != null) {
147+
return response
120148
.errorConflict([JsonApiError(detail: 'Resource already exists')]);
121149
}
122-
final created = dao[r.type].create(r.resource);
123-
dao[r.type].insert(created);
124-
return r.sendNoContent();
125-
}
126-
127-
final created = dao[r.type].create(Resource(r.resource.type, Uuid().v4(),
128-
attributes: r.resource.attributes,
129-
toMany: r.resource.toMany,
130-
toOne: r.resource.toOne));
131-
dao[r.type].insert(created);
132-
return r.sendCreated(dao[r.type].toResource(created));
150+
final created = _dao[request.target.type].create(request.payload);
151+
_dao[request.target.type].insert(created);
152+
return response.sendNoContent();
153+
}
154+
155+
final created = _dao[request.target.type].create(Resource(
156+
request.payload.type, Uuid().v4(),
157+
attributes: request.payload.attributes,
158+
toMany: request.payload.toMany,
159+
toOne: request.payload.toOne));
160+
_dao[request.target.type].insert(created);
161+
return response.sendCreated(_dao[request.target.type].toResource(created));
133162
}
134163

135164
@override
136-
Future updateResource(UpdateResourceRequest r) async {
137-
if (!dao.containsKey(r.type)) {
138-
return r.errorNotFound([JsonApiError(detail: 'Unknown resource type')]);
139-
}
140-
if (r.type != r.resource.type) {
141-
return r.errorConflict([JsonApiError(detail: 'Incompatible type')]);
142-
}
143-
if (dao[r.type].fetchById(r.id) == null) {
144-
return r.errorNotFound([JsonApiError(detail: 'Resource not found')]);
145-
}
146-
final updated = dao[r.type].update(r.id, r.resource);
165+
Future<void> updateResource(
166+
ControllerRequest<ResourceTarget, Resource> request,
167+
UpdateResourceResponse response) async {
168+
if (!_dao.containsKey(request.target.type)) {
169+
return response
170+
.errorNotFound([JsonApiError(detail: 'Unknown resource type')]);
171+
}
172+
if (request.target.type != request.payload.type) {
173+
return response
174+
.errorConflict([JsonApiError(detail: 'Incompatible type')]);
175+
}
176+
if (_dao[request.target.type].fetchById(request.target.id) == null) {
177+
return response
178+
.errorNotFound([JsonApiError(detail: 'Resource not found')]);
179+
}
180+
final updated =
181+
_dao[request.target.type].update(request.target.id, request.payload);
147182
if (updated == null) {
148-
return r.sendNoContent();
183+
return response.sendNoContent();
149184
}
150-
return r.sendUpdated(updated);
185+
return response.sendUpdated(updated);
151186
}
152187

153188
@override
154-
Future replaceToOne(ReplaceToOneRequest r) async {
155-
if (!dao.containsKey(r.type)) {
156-
return r.errorNotFound([JsonApiError(detail: 'Unknown resource type')]);
157-
}
158-
dao[r.type].replaceToOne(r.id, r.relationship, r.identifier);
159-
return r.sendNoContent();
189+
Future<void> replaceToOne(
190+
ControllerRequest<RelationshipTarget, Identifier> request,
191+
ReplaceToOneResponse response) async {
192+
if (!_dao.containsKey(request.target.type)) {
193+
return response
194+
.errorNotFound([JsonApiError(detail: 'Unknown resource type')]);
195+
}
196+
_dao[request.target.type].replaceToOne(
197+
request.target.id, request.target.relationship, request.payload);
198+
return response.sendNoContent();
160199
}
161200

162201
@override
163-
Future replaceToMany(ReplaceToManyRequest r) async {
164-
if (!dao.containsKey(r.type)) {
165-
return r.errorNotFound([JsonApiError(detail: 'Unknown resource type')]);
166-
}
167-
dao[r.type].replaceToMany(r.id, r.relationship, r.identifiers);
168-
return r.sendNoContent();
202+
Future<void> replaceToMany(
203+
ControllerRequest<RelationshipTarget, Iterable<Identifier>> request,
204+
ReplaceToManyResponse response) async {
205+
if (!_dao.containsKey(request.target.type)) {
206+
return response
207+
.errorNotFound([JsonApiError(detail: 'Unknown resource type')]);
208+
}
209+
_dao[request.target.type].replaceToMany(
210+
request.target.id, request.target.relationship, request.payload);
211+
return response.sendNoContent();
169212
}
170213

171214
@override
172-
Future addToMany(AddToManyRequest r) async {
173-
if (!dao.containsKey(r.type)) {
174-
return r.errorNotFound([JsonApiError(detail: 'Unknown resource type')]);
175-
}
176-
final result = dao[r.type].addToMany(r.id, r.relationship, r.identifiers);
177-
return r.sendToMany(result);
215+
Future<void> addToMany(
216+
ControllerRequest<RelationshipTarget, Iterable<Identifier>> request,
217+
AddToManyResponse response) async {
218+
if (!_dao.containsKey(request.target.type)) {
219+
return response
220+
.errorNotFound([JsonApiError(detail: 'Unknown resource type')]);
221+
}
222+
final result = _dao[request.target.type].addToMany(
223+
request.target.id, request.target.relationship, request.payload);
224+
return response.sendToMany(result);
178225
}
179226
}

lib/server.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
export 'package:json_api/src/server/contracts/controller.dart';
2-
export 'package:json_api/src/server/contracts/page.dart';
3-
export 'package:json_api/src/server/contracts/router.dart';
1+
export 'package:json_api/src/server/collection.dart';
2+
export 'package:json_api/src/server/controller.dart';
43
export 'package:json_api/src/server/numbered_page.dart';
4+
export 'package:json_api/src/server/page.dart';
5+
export 'package:json_api/src/server/router.dart';
56
export 'package:json_api/src/server/server.dart';
7+
export 'package:json_api/src/server/standard_document_builder.dart';
68
export 'package:json_api/src/server/standard_router.dart';

lib/src/client/client.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@ class JsonApiClient {
181181
}
182182
final body = json.decode(r.body);
183183
final document = body == null ? null : _parser.parseDocument(body, parse);
184-
185184
return Response(r.statusCode, r.headers, document: document);
186185
} finally {
187186
client.close();

lib/src/server/collection.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import 'package:json_api/src/server/page.dart';
2+
3+
class Collection<T> {
4+
final Iterable<T> elements;
5+
final Page page;
6+
7+
Collection(this.elements, {this.page});
8+
}

0 commit comments

Comments
 (0)