Skip to content

Commit 52e453b

Browse files
committed
More methods. Still work-in-progress
1 parent 3fe5307 commit 52e453b

File tree

5 files changed

+241
-56
lines changed

5 files changed

+241
-56
lines changed

example/example.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import 'package:json_api/json_api.dart';
2+
import 'package:json_api_document/json_api_document.dart';
3+
4+
/// This is a simple example of JsonApiClient.
5+
///
6+
/// Do not forget to start server.dart first!
7+
void main() async {
8+
final client = JsonApiClient(baseUrl: 'http://localhost:8888');
9+
final response = await client.fetchResource('/example');
10+
print((response.document as DataDocument).data.resources.first.attributes);
11+
}

example/server.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import 'dart:convert';
2+
import 'dart:io';
3+
4+
import 'package:json_api_document/json_api_document.dart';
5+
6+
/// Run this server before trying out the example
7+
void main() async {
8+
final port = 8888;
9+
final server = await HttpServer.bind(InternetAddress.loopbackIPv4, port);
10+
final document = DataDocument.fromResource(
11+
Resource('example', '42', attributes: {'message': 'Hello world!'}),
12+
api: Api('1.0'));
13+
server.listen((_) {
14+
_.response.headers.contentType = ContentType.parse(Document.mediaType);
15+
_.response.headers.add('Access-Control-Allow-Origin', '*');
16+
_.response.write(json.encode(document));
17+
_.response.close();
18+
});
19+
print('Server is listening on http://localhost:8888');
20+
print('Press ^C to close');
21+
}

lib/src/json_api_client.dart

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class JsonApiClient {
3636
/// Pass a [Map] of [headers] to add extra headers to the request.
3737
///
3838
/// More details: https://jsonapi.org/format/#fetching-relationships
39-
fetchRelationship(String url,
39+
Future<Response> fetchRelationship(String url,
4040
{Map<String, String> headers = const {}}) async =>
4141
Response(
4242
await _exec((_) => _.get(_url(url), headers: _headers(headers))));
@@ -45,36 +45,68 @@ class JsonApiClient {
4545
/// Pass a [Map] of [headers] to add extra headers to the request.
4646
///
4747
/// More details: https://jsonapi.org/format/#crud-creating
48-
createResource(String url, Resource resource,
48+
Future<Response> createResource(String url, Resource resource,
4949
{Map<String, String> headers = const {}}) async =>
5050
Response(
5151
await _exec((_) => _.post(_url(url),
52-
body: _body(resource),
52+
body: json.encode(DataDocument.fromResource(resource, api: api)),
5353
headers: _headers(headers, withContentType: true))),
5454
preferResource: true);
5555

5656
/// Deletes the resource sending a DELETE request to the [url].
5757
/// Pass a [Map] of [headers] to add extra headers to the request.
5858
///
5959
/// More details: https://jsonapi.org/format/#crud-deleting
60-
deleteResource(String url, {Map<String, String> headers = const {}}) async =>
60+
Future<Response> deleteResource(String url,
61+
{Map<String, String> headers = const {}}) async =>
6162
Response(
6263
await _exec((_) => _.delete(_url(url), headers: _headers(headers))));
6364

6465
/// Updates the [resource] sending a PATCH request to the [url].
6566
/// Pass a [Map] of [headers] to add extra headers to the request.
6667
///
6768
/// More details: https://jsonapi.org/format/#crud-updating
68-
updateResource(String url, Resource resource,
69+
Future<Response> updateResource(String url, Resource resource,
6970
{Map<String, String> headers = const {}}) async =>
7071
Response(
7172
await _exec((_) => _.patch(_url(url),
72-
body: _body(resource),
73+
body: json.encode(DataDocument.fromResource(resource, api: api)),
7374
headers: _headers(headers, withContentType: true))),
7475
preferResource: true);
7576

76-
String _body(Resource resource) =>
77-
json.encode(DataDocument.fromResource(resource, api: api));
77+
/// Creates or updates a to-one relationship sending a corresponding
78+
/// [identifier] via PATCH request to the [url].
79+
/// Pass a [Map] of [headers] to add extra headers to the request.
80+
///
81+
/// More details: https://jsonapi.org/format/#crud-updating-to-one-relationships
82+
Future<Response> setToOne(String url, Identifier identifier,
83+
{Map<String, String> headers = const {}}) async =>
84+
Response(await _exec((_) => _.patch(_url(url),
85+
body: json.encode(DataDocument.fromIdentifier(identifier, api: api)),
86+
headers: _headers(headers, withContentType: true))));
87+
88+
/// Removes a to-one relationship sending PATCH request with "null" data
89+
/// to the [url].
90+
/// Pass a [Map] of [headers] to add extra headers to the request.
91+
///
92+
/// More details: https://jsonapi.org/format/#crud-updating-to-one-relationships
93+
Future<Response> deleteToOne(String url,
94+
{Map<String, String> headers = const {}}) async =>
95+
Response(await _exec((_) => _.patch(_url(url),
96+
body: json.encode(DataDocument.fromNull(api: api)),
97+
headers: _headers(headers, withContentType: true))));
98+
99+
/// Updates a to-many relationship sending the
100+
/// [identifiers] via PATCH request to the [url].
101+
/// Pass a [Map] of [headers] to add extra headers to the request.
102+
///
103+
/// More details: https://jsonapi.org/format/#crud-updating-to-many-relationships
104+
setToMany(String url, List<Identifier> identifiers,
105+
{Map<String, String> headers = const {}}) async =>
106+
Response(await _exec((_) => _.patch(_url(url),
107+
body: json
108+
.encode(DataDocument.fromIdentifierList(identifiers, api: api)),
109+
headers: _headers(headers, withContentType: true))));
78110

79111
String _url(String url) => '${baseUrl}${url}';
80112

pubspec.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
author: "Alexey Karapetov <karapetov@gmail.com>"
2-
description: "JSON:API v1.0 (http://jsonapi.org) HTTP Client"
2+
description: "JSON:API v1.0 (http://jsonapi.org) HTTP Client for Flutter, Server and Browsers"
33
homepage: "https://github.com/f3ath/json-api-dart"
44
name: "json_api"
5-
version: "0.0.1-dev1"
5+
version: "0.0.1-dev3"
66
dependencies:
77
http: "^0.12.0"
8-
json_api_document: "^0.3.8"
8+
json_api_document: "^0.3.9"
99
dev_dependencies:
1010
pubspec_version: "^0.1.0"
11-
test: "1.3.0"
11+
test: "^1.3.0"
1212
environment:
1313
sdk: ">=2.0.0 <3.0.0"

test/json_api_client_test.dart

Lines changed: 165 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ void main() {
6565
});
6666
}, tags: ['vm-only']);
6767

68+
///
69+
6870
group('deleteResource()', () {
6971
test('200 with a meta document', () async {
7072
final doc = MetaDocument({"test": "test"});
@@ -106,49 +108,7 @@ void main() {
106108
});
107109
}, tags: ['vm-only']);
108110

109-
group('fetchRelationship()', () {
110-
test('200 with a document', () async {
111-
final doc = DataDocument.fromResource(appleResource);
112-
113-
server.listen((rq) {
114-
expect(rq.method, 'GET');
115-
expect(rq.headers['foo'], ['bar']);
116-
expect(rq.uri.path, '/fetch');
117-
expect(rq.headers.host, 'localhost');
118-
expect(rq.headers.port, 4041);
119-
rq.response.headers.contentType = ContentType.parse(Document.mediaType);
120-
rq.response.write(json.encode(doc));
121-
rq.response.close();
122-
});
123-
124-
final result =
125-
await client.fetchRelationship('/fetch', headers: {'foo': 'bar'});
126-
expectSame(doc, result.document);
127-
expect((result.document as DataDocument).data,
128-
TypeMatcher<IdentifierData>());
129-
130-
expect(result.status, HttpStatus.ok);
131-
}, tags: ['vm-only']);
132-
133-
test('404 without a document', () async {
134-
server.listen((rq) {
135-
rq.response.statusCode = HttpStatus.notFound;
136-
rq.response.headers.contentType = ContentType.parse(Document.mediaType);
137-
rq.response.close();
138-
});
139-
final result = await client.fetchRelationship('/fetch');
140-
expect(result.document, isNull);
141-
expect(result.status, HttpStatus.notFound);
142-
});
143-
144-
test('invalid Content-Type', () async {
145-
server.listen((rq) {
146-
rq.response.close();
147-
});
148-
expect(() async => await client.fetchRelationship('/fetch'),
149-
throwsA(TypeMatcher<InvalidContentTypeException>()));
150-
});
151-
}, tags: ['vm-only']);
111+
///
152112
153113
group('createResource()', () {
154114
test('201 created', () async {
@@ -199,7 +159,9 @@ void main() {
199159
});
200160
}, tags: ['vm-only']);
201161

202-
group('update resource', () {
162+
///
163+
164+
group('updateResource()', () {
203165
test('200 ok', () async {
204166
server.listen((rq) async {
205167
expect(rq.method, 'PATCH');
@@ -247,4 +209,163 @@ void main() {
247209
throwsA(TypeMatcher<InvalidContentTypeException>()));
248210
});
249211
}, tags: ['vm-only']);
212+
213+
///
214+
215+
group('fetchRelationship()', () {
216+
test('200 with a document', () async {
217+
final doc = DataDocument.fromResource(appleResource);
218+
219+
server.listen((rq) {
220+
expect(rq.method, 'GET');
221+
expect(rq.headers['foo'], ['bar']);
222+
expect(rq.uri.path, '/fetch');
223+
expect(rq.headers.host, 'localhost');
224+
expect(rq.headers.port, 4041);
225+
rq.response.headers.contentType = ContentType.parse(Document.mediaType);
226+
rq.response.write(json.encode(doc));
227+
rq.response.close();
228+
});
229+
230+
final result =
231+
await client.fetchRelationship('/fetch', headers: {'foo': 'bar'});
232+
expectSame(doc, result.document);
233+
expect((result.document as DataDocument).data,
234+
TypeMatcher<IdentifierData>());
235+
236+
expect(result.status, HttpStatus.ok);
237+
}, tags: ['vm-only']);
238+
239+
test('404 without a document', () async {
240+
server.listen((rq) {
241+
rq.response.statusCode = HttpStatus.notFound;
242+
rq.response.headers.contentType = ContentType.parse(Document.mediaType);
243+
rq.response.close();
244+
});
245+
final result = await client.fetchRelationship('/fetch');
246+
expect(result.document, isNull);
247+
expect(result.status, HttpStatus.notFound);
248+
});
249+
250+
test('invalid Content-Type', () async {
251+
server.listen((rq) {
252+
rq.response.close();
253+
});
254+
expect(() async => await client.fetchRelationship('/fetch'),
255+
throwsA(TypeMatcher<InvalidContentTypeException>()));
256+
});
257+
}, tags: ['vm-only']);
258+
259+
///
260+
261+
group('setToOne()', () {
262+
final identifier = Identifier('apples', '42');
263+
264+
test('200', () async {
265+
server.listen((rq) async {
266+
expect(rq.method, 'PATCH');
267+
expect(rq.headers['foo'], ['bar']);
268+
expect(rq.uri.path, '/update');
269+
expect(rq.headers.host, 'localhost');
270+
expect(rq.headers.port, 4041);
271+
final doc = Document.fromJson(json.decode(await utf8.decodeStream(rq)));
272+
expect(doc, TypeMatcher<DataDocument>());
273+
expect((doc as DataDocument).data, TypeMatcher<IdentifierData>());
274+
expect((doc as DataDocument).data.identifies(Resource('apples', '42')),
275+
true);
276+
277+
rq.response.headers.contentType = ContentType.parse(Document.mediaType);
278+
rq.response.close();
279+
});
280+
281+
final result = await client
282+
.setToOne('/update', identifier, headers: {'foo': 'bar'});
283+
284+
expect(result.status, HttpStatus.ok);
285+
}, tags: ['vm-only']);
286+
287+
test('invalid Content-Type', () async {
288+
server.listen((rq) {
289+
rq.response.close();
290+
});
291+
expect(() async => await client.setToOne('/update', identifier),
292+
throwsA(TypeMatcher<InvalidContentTypeException>()));
293+
});
294+
}, tags: ['vm-only']);
295+
296+
///
297+
298+
group('deleteToOne()', () {
299+
300+
test('200', () async {
301+
server.listen((rq) async {
302+
expect(rq.method, 'PATCH');
303+
expect(rq.headers['foo'], ['bar']);
304+
expect(rq.uri.path, '/update');
305+
expect(rq.headers.host, 'localhost');
306+
expect(rq.headers.port, 4041);
307+
final doc = Document.fromJson(json.decode(await utf8.decodeStream(rq)));
308+
expect(doc, TypeMatcher<DataDocument>());
309+
expect((doc as DataDocument).data, TypeMatcher<NullData>());
310+
311+
rq.response.headers.contentType = ContentType.parse(Document.mediaType);
312+
rq.response.close();
313+
});
314+
315+
final result = await client
316+
.deleteToOne('/update', headers: {'foo': 'bar'});
317+
318+
expect(result.status, HttpStatus.ok);
319+
}, tags: ['vm-only']);
320+
321+
test('invalid Content-Type', () async {
322+
server.listen((rq) {
323+
rq.response.close();
324+
});
325+
expect(() async => await client.deleteToOne('/update'),
326+
throwsA(TypeMatcher<InvalidContentTypeException>()));
327+
});
328+
}, tags: ['vm-only']);
329+
330+
///
331+
332+
group('setToMany()', () {
333+
final apple1 = Identifier('apples', '1');
334+
final apple2 = Identifier('apples', '2');
335+
336+
test('200', () async {
337+
server.listen((rq) async {
338+
expect(rq.method, 'PATCH');
339+
expect(rq.headers['foo'], ['bar']);
340+
expect(rq.uri.path, '/update');
341+
expect(rq.headers.host, 'localhost');
342+
expect(rq.headers.port, 4041);
343+
final doc = Document.fromJson(json.decode(await utf8.decodeStream(rq)));
344+
expect(doc, TypeMatcher<DataDocument>());
345+
expect((doc as DataDocument).data, TypeMatcher<IdentifierListData>());
346+
expect((doc as DataDocument).data.identifies(Resource('apples', '1')),
347+
true);
348+
expect((doc as DataDocument).data.identifies(Resource('apples', '2')),
349+
true);
350+
351+
rq.response.headers.contentType = ContentType.parse(Document.mediaType);
352+
rq.response.close();
353+
});
354+
355+
final result = await client
356+
.setToMany('/update', [apple1, apple2], headers: {'foo': 'bar'});
357+
358+
expect(result.status, HttpStatus.ok);
359+
}, tags: ['vm-only']);
360+
361+
test('invalid Content-Type', () async {
362+
server.listen((rq) {
363+
rq.response.close();
364+
});
365+
expect(() async => await client.setToMany('/update', [apple1, apple2]),
366+
throwsA(TypeMatcher<InvalidContentTypeException>()));
367+
});
368+
}, tags: ['vm-only']);
369+
370+
250371
}

0 commit comments

Comments
 (0)