Skip to content

Commit eedd01e

Browse files
authored
V3 Relese (#69)
1 parent 61b6dfe commit eedd01e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+692
-931
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ dart:
44
- dev
55
- "2.6.0"
66
- "2.6.1"
7+
- "2.7.0"
78
dart_task:
89
- test: --platform vm
910
- test: --platform chrome

CHANGELOG.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,30 @@ 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+
## [3.0.0] - 2019-12-17
89
### Added
910
- Support for custom non-standard links ([#61](https://github.com/f3ath/json-api-dart/issues/61))
1011
- Client supports `jsonapi` key in outgoing requests.
1112
- `Document.contentType` constant.
1213
- `IdentifierObject.fromIdentifier` factory method
1314

1415
### Changed
15-
Most of this changes are **BC-BREAKING**.
16+
Most of the changes are **BC-BREAKING**.
1617
- `URLBuilder` was renamed to `UrlFactory`.
1718
- `DocumentBuilder` was split into `ServerDocumentFactory` and `ClientDocumentFactory`. Some methods were renamed.
1819
- Static `decodeJson` methods were renamed to `fromJson`.
1920
- `Identifier.equals` now requires the runtime type to be exactly the same.
20-
- `Link.decodeJsonMap` was renamed to `mapFromJson`
21-
- `TargetMatcher` changed its signature.
21+
- `Link.decodeJsonMap` was renamed to `mapFromJson`.
22+
- The signature of `TargetMatcher`.
23+
- The signature of `Controller`.
24+
- `Server` was renamed to `JsonApiServer`.
25+
- `Pagination` was renamed to `PaginationStrategy`.
2226

2327
### Removed
2428
- (Server) `ResourceTarget`, `CollectionTarget`, `RelationshipTarget` classes.
2529
- `QueryParameters` interface.
2630
- `Router` class.
31+
- `Query` class.
2732

2833
## [2.1.0] - 2019-12-04
2934
### Added
@@ -112,7 +117,8 @@ Most of this changes are **BC-BREAKING**.
112117
### Added
113118
- Client: fetch resources, collections, related resources and relationships
114119

115-
[Unreleased]: https://github.com/f3ath/json-api-dart/compare/2.1.0...HEAD
120+
[Unreleased]: https://github.com/f3ath/json-api-dart/compare/3.0.0...HEAD
121+
[3.0.0]: https://github.com/f3ath/json-api-dart/compare/2.1.0...3.0.0
116122
[2.1.0]: https://github.com/f3ath/json-api-dart/compare/2.0.3...2.1.0
117123
[2.0.3]: https://github.com/f3ath/json-api-dart/compare/2.0.2...2.0.3
118124
[2.0.2]: https://github.com/f3ath/json-api-dart/compare/2.0.1...2.0.2

CONTRIBUTING.md

Lines changed: 0 additions & 8 deletions
This file was deleted.

README.md

Lines changed: 14 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -1,162 +1,14 @@
1-
[JSON:API](http://jsonapi.org) is a specification for building APIs in JSON.
2-
3-
# Client
4-
Quick usage example:
5-
```dart
6-
import 'package:http/http.dart';
7-
import 'package:json_api/json_api.dart';
8-
9-
void main() async {
10-
final httpClient = Client();
11-
final jsonApiClient = JsonApiClient(httpClient);
12-
final companiesUri = Uri.parse('http://localhost:8080/companies');
13-
final response = await jsonApiClient.fetchCollection(companiesUri);
14-
httpClient.close();
15-
print('Status: ${response.status}');
16-
print('Headers: ${response.headers}');
17-
18-
final resource = response.data.unwrap().first;
19-
20-
print('The collection page size is ${response.data.collection.length}');
21-
print('The first element is ${resource}');
22-
print('Attributes:');
23-
resource.attributes.forEach((k, v) => print('$k=$v'));
24-
print('Relationships:');
25-
resource.toOne.forEach((k, v) => print('$k=$v'));
26-
resource.toMany.forEach((k, v) => print('$k=$v'));
27-
}
28-
```
29-
To see this in action:
30-
31-
1. start the server:
32-
```
33-
$ dart example/cars_server.dart
34-
Listening on 127.0.0.1:8080
35-
```
36-
2. run the script:
37-
```
38-
$ dart example/fetch_collection.dart
39-
Status: 200
40-
Headers: {x-frame-options: SAMEORIGIN, content-type: application/vnd.api+json, x-xss-protection: 1; mode=block, x-content-type-options: nosniff, transfer-encoding: chunked, access-control-allow-origin: *}
41-
The collection page size is 1
42-
The first element is Resource(companies:1)
43-
Attributes:
44-
name=Tesla
45-
nasdaq=null
46-
updatedAt=2019-07-07T13:08:18.125737
47-
Relationships:
48-
hq=Identifier(cities:2)
49-
models=[Identifier(models:1), Identifier(models:2), Identifier(models:3), Identifier(models:4)]
50-
```
51-
52-
The client provides a set of methods to deal with resources and relationships.
53-
- Fetching
54-
- [fetchCollection](https://pub.dev/documentation/json_api/latest/client/JsonApiClient/fetchCollection.html) - resource collection, either primary or related
55-
- [fetchResource](https://pub.dev/documentation/json_api/latest/client/JsonApiClient/fetchResource.html) - a single resource, either primary or related
56-
- [fetchRelationship](https://pub.dev/documentation/json_api/latest/client/JsonApiClient/fetchRelationship.html) - a generic relationship (either to-one, to-many or even incomplete)
57-
- [fetchToOne](https://pub.dev/documentation/json_api/latest/client/JsonApiClient/fetchToOne.html) - a to-one relationship
58-
- [fetchToMany](https://pub.dev/documentation/json_api/latest/client/JsonApiClient/fetchToMany.html) - a to-many relationship
59-
- Manipulating resources
60-
- [createResource](https://pub.dev/documentation/json_api/latest/client/JsonApiClient/createResource.html) - creates a new primary resource
61-
- [updateResource](https://pub.dev/documentation/json_api/latest/client/JsonApiClient/updateResource.html) - updates the existing resource by its type and id
62-
- [deleteResource](https://pub.dev/documentation/json_api/latest/client/JsonApiClient/deleteResource.html) - deletes the existing resource
63-
- Manipulating relationships
64-
- [replaceToOne](https://pub.dev/documentation/json_api/latest/client/JsonApiClient/replaceToOne.html) - replaces the existing to-one relationship with a new resource identifier
65-
- [deleteToOne](https://pub.dev/documentation/json_api/latest/client/JsonApiClient/deleteToOne.html) - deletes the existing to-one relationship by setting the resource identifier to null
66-
- [replaceToMany](https://pub.dev/documentation/json_api/latest/client/JsonApiClient/replaceToMany.html) - replaces the existing to-many relationship with the given set of resource identifiers
67-
- [addToMany](https://pub.dev/documentation/json_api/latest/client/JsonApiClient/addToMany.html) - adds the given identifiers to the existing to-many relationship
68-
69-
These methods accept the target URI and the object to update (except for fetch and delete requests).
70-
You can also pass an optional map of HTTP headers, e.g. for authentication. The return value
71-
is a [Response] object.
72-
73-
You can get the status of the [Response] from either [Response.status] or one of the following properties:
74-
- [Response.isSuccessful]
75-
- [Response.isFailed]
76-
- [Response.isAsync] (see [Asynchronous Processing])
77-
78-
The Response also contains the raw [Response.status] and a map of HTTP headers.
79-
Two headers used by JSON:API can be accessed directly for your convenience:
80-
- [Response.location] holds the `Location` header used in creation requests
81-
- [Response.contentLocation] holds the `Content-Location` header used for [Asynchronous Processing]
82-
83-
The most important part of the Response is the [Response.document] containing the JSON:API document sent by the server (if any).
84-
If the document has the Primary Data, you can use [Response.data] shortcut to access it directly.
85-
86-
#### Included resources
87-
If you requested related resources to be included in the response (see [Compound Documents]) and the server fulfilled
88-
your request, the [PrimaryData.included] property will contain them.
89-
90-
#### Errors
91-
For unsuccessful operations the [Response.data] property will be null.
92-
If the server decided to include the error details in the response, those can be found in the [Document.errors] property.
93-
94-
#### Async processing
95-
Some servers may support [Asynchronous Processing].
96-
When the server responds with `202 Accepted`, the client expects the Primary Data to always be a Resource (usually
97-
representing a job queue). In this case, [Response.document] and [Response.data] will be null. Instead,
98-
the response document will be placed to [Response.asyncDocument] (and [Response.asyncData]).
99-
Also in this case the [Response.contentLocation]
100-
will point to the job queue resource. You can fetch the job queue resource periodically and check
101-
the type of the returned resource. Once the operation is complete, the request will return the created resource.
102-
103-
#### Adding JSON:API Object
104-
It is possible to add the [JSON:API Object] to all documents sent by the [JsonApiClient]. To do so, pass the
105-
pre-configured [DocumentFactory] to the [JsonApiClient]:
106-
```dart
107-
import 'package:http/http.dart';
108-
import 'package:json_api/json_api.dart';
109-
110-
void main() async {
111-
final api = Api(version: "1.0");
112-
final httpClient = Client();
113-
final jsonApiClient = JsonApiClient(httpClient, documentFactory: DocumentFactory(api: api));
114-
}
115-
116-
```
117-
118-
119-
# Server
120-
The server included in this package is still under development. It is not yet suitable for real production environment
121-
except maybe for really simple demo or testing cases.
122-
123-
## URL Design
124-
The URL Design specifies the structure of the URLs used for specific targets. The JSON:API standard describes 4
125-
possible request targets:
126-
- Collections (parameterized by the resource type)
127-
- Individual resources (parameterized by the resource type and id)
128-
- Related resources and collections (parameterized by the resource type, resource id and the relation name)
129-
- Relationships (parameterized by the resource type, resource id and the relation name)
130-
131-
The [UrlFactory] makes those 4 kinds of URLs by the given parameters. The [TargetMatcher] does the opposite,
132-
it determines the target of the given URL (if possible). Together they form the [UrlDesign].
133-
134-
This package provides one built-in implementation of [UrlDesign] which is called [PathBasedUrlDesign].
135-
The [PathBasedUrlDesign] implements the [Recommended URL Design] allowing you to specify the a common prefix
136-
for all your JSON:API endpoints.
137-
138-
139-
[DocumentFactory]: https://pub.dev/documentation/json_api/latest/document_factory/DocumentFactory-class.html
140-
[Document.errors]: https://pub.dev/documentation/json_api/latest/document/Document/errors.html
141-
[JsonApiClient]: https://pub.dev/documentation/json_api/latest/client/JsonApiClient-class.html
142-
[PathBasedUrlDesign]: https://pub.dev/documentation/json_api/latest/url_design/PathBasedUrlDesign-class.html
143-
[PrimaryData.included]: https://pub.dev/documentation/json_api/latest/document/PrimaryData/included.html
144-
[Response]: https://pub.dev/documentation/json_api/latest/client/Response-class.html
145-
[Response.data]: https://pub.dev/documentation/json_api/latest/client/Response/data.html
146-
[Response.document]: https://pub.dev/documentation/json_api/latest/client/Response/document.html
147-
[Response.isSuccessful]: https://pub.dev/documentation/json_api/latest/client/Response/isSuccessful.html
148-
[Response.isFailed]: https://pub.dev/documentation/json_api/latest/client/Response/isFailed.html
149-
[Response.isAsync]: https://pub.dev/documentation/json_api/latest/client/Response/isAsync.html
150-
[Response.location]: https://pub.dev/documentation/json_api/latest/client/Response/location.html
151-
[Response.contentLocation]: https://pub.dev/documentation/json_api/latest/client/Response/contentLocation.html
152-
[Response.status]: https://pub.dev/documentation/json_api/latest/client/Response/status.html
153-
[Response.asyncDocument]: https://pub.dev/documentation/json_api/latest/client/Response/asyncDocument.html
154-
[Response.asyncData]: https://pub.dev/documentation/json_api/latest/client/Response/asyncData.html
155-
[TargetMatcher]: https://pub.dev/documentation/json_api/latest/url_design/TargetMatcher-class.html
156-
[UrlFactory]: https://pub.dev/documentation/json_api/latest/url_design/UrlFactory-class.html
157-
[UrlDesign]: https://pub.dev/documentation/json_api/latest/url_design/UrlDesign-class.html
158-
159-
[Asynchronous Processing]: https://jsonapi.org/recommendations/#asynchronous-processing
160-
[Compound Documents]: https://jsonapi.org/format/#document-compound-documents
161-
[JSON:API Object]: https://jsonapi.org/format/#document-jsonapi-object
162-
[Recommended URL Design]: https://jsonapi.org/recommendations/#urls
1+
[JSON:API](http://jsonapi.org) is a specification for building APIs in JSON.
2+
3+
This package consists of several libraries:
4+
- The [Client] to make requests to JSON:API servers
5+
- The [Server] which is still under development
6+
- The [Document] model for resources, relationships, identifiers, etc
7+
- The [Query] to build and parse the query parameters (pagination, sorting, etc)
8+
- The [URL Design] to build and match URLs for resources, collections, and relationships
9+
10+
[Client]: https://pub.dev/documentation/json_api/latest/client/client-library.html
11+
[Server]: https://pub.dev/documentation/json_api/latest/server/server-library.html
12+
[Document]: https://pub.dev/documentation/json_api/latest/document/document-library.html
13+
[Query]: https://pub.dev/documentation/json_api/latest/query/query-library.html
14+
[URL Design]: https://pub.dev/documentation/json_api/latest/url_design/url_design-library.html

example/cars_server.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ Future<HttpServer> createServer(InternetAddress addr, int port) async {
6161
final urlDesign = PathBasedUrlDesign(Uri.parse('http://localhost:$port'));
6262
final documentFactory =
6363
ServerDocumentFactory(urlDesign, pagination: pagination);
64-
final jsonApiServer = Server(urlDesign, controller, documentFactory);
64+
final jsonApiServer = JsonApiServer(urlDesign, controller, documentFactory);
6565

6666
httpServer.forEach(jsonApiServer.serve);
6767
return httpServer;

example/cars_server/controller.dart

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,48 +10,46 @@ import 'job_queue.dart';
1010
class CarsController implements Controller {
1111
final Map<String, DAO> _dao;
1212

13-
final Pagination _pagination;
13+
final PaginationStrategy _pagination;
1414

1515
CarsController(this._dao, this._pagination);
1616

1717
@override
18-
Response fetchCollection(String type, Query query) {
18+
Response fetchCollection(String type, Uri uri) {
19+
final page = Page.fromUri(uri);
1920
final dao = _getDaoOrThrow(type);
20-
final collection = dao.fetchCollection(
21-
_pagination.limit(query.page), _pagination.offset(query.page));
21+
final collection =
22+
dao.fetchCollection(_pagination.limit(page), _pagination.offset(page));
2223
return CollectionResponse(collection.elements.map(dao.toResource),
23-
included: const [], total: collection.totalCount);
24+
total: collection.totalCount);
2425
}
2526

2627
@override
27-
Response fetchRelated(
28-
String type, String id, String relationship, Query query) {
28+
Response fetchRelated(String type, String id, String relationship, Uri uri) {
2929
final res = _fetchResourceOrThrow(type, id);
30-
30+
final page = Page.fromUri(uri);
3131
if (res.toOne.containsKey(relationship)) {
3232
final id = res.toOne[relationship];
3333
final resource = _dao[id.type].fetchByIdAsResource(id.id);
3434
return RelatedResourceResponse(resource);
3535
}
36-
3736
if (res.toMany.containsKey(relationship)) {
3837
final relationships = res.toMany[relationship];
3938
final resources = relationships
40-
.skip(_pagination.offset(query.page))
41-
.take(_pagination.limit(query.page))
39+
.skip(_pagination.offset(page))
40+
.take(_pagination.limit(page))
4241
.map((id) => _dao[id.type].fetchByIdAsResource(id.id));
43-
return RelatedCollectionResponse(resources,
44-
total: relationships.length, included: const []);
42+
return RelatedCollectionResponse(resources, total: relationships.length);
4543
}
4644
return ErrorResponse.notFound(
4745
[JsonApiError(detail: 'Relationship not found')]);
4846
}
4947

5048
@override
51-
Response fetchResource(String type, String id, Query query) {
49+
Response fetchResource(String type, String id, Uri uri) {
5250
final dao = _getDaoOrThrow(type);
53-
5451
final obj = dao.fetchById(id);
52+
final include = Include.fromUri(uri);
5553

5654
if (obj == null) {
5755
return ErrorResponse.notFound(
@@ -64,16 +62,18 @@ class CarsController implements Controller {
6462
final fetchById = (Identifier _) => _dao[_.type].fetchByIdAsResource(_.id);
6563

6664
final res = dao.toResource(obj);
67-
final children = res.toOne.values
68-
.map(fetchById)
69-
.followedBy(res.toMany.values.expand((_) => _.map(fetchById)));
7065

71-
return ResourceResponse(res, included: children);
66+
var filter = _filter(res.toMany, include.contains);
67+
var followedBy = _filter(res.toOne, include.contains)
68+
.values
69+
.map(fetchById)
70+
.followedBy(filter.values.expand((_) => _.map(fetchById)));
71+
return ResourceResponse(res, included: followedBy);
7272
}
7373

7474
@override
7575
Response fetchRelationship(
76-
String type, String id, String relationship, Query query) {
76+
String type, String id, String relationship, Uri uri) {
7777
final res = _fetchResourceOrThrow(type, id);
7878

7979
if (res.toOne.containsKey(relationship)) {
@@ -203,4 +203,7 @@ class CarsController implements Controller {
203203
}
204204
return resource;
205205
}
206+
207+
Map<T, R> _filter<T, R>(Map<T, R> map, bool f(T t)) =>
208+
{...map}..removeWhere((k, _) => !f(k));
206209
}

example/cars_server/dao.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import 'package:json_api/json_api.dart';
1+
import 'package:json_api/document.dart';
22

33
import 'collection.dart';
44
import 'job_queue.dart';

example/cars_server/job_queue.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import 'dart:async';
22

3-
import 'package:json_api/json_api.dart';
3+
import 'package:json_api/document.dart';
44
import 'package:uuid/uuid.dart';
55

66
class Job {

example/fetch_collection.dart

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,15 @@
11
import 'package:http/http.dart';
2-
import 'package:json_api/json_api.dart';
2+
import 'package:json_api/client.dart';
33

4+
/// Start the `cars_server.dart` first!
45
void main() async {
56
final httpClient = Client();
67
final jsonApiClient = JsonApiClient(httpClient);
7-
final companiesUri = Uri.parse('http://localhost:8080/companies');
8-
final response = await jsonApiClient.fetchCollection(companiesUri);
9-
httpClient.close();
10-
print('Status: ${response.status}');
11-
print('Headers: ${response.headers}');
12-
13-
final resource = response.data.unwrap().first;
14-
8+
final url = Uri.parse('http://localhost:8080/companies/2');
9+
final response = await jsonApiClient.fetchCollection(url);
10+
httpClient.close(); // Don't forget to close the http client
1511
print('The collection page size is ${response.data.collection.length}');
16-
print('The first element is ${resource}');
17-
print('Attributes:');
18-
resource.attributes.forEach((k, v) => print('$k=$v'));
19-
print('Relationships:');
20-
resource.toOne.forEach((k, v) => print('$k=$v'));
21-
resource.toMany.forEach((k, v) => print('$k=$v'));
12+
final resource = response.data.unwrap().first;
13+
print('The last element is ${resource}');
14+
resource.attributes.forEach((k, v) => print('Attribute $k is $v'));
2215
}

lib/client.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
export 'package:json_api/src/client/client.dart';
1+
library client;
2+
23
export 'package:json_api/src/client/client_document_factory.dart';
4+
export 'package:json_api/src/client/json_api_client.dart';
35
export 'package:json_api/src/client/response.dart';
46
export 'package:json_api/src/client/status_code.dart';

0 commit comments

Comments
 (0)