Skip to content

Commit a1b2f09

Browse files
authored
v8 (#141)
1 parent 356319b commit a1b2f09

30 files changed

+502
-387
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
44
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

7+
## [8.0.0] - 2024-07-01
8+
### Added
9+
- CORS middware
10+
11+
### Changed
12+
- Bump http\_interop to v2.0
13+
714
## [7.0.1] - 2024-06-17
815
### Fixed
916
- "Accept" header with multiple values was being mishandled
@@ -250,6 +257,7 @@ the Document model.
250257
### Added
251258
- Client: fetch resources, collections, related resources and relationships
252259

260+
[8.0.0]: https://github.com/f3ath/json-api-dart/compare/7.0.1...8.0.0
253261
[7.0.1]: https://github.com/f3ath/json-api-dart/compare/7.0.0...7.0.1
254262
[7.0.0]: https://github.com/f3ath/json-api-dart/compare/6.0.1...7.0.0
255263
[6.0.1]: https://github.com/f3ath/json-api-dart/compare/6.0.0...6.0.1

README.md

+34-10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
TL;DR:
44
```dart
5+
import 'package:http/http.dart' as http;
56
import 'package:http_interop_http/http_interop_http.dart';
67
import 'package:json_api/client.dart';
78
import 'package:json_api/routing.dart';
@@ -13,29 +14,52 @@ void main() async {
1314
/// Use the standard recommended URL structure or implement your own
1415
final uriDesign = StandardUriDesign(Uri.parse(baseUri));
1516
17+
/// This is the Dart's standard HTTP client.
18+
/// Do not forget to close it in the end.
19+
final httpClient = http.Client();
20+
21+
/// This is the interface which decouples this JSON:API implementation
22+
/// from the HTTP client.
23+
/// Learn more: https://pub.dev/packages/http_interop
24+
final httpHandler = httpClient.handleInterop;
25+
26+
/// This is the basic JSON:API client. It is flexible but not very convenient
27+
/// to use, because you would need to remember a lot of JSON:API protocol details.
28+
/// We will use another wrapper on top of it.
29+
final jsonApiClient = Client(httpHandler);
30+
1631
/// The [RoutingClient] is most likely the right choice.
17-
/// It has methods covering many standard use cases.
18-
final client = RoutingClient(uriDesign, Client(OneOffHandler()));
32+
/// It is called routing because it routes the calls to the correct
33+
/// URLs depending on the use case. Take a look at its methods, they cover
34+
/// all the standard scenarios specified by the JSON:API standard.
35+
final client = RoutingClient(uriDesign, jsonApiClient);
1936
2037
try {
2138
/// Fetch the collection.
2239
/// See other methods to query and manipulate resources.
2340
final response = await client.fetchCollection('colors');
2441
25-
final resources = response.collection;
26-
resources.map((resource) => resource.attributes).forEach((attr) {
27-
final name = attr['name'];
28-
final red = attr['red'];
29-
final green = attr['green'];
30-
final blue = attr['blue'];
42+
/// The fetched collection allows us to iterate over the resources
43+
/// and to look into their attributes
44+
for (final resource in response.collection) {
45+
final {
46+
'name': name,
47+
'red': red,
48+
'green': green,
49+
'blue': blue,
50+
} = resource.attributes;
51+
print('${resource.type}:${resource.id}');
3152
print('$name - $red:$green:$blue');
32-
});
53+
}
3354
} on RequestFailure catch (e) {
3455
/// Catch error response
35-
for (var error in e.errors) {
56+
for (final error in e.errors) {
3657
print(error.title);
3758
}
3859
}
60+
61+
/// Free up the resources before exit.
62+
httpClient.close();
3963
}
4064
```
4165
This is a work-in-progress. You can help it by submitting a PR with a feature or documentation improvements.

example/client.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ void main() async {
1414
/// Do not forget to close it in the end.
1515
final httpClient = http.Client();
1616

17-
/// This is the adapter which decouples this JSON:API implementation
17+
/// This is the interface which decouples this JSON:API implementation
1818
/// from the HTTP client.
1919
/// Learn more: https://pub.dev/packages/http_interop
20-
final httpHandler = ClientWrapper(httpClient);
20+
final httpHandler = httpClient.handleInterop;
2121

2222
/// This is the basic JSON:API client. It is flexible but not very convenient
2323
/// to use, because you would need to remember a lot of JSON:API protocol details.

example/server.dart

+6-4
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,18 @@ Future<void> main() async {
1919
await initRepo(repo);
2020
final controller = RepositoryController(repo, Uuid().v4);
2121
interop.Handler handler =
22-
ControllerRouter(controller, StandardUriDesign.matchTarget);
23-
handler = TryCatchHandler(handler,
22+
ControllerRouter(controller, StandardUriDesign.matchTarget).handle;
23+
24+
handler = tryCatchMiddleware(handler,
2425
onError: ErrorConverter(onError: (e, stack) async {
2526
stderr.writeln(e);
2627
stderr.writeln(stack);
27-
return Response(500,
28+
return response(500,
2829
document: OutboundErrorDocument(
2930
[ErrorObject(title: 'Internal Server Error')]));
3031
}).call);
31-
handler = LoggingHandler(handler,
32+
33+
handler = loggingMiddleware(handler,
3234
onRequest: (r) => print('${r.method} ${r.uri}'),
3335
onResponse: (r) => print('${r.statusCode}'));
3436
final server = JsonApiServer(handler, host: host, port: port);

example/server/cors_handler.dart

-31
This file was deleted.

example/server/json_api_server.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class JsonApiServer {
4141

4242
Future<HttpServer> _createServer() async {
4343
final server = await HttpServer.bind(host, port);
44-
server.listen(listener(_handler));
44+
server.listenInterop(_handler);
4545
return server;
4646
}
4747
}

example/server/repository_controller.dart

+29-32
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import 'dart:convert';
22

33
import 'package:http_interop/extensions.dart';
4-
import 'package:http_interop/http_interop.dart' as http;
4+
import 'package:http_interop/http_interop.dart';
55
import 'package:json_api/document.dart';
66
import 'package:json_api/query.dart';
77
import 'package:json_api/routing.dart';
@@ -22,7 +22,7 @@ class RepositoryController implements Controller {
2222
final design = StandardUriDesign.pathOnly;
2323

2424
@override
25-
Future<Response> fetchCollection(http.Request request, Target target) async {
25+
Future<Response> fetchCollection(Request request, Target target) async {
2626
final resources = await _fetchAll(target.type).toList();
2727
final doc = OutboundDataDocument.collection(resources)
2828
..links['self'] = Link(design.collection(target.type));
@@ -32,128 +32,125 @@ class RepositoryController implements Controller {
3232
doc.included.add(r);
3333
}
3434
}
35-
return Response.ok(doc);
35+
return ok(doc);
3636
}
3737

3838
@override
39-
Future<Response> fetchResource(
40-
http.Request request, ResourceTarget target) async {
39+
Future<Response> fetchResource(Request request, ResourceTarget target) async {
4140
final resource = await _fetchLinkedResource(target.type, target.id);
4241
final doc = OutboundDataDocument.resource(resource)
4342
..links['self'] = Link(design.resource(target.type, target.id));
4443
final forest = RelationshipNode.forest(Include.fromUri(request.uri));
4544
await for (final r in _getAllRelated(resource, forest)) {
4645
doc.included.add(r);
4746
}
48-
return Response.ok(doc);
47+
return ok(doc);
4948
}
5049

5150
@override
52-
Future<Response> createResource(http.Request request, Target target) async {
51+
Future<Response> createResource(Request request, Target target) async {
5352
final document = await _decode(request);
5453
final newResource = document.dataAsNewResource();
5554
final res = newResource.toResource(getId);
5655
await repo.persist(
5756
res.type, Model(res.id)..setFrom(ModelProps.fromResource(res)));
5857
if (newResource.id != null) {
59-
return Response.noContent();
58+
return noContent();
6059
}
6160
final ref = Reference.of(res.toIdentifier());
6261
final self = Link(design.resource(ref.type, ref.id));
6362
final resource = (await _fetchResource(ref.type, ref.id))
6463
..links['self'] = self;
65-
return Response.created(
64+
return created(
6665
OutboundDataDocument.resource(resource)..links['self'] = self,
6766
self.uri.toString());
6867
}
6968

7069
@override
71-
Future<Response> addMany(
72-
http.Request request, RelationshipTarget target) async {
70+
Future<Response> addMany(Request request, RelationshipTarget target) async {
7371
final many = (await _decode(request)).asRelationship<ToMany>();
7472
final refs = await repo
7573
.addMany(target.type, target.id, target.relationship, many)
7674
.toList();
77-
return Response.ok(OutboundDataDocument.many(ToMany(refs)));
75+
return ok(OutboundDataDocument.many(ToMany(refs)));
7876
}
7977

8078
@override
8179
Future<Response> deleteResource(
82-
http.Request request, ResourceTarget target) async {
80+
Request request, ResourceTarget target) async {
8381
await repo.delete(target.type, target.id);
84-
return Response.noContent();
82+
return noContent();
8583
}
8684

8785
@override
8886
Future<Response> updateResource(
89-
http.Request request, ResourceTarget target) async {
87+
Request request, ResourceTarget target) async {
9088
await repo.update(target.type, target.id,
9189
ModelProps.fromResource((await _decode(request)).dataAsResource()));
92-
return Response.noContent();
90+
return noContent();
9391
}
9492

9593
@override
9694
Future<Response> replaceRelationship(
97-
http.Request request, RelationshipTarget target) async {
95+
Request request, RelationshipTarget target) async {
9896
final rel = (await _decode(request)).asRelationship();
9997
if (rel is ToOne) {
10098
final ref = rel.identifier;
10199
await repo.replaceOne(target.type, target.id, target.relationship, ref);
102-
return Response.ok(
100+
return ok(
103101
OutboundDataDocument.one(ref == null ? ToOne.empty() : ToOne(ref)));
104102
}
105103
if (rel is ToMany) {
106104
final ids = await repo
107105
.replaceMany(target.type, target.id, target.relationship, rel)
108106
.toList();
109-
return Response.ok(OutboundDataDocument.many(ToMany(ids)));
107+
return ok(OutboundDataDocument.many(ToMany(ids)));
110108
}
111109
throw FormatException('Incomplete relationship');
112110
}
113111

114112
@override
115113
Future<Response> deleteMany(
116-
http.Request request, RelationshipTarget target) async {
114+
Request request, RelationshipTarget target) async {
117115
final rel = (await _decode(request)).asToMany();
118116
final ids = await repo
119117
.deleteMany(target.type, target.id, target.relationship, rel)
120118
.toList();
121-
return Response.ok(OutboundDataDocument.many(ToMany(ids)));
119+
return ok(OutboundDataDocument.many(ToMany(ids)));
122120
}
123121

124122
@override
125123
Future<Response> fetchRelationship(
126-
http.Request request, RelationshipTarget target) async {
124+
Request request, RelationshipTarget target) async {
127125
final model = (await repo.fetch(target.type, target.id));
128126

129127
if (model.one.containsKey(target.relationship)) {
130-
return Response.ok(OutboundDataDocument.one(
128+
return ok(OutboundDataDocument.one(
131129
ToOne(model.one[target.relationship]?.toIdentifier())));
132130
}
133131
final many =
134132
model.many[target.relationship]?.map((it) => it.toIdentifier());
135133
if (many != null) {
136134
final doc = OutboundDataDocument.many(ToMany(many));
137-
return Response.ok(doc);
135+
return ok(doc);
138136
}
139137
throw RelationshipNotFound(target.type, target.id, target.relationship);
140138
}
141139

142140
@override
143-
Future<Response> fetchRelated(
144-
http.Request request, RelatedTarget target) async {
141+
Future<Response> fetchRelated(Request request, RelatedTarget target) async {
145142
final model = await repo.fetch(target.type, target.id);
146143
if (model.one.containsKey(target.relationship)) {
147144
final related =
148145
await nullable(_fetchRelatedResource)(model.one[target.relationship]);
149146
final doc = OutboundDataDocument.resource(related);
150-
return Response.ok(doc);
147+
return ok(doc);
151148
}
152149
if (model.many.containsKey(target.relationship)) {
153150
final many = model.many[target.relationship] ?? {};
154151
final doc = OutboundDataDocument.collection(
155152
await _fetchRelatedCollection(many).toList());
156-
return Response.ok(doc);
153+
return ok(doc);
157154
}
158155
throw RelationshipNotFound(target.type, target.id, target.relationship);
159156
}
@@ -171,10 +168,10 @@ class RepositoryController implements Controller {
171168

172169
/// Returns a stream of related resources
173170
Stream<Resource> _getRelated(Resource resource, String relationship) async* {
174-
for (final _ in resource.relationships[relationship] ??
171+
for (final rel in resource.relationships[relationship] ??
175172
(throw RelationshipNotFound(
176173
resource.type, resource.id, relationship))) {
177-
yield await _fetchLinkedResource(_.type, _.id);
174+
yield await _fetchLinkedResource(rel.type, rel.id);
178175
}
179176
}
180177

@@ -185,7 +182,7 @@ class RepositoryController implements Controller {
185182
}
186183

187184
Stream<Resource> _fetchAll(String type) =>
188-
repo.fetchCollection(type).map((_) => _.toResource(type));
185+
repo.fetchCollection(type).map((model) => model.toResource(type));
189186

190187
/// Fetches and builds a resource object
191188
Future<Resource> _fetchResource(String type, String id) async {
@@ -203,7 +200,7 @@ class RepositoryController implements Controller {
203200
}
204201
}
205202

206-
Future<InboundDocument> _decode(http.Request r) => r.body
203+
Future<InboundDocument> _decode(Request r) => r.body
207204
.decode(utf8)
208205
.then(const PayloadCodec().decode)
209206
.then(InboundDocument.new);

0 commit comments

Comments
 (0)