Skip to content

Commit 66e812b

Browse files
authored
v2 (#50)
1 parent 7cc939a commit 66e812b

Some content is hidden

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

73 files changed

+2749
-649
lines changed

.travis.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
language: dart
22
dart:
3-
- stable
3+
- stable
44
dart_task:
5-
- test: --platform vm
6-
- test: --platform chrome
7-
- test: --platform firefox
8-
- dartfmt: true
9-
- dartanalyzer: --fatal-infos --fatal-warnings lib test example
5+
- test: --platform vm
6+
- test: --platform chrome
7+
- test: --platform firefox
8+
- dartfmt: true
9+
- dartanalyzer: --fatal-infos --fatal-warnings lib test example

CHANGELOG.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,22 @@ 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+
## [2.0.0]
9+
810
### Changed
9-
- Dart >= 2.3.0 is now required
11+
- This package now consolidates the Client, the Server and the Document in one single library.
12+
It does not depend on `json_api_document` and `json_api_server` anymore, please remove these packages
13+
from your `pubspec.yaml`.
14+
- The min Dart SDK version bumped to `2.3.0`
15+
- The Client requires an instance of HttpClient to be passed to the constructor explicitly.
16+
- Both the Document and the Server have been refactored with lots of **BREAKING CHANGES**.
17+
See the examples and the functional tests for details.
18+
- Meta properties are not defensively copied, but set directly. Meta property behavior is unified across
19+
the Document model.
20+
21+
### Removed
22+
- `JsonApiParser` is removed. Use the static `decodeJson` methods in the corresponding classes instead.
1023

11-
### Added
12-
- `JsonApiClient` has `const` constructor
1324

1425
## [1.0.1] - 2019-04-05
1526
### Fixed

CONTRIBUTING.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
This document describes architectural decisions which may not be clear from the code.
2+
3+
## Meta
4+
Meta is the only mutable property. It can be set to null or mutated in-place.
5+
6+
## Json decoding/encoding
7+
Encoding is done via dynamic `toJson()`.
8+
Decoding is done via static `decodeJson()`

README.md

Lines changed: 85 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,53 @@
1-
Other JSON:API packages: [Document](https://pub.dartlang.org/packages/json_api_document) | [Server](https://pub.dartlang.org/packages/json_api_server)
1+
[JSON:API](http://jsonapi.org) is a specification for building APIs in JSON.
22

3-
---
4-
5-
# JSON:API Client
3+
# Client
4+
Quick usage example:
5+
```dart
6+
import 'package:json_api/json_api.dart';
67
7-
[JSON:API](http://jsonapi.org) is a specification for building APIs in JSON. This package implements
8-
the Client.
8+
void main() async {
9+
final client = JsonApiClient();
10+
final companiesUri = Uri.parse('http://localhost:8080/companies');
11+
final response = await client.fetchCollection(companiesUri);
912
10-
## Features
11-
- Fetching single resources, resource collections, related resources
12-
- Fetching/updating relationships
13-
- Creating/updating/deleting resources
14-
- Collection pagination
15-
- Compound documents (included resources)
16-
- Asynchronous processing
13+
print('Status: ${response.status}');
14+
print('The collection page size is ${response.data.collection.length}');
1715
18-
## Usage
19-
### Creating a client instance
20-
JSON:API Client uses the Dart's native HttpClient. Depending on the platform,
21-
you may want to use either the one which comes from `dart:io` or the `BrowserClient`.
16+
final resource = response.data.collection.first.toResource();
17+
print('The first element is ${resource}');
2218
23-
In the VM/Flutter you don't need to provide any dependencies:
24-
```dart
25-
import 'package:json_api/json_api.dart';
19+
print('Attributes:');
20+
resource.attributes.forEach((k, v) => print('$k=$v'));
2621
27-
final client = JsonApiClient();
22+
print('Relationships:');
23+
resource.toOne.forEach((k, v) => print('$k=$v'));
24+
resource.toMany.forEach((k, v) => print('$k=$v'));
25+
}
2826
```
29-
30-
In a browser use the `BrowserClient`:
31-
```dart
32-
import 'package:json_api/json_api.dart';
33-
import 'package:http/browser_client.dart';
34-
35-
final client = JsonApiClient(factory: () => BrowserClient());
27+
To see this in action:
28+
29+
1. start the server:
30+
```
31+
$ dart example/cars_server.dart
32+
Listening on 127.0.0.1:8080
33+
```
34+
2. run the script:
35+
```
36+
$ dart example/fetch_collection.dart
37+
Status: 200
38+
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: *}
39+
The collection page size is 1
40+
The first element is Resource(companies:1)
41+
Attributes:
42+
name=Tesla
43+
nasdaq=null
44+
updatedAt=2019-07-07T13:08:18.125737
45+
Relationships:
46+
hq=Identifier(cities:2)
47+
models=[Identifier(models:1), Identifier(models:2), Identifier(models:3), Identifier(models:4)]
3648
```
3749

38-
### Making requests
39-
The client provides a set of methods to manipulate resources and relationships.
50+
The client provides a set of methods to deal with resources and relationships.
4051
- Fetching
4152
- [fetchCollection](https://pub.dartlang.org/documentation/json_api/latest/json_api/JsonApiClient/fetchCollection.html) - resource collection, either primary or related
4253
- [fetchResource](https://pub.dartlang.org/documentation/json_api/latest/json_api/JsonApiClient/fetchResource.html) - a single resource, either primary or related
@@ -54,92 +65,60 @@ The client provides a set of methods to manipulate resources and relationships.
5465
- [addToMany](https://pub.dartlang.org/documentation/json_api/latest/json_api/JsonApiClient/addToMany.html) - adds the given identifiers to the existing to-many relationship
5566

5667
These methods accept the target URI and the object to update (except for fetch and delete requests).
57-
You can also pass an optional map of HTTP headers e.g. for authentication. The return value
58-
is [Response](https://pub.dartlang.org/documentation/json_api/latest/json_api/Response-class.html) object bearing the
59-
HTTP response status and headers and the JSON:API
60-
document with the primary data according to the type of the request.
68+
You can also pass an optional map of HTTP headers, e.g. for authentication. The return value
69+
is a [Response] object.
6170

62-
Here's a collection fetching example:
71+
You can get the status of the [Response] from either [Response.status] or one of the following properties:
72+
- [Response.isSuccessful]
73+
- [Response.isFailed]
74+
- [Response.isAsync] (see [Asynchronous Processing])
6375

64-
```dart
65-
import 'package:json_api/json_api.dart';
66-
67-
void main() async {
68-
final client = JsonApiClient();
69-
final companiesUri = Uri.parse('http://localhost:8080/companies');
70-
final response = await client.fetchCollection(companiesUri);
71-
72-
print('Status: ${response.status}');
73-
print('Headers: ${response.headers}');
74-
75-
print('The collection page size is ${response.data.collection.length}');
76-
77-
final resource = response.data.collection.first.toResource();
78-
print('The first element is ${resource}');
79-
80-
print('Attributes:');
81-
resource.attributes.forEach((k, v) => print('$k=$v'));
82-
83-
print('Relationships:');
84-
resource.toOne.forEach((k, v) => print('$k=$v'));
85-
resource.toMany.forEach((k, v) => print('$k=$v'));
86-
}
87-
```
88-
89-
### The Response object
90-
The Client always returns a [Response object](https://pub.dartlang.org/documentation/json_api/latest/json_api/Response-class.html)
91-
which indicates either a successful, failed, or async (neither failed nor successful yet, see [here](https://jsonapi.org/recommendations/#asynchronous-processing)) operation.
92-
You can determine which one you have by reading these properties:
93-
- [isSuccessful](https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/isSuccessful.html)
94-
- [isFailed](https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/isFailed.html)
95-
- [isAsync](https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/isAsync.html)
96-
97-
The Response also contains [HTTP status](https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/status.html)
98-
and a map of [HTTP headers](https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/headers.html).
76+
The Response also contains the raw [Response.status] and a map of HTTP headers.
9977
Two headers used by JSON:API can be accessed directly for your convenience:
100-
- [location](https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/location.html) -
101-
the `Location:` header used in creation requests
102-
- [contentLocation](https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/contentLocation.html) -
103-
the `Content-Location:` header used for asynchronous processing
104-
105-
### The Response Document
106-
The most important part of the Response is the [document](https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/document.html)
107-
property which contains the JSON:API document sent by the server (if any). If the document has Primary Data, you
108-
can use [data](https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/data.html)
109-
shortcut to access it directly. Like the Document, the Response is generalized by the expected Primary Data
110-
which depends of the operation. The Document and the rest of the JSON:API object model are parts of [json_api_document](https://pub.dartlang.org/packages/json_api_document)
111-
which is a separate package. Refer to that package for [complete API documentation](https://pub.dartlang.org/documentation/json_api_document/latest/).
112-
This README only gives a brief overview.
113-
114-
#### Successful responses
115-
Most of the times when the response is successful, you can read the [data](https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/data.html)
116-
property directly. It will be either a [primary resource](https://pub.dartlang.org/documentation/json_api_document/latest/json_api_document/ResourceData-class.html)
117-
, primary [resource collection](https://pub.dartlang.org/documentation/json_api_document/latest/json_api_document/ResourceCollectionData-class.html),
118-
or a relationship: [to-one](https://pub.dartlang.org/documentation/json_api_document/latest/json_api_document/ToOne-class.html)
119-
or [to-many](https://pub.dartlang.org/documentation/json_api_document/latest/json_api_document/ToMany-class.html).
120-
The collection-like data may also contain [pagination links](https://pub.dartlang.org/documentation/json_api_document/latest/json_api_document/Pagination-class.html).
78+
- [Response.location] holds the `Location` header used in creation requests
79+
- [Response.contentLocation] holds the `Content-Location` header used for [Asynchronous Processing]
80+
81+
The most important part of the Response is the [Response.document] containing the JSON:API document sent by the server (if any).
82+
If the document has the Primary Data, you can use [Response.data] shortcut to access it directly.
12183

12284
#### Included resources
123-
If you requested related resources to be included in the response (see [Compound Documents](https://jsonapi.org/format/#document-compound-documents)) and the server fulfilled
124-
your request, the [included](https://pub.dartlang.org/documentation/json_api_document/latest/json_api_document/PrimaryData/included.html) property will contain them.
85+
If you requested related resources to be included in the response (see [Compound Documents]) and the server fulfilled
86+
your request, the [PrimaryData.included] property will contain them.
12587

12688
#### Errors
127-
For unsuccessful operations the [data](https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/data.html)
128-
property will be null. If the server decided to include the error details in the response, those can be found in the
129-
[errors](https://pub.dartlang.org/documentation/json_api_document/latest/json_api_document/Document/errors.html) property.
130-
89+
For unsuccessful operations the [Response.data] property will be null.
90+
If the server decided to include the error details in the response, those can be found in the [Document.errors] property.
13191

13292
#### Async processing
133-
Some servers may support [Asynchronous Processing](https://jsonapi.org/recommendations/#asynchronous-processing).
93+
Some servers may support [Asynchronous Processing].
13494
When the server responds with `202 Accepted`, the client expects the Primary Data to always be a Resource (usually
135-
representing a job queue). In this case, the [document](https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/document.html)
136-
and the [data](https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/data.html)
137-
properties of the Response will be null. Instead,
138-
the response document will be placed to [asyncDocument](https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/asyncDocument.html)
139-
(and [asyncData](https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/asyncData.html)).
140-
Also in this case the [contentLocation](https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/contentLocation.html)
95+
representing a job queue). In this case, [Response.document] and [Response.data] will be null. Instead,
96+
the response document will be placed to [Response.asyncDocument] (and [Response.asyncData]).
97+
Also in this case the [Response.contentLocation]
14198
will point to the job queue resource. You can fetch the job queue resource periodically and check
14299
the type of the returned resource. Once the operation is complete, the request will return the created resource.
143100

144-
### Further reading
145-
For more usage examples refer to the [functional tests](https://github.com/f3ath/json-api-dart/tree/master/test/functional).
101+
# Server
102+
The server included in this package is still under development. It is not suitable for real production environment yet
103+
except maybe for really simple demo or testing cases.
104+
105+
## URL Design
106+
##
107+
108+
109+
[Response]: https://pub.dartlang.org/documentation/json_api/latest/json_api/Response-class.html
110+
[Response.data]: https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/data.html
111+
[Response.document]: https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/document.html
112+
[Response.isSuccessful]: https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/isSuccessful.html
113+
[Response.isFailed]: https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/isFailed.html
114+
[Response.isAsync]: https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/isAsync.html
115+
[Response.location]: https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/location.html
116+
[Response.contentLocation]: https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/contentLocation.html
117+
[Response.status]: https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/status.html
118+
[Response.asyncDocument]: https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/asyncDocument.html
119+
[Response.asyncData]: https://pub.dartlang.org/documentation/json_api/latest/json_api/Response/asyncData.html
120+
[PrimaryData.included]: https://pub.dev/documentation/json_api/latest/document/PrimaryData/included.html
121+
[Document.errors]: https://pub.dev/documentation/json_api/latest/document/Document/errors.html
122+
123+
[Asynchronous Processing]: https://jsonapi.org/recommendations/#asynchronous-processing
124+
[Compound Documents]: https://jsonapi.org/format/#document-compound-documents

dart_test.yaml

Lines changed: 0 additions & 1 deletion
This file was deleted.

example/cars_server.dart

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import 'dart:async';
22
import 'dart:io';
33

4-
import 'package:json_api_server/json_api_server.dart';
4+
import 'package:json_api/server.dart';
5+
import 'package:json_api/src/document_builder.dart';
6+
import 'package:json_api/url_design.dart';
57

68
import 'cars_server/controller.dart';
79
import 'cars_server/dao.dart';
@@ -46,22 +48,21 @@ Future<HttpServer> createServer(InternetAddress addr, int port) async {
4648
Company('4')..name = 'Toyota',
4749
].forEach(companies.insert);
4850

49-
final controller = CarsController(
50-
{
51-
'companies': companies,
52-
'cities': cities,
53-
'models': models,
54-
'jobs': JobDAO()
55-
},
56-
(query) => NumberedPage(
57-
int.parse(
58-
PageParameters.fromQuery(query).parameters['number'] ?? '1'),
59-
1));
51+
final pagination = FixedSizePage(1);
52+
53+
final controller = CarsController({
54+
'companies': companies,
55+
'cities': cities,
56+
'models': models,
57+
'jobs': JobDAO()
58+
}, pagination);
6059

6160
final httpServer = await HttpServer.bind(addr, port);
62-
final jsonApiServer =
63-
Server(Routing(Uri.parse('http://localhost:$port')), controller);
61+
final urlDesign = PathBasedUrlDesign(Uri.parse('http://localhost:$port'));
62+
final documentBuilder =
63+
DocumentBuilder(urlBuilder: urlDesign, pagination: pagination);
64+
final jsonApiServer = Server(urlDesign, controller, documentBuilder);
6465

65-
httpServer.forEach(jsonApiServer.process);
66+
httpServer.forEach(jsonApiServer.serve);
6667
return httpServer;
6768
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/// A collection of elements (e.g. resources) returned by the server.
2+
class Collection<T> {
3+
final Iterable<T> elements;
4+
5+
/// Total count of the elements on the server. May be null.
6+
final int totalCount;
7+
8+
Collection(this.elements, [this.totalCount]);
9+
}

0 commit comments

Comments
 (0)