|
| 1 | +import 'dart:convert'; |
| 2 | + |
| 3 | +import 'package:http/http.dart' as http; |
| 4 | +import 'package:json_api/src/response.dart'; |
| 5 | +import 'package:json_api_document/json_api_document.dart'; |
| 6 | + |
| 7 | +/// JSON:API client |
| 8 | +/// |
| 9 | +/// The client is based on top of Dart's [http.Client] class. To use a custom |
| 10 | +/// client, provide your own [clientFactory]. |
| 11 | +class JsonApiClient { |
| 12 | + JsonApiClient({ |
| 13 | + this.baseUrl = '', |
| 14 | + ClientFactory clientFactory, |
| 15 | + Map<String, String> defaultHeaders = const {}, |
| 16 | + }) : clientFactory = clientFactory ?? (() => http.Client()), |
| 17 | + defaultHeaders = Map.unmodifiable({} |
| 18 | + ..addAll(defaultHeaders) |
| 19 | + ..['Accept'] = Document.mediaType); |
| 20 | + |
| 21 | + final String baseUrl; |
| 22 | + final ClientFactory clientFactory; |
| 23 | + final Map<String, String> defaultHeaders; |
| 24 | + final api = Api('1.0'); |
| 25 | + |
| 26 | + /// Fetches a [Document] containing resource(s) from the given [url]. |
| 27 | + /// Pass a [Map] of [headers] to add extra headers to the request. |
| 28 | + /// |
| 29 | + /// More details: https://jsonapi.org/format/#fetching-resources |
| 30 | + Future<Response> fetchResource(String url, |
| 31 | + {Map<String, String> headers = const {}}) async => |
| 32 | + Response(await _exec((_) => _.get(_url(url), headers: _headers(headers))), |
| 33 | + preferResource: true); |
| 34 | + |
| 35 | + /// Fetches a [Document] containing identifier(s) from the given [url]. |
| 36 | + /// Pass a [Map] of [headers] to add extra headers to the request. |
| 37 | + /// |
| 38 | + /// More details: https://jsonapi.org/format/#fetching-relationships |
| 39 | + fetchRelationship(String url, |
| 40 | + {Map<String, String> headers = const {}}) async => |
| 41 | + Response( |
| 42 | + await _exec((_) => _.get(_url(url), headers: _headers(headers)))); |
| 43 | + |
| 44 | + /// Creates a new [resource] sending a POST request to the [url]. |
| 45 | + /// Pass a [Map] of [headers] to add extra headers to the request. |
| 46 | + /// |
| 47 | + /// More details: https://jsonapi.org/format/#crud-creating |
| 48 | + createResource(String url, Resource resource, |
| 49 | + {Map<String, String> headers = const {}}) async => |
| 50 | + Response( |
| 51 | + await _exec((_) => _.post(_url(url), |
| 52 | + body: _body(resource), |
| 53 | + headers: _headers(headers, withContentType: true))), |
| 54 | + preferResource: true); |
| 55 | + |
| 56 | + /// Deletes the resource sending a DELETE request to the [url]. |
| 57 | + /// Pass a [Map] of [headers] to add extra headers to the request. |
| 58 | + /// |
| 59 | + /// More details: https://jsonapi.org/format/#crud-deleting |
| 60 | + deleteResource(String url, {Map<String, String> headers = const {}}) async => |
| 61 | + Response( |
| 62 | + await _exec((_) => _.delete(_url(url), headers: _headers(headers)))); |
| 63 | + |
| 64 | + /// Updates the [resource] sending a PATCH request to the [url]. |
| 65 | + /// Pass a [Map] of [headers] to add extra headers to the request. |
| 66 | + /// |
| 67 | + /// More details: https://jsonapi.org/format/#crud-updating |
| 68 | + updateResource(String url, Resource resource, |
| 69 | + {Map<String, String> headers = const {}}) async => |
| 70 | + Response( |
| 71 | + await _exec((_) => _.patch(_url(url), |
| 72 | + body: _body(resource), |
| 73 | + headers: _headers(headers, withContentType: true))), |
| 74 | + preferResource: true); |
| 75 | + |
| 76 | + String _body(Resource resource) => |
| 77 | + json.encode(DataDocument.fromResource(resource, api: api)); |
| 78 | + |
| 79 | + String _url(String url) => '${baseUrl}${url}'; |
| 80 | + |
| 81 | + Map<String, String> _headers(Map<String, String> headers, |
| 82 | + {bool withContentType = false}) { |
| 83 | + final h = <String, String>{}..addAll(defaultHeaders)..addAll(headers); |
| 84 | + if (withContentType) h['Content-Type'] = Document.mediaType; |
| 85 | + return h; |
| 86 | + } |
| 87 | + |
| 88 | + Future<http.Response> _exec( |
| 89 | + Future<http.Response> fn(http.Client client)) async { |
| 90 | + final client = clientFactory(); |
| 91 | + try { |
| 92 | + return await fn(client); |
| 93 | + } finally { |
| 94 | + client.close(); |
| 95 | + } |
| 96 | + } |
| 97 | +} |
| 98 | + |
| 99 | +typedef http.Client ClientFactory(); |
0 commit comments