Skip to content

Commit

Permalink
Release v6 (#135)
Browse files Browse the repository at this point in the history
  • Loading branch information
f3ath authored Sep 8, 2023
1 parent 8695d7d commit 2cb8402
Show file tree
Hide file tree
Showing 94 changed files with 1,751 additions and 1,374 deletions.
4 changes: 1 addition & 3 deletions .github/workflows/dart.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,11 @@ jobs:
- uses: actions/checkout@v2
- name: Install dependencies
run: dart pub get
- name: Print Dart version
run: dart --version
- name: Format
run: dart format --output none --set-exit-if-changed example lib test
- name: Analyzer
run: dart analyze --fatal-infos --fatal-warnings
- name: Tests
run: dart test --coverage=.coverage -j1
- name: Coverage
run: dart run coverage:format_coverage -l -c -i .coverage --report-on=lib | dart run check_coverage:check_coverage
run: dart run coverage:format_coverage -l -c -i .coverage --report-on=lib | dart run check_coverage:check_coverage 98
18 changes: 14 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [6.0.0] - 2023-09-07
### Added
- Partial support for JSON:API v1.1

### Changed
- A bunch of BC-breaking changes. Please refer to the tests.
- Min SDK version is 3.0.0
- Migrated to `http_interop`. You'll have to install `http_interop_http` or another implementation to get the HTTP client.

### Removed
- Query filter.

## [5.4.0] - 2023-04-30
### Changed
- Switch to http\_interop packages.
Expand All @@ -13,9 +25,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Client MessageConverter class to control HTTP request/response conversion.

### Fixed
- Content-Type being set for GET/OPTIONS requests ([issue](https://github.com/f3ath/json-api-dart/issues/129)).

## [5.2.0] - 2022-06-01
### Added
- Support for included resources in create and update methods. Author: @kszczek
Expand Down Expand Up @@ -229,6 +238,7 @@ the Document model.
### Added
- Client: fetch resources, collections, related resources and relationships

[6.0.0]: https://github.com/f3ath/json-api-dart/compare/5.4.0...6.0.0
[5.4.0]: https://github.com/f3ath/json-api-dart/compare/5.3.0...5.4.0
[5.3.0]: https://github.com/f3ath/json-api-dart/compare/5.2.0...5.3.0
[5.2.0]: https://github.com/f3ath/json-api-dart/compare/5.1.0...5.2.0
Expand Down Expand Up @@ -263,4 +273,4 @@ the Document model.
[0.4.0]: https://github.com/f3ath/json-api-dart/compare/0.3.0...0.4.0
[0.3.0]: https://github.com/f3ath/json-api-dart/compare/0.2.0...0.3.0
[0.2.0]: https://github.com/f3ath/json-api-dart/compare/0.1.0...0.2.0
[0.1.0]: https://github.com/f3ath/json-api-dart/releases/tag/0.1.0
[0.1.0]: https://github.com/f3ath/json-api-dart/releases/tag/0.1.0
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

TL;DR:
```dart
import 'package:http_interop_http/http_interop_http.dart';
import 'package:json_api/client.dart';
import 'package:json_api/routing.dart';
Expand All @@ -14,7 +15,7 @@ void main() async {
/// The [RoutingClient] is most likely the right choice.
/// It has methods covering many standard use cases.
final client = RoutingClient(uriDesign);
final client = RoutingClient(uriDesign, Client(OneOffHandler()));
try {
/// Fetch the collection.
Expand All @@ -31,14 +32,13 @@ void main() async {
});
} on RequestFailure catch (e) {
/// Catch error response
e.errors.forEach((error) => print('${error.title}'));
for (var error in e.errors) {
print(error.title);
}
}
}
```
This is a work-in-progress. You can help it by submitting a PR with a feature or documentation improvements.





[JSON:API]: https://jsonapi.org
45 changes: 35 additions & 10 deletions example/client.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'package:http/http.dart' as http;
import 'package:http_interop_http/http_interop_http.dart';
import 'package:json_api/client.dart';
import 'package:json_api/routing.dart';

Expand All @@ -8,27 +10,50 @@ void main() async {
/// Use the standard recommended URL structure or implement your own
final uriDesign = StandardUriDesign(Uri.parse(baseUri));

/// This is the Dart's standard HTTP client.
/// Do not forget to close it in the end.
final httpClient = http.Client();

/// This is the adapter which decouples this JSON:API implementation
/// from the HTTP client.
/// Learn more: https://pub.dev/packages/http_interop
final httpHandler = ClientWrapper(httpClient);

/// This is the basic JSON:API client. It is flexible but not very convenient
/// to use, because you would need to remember a lot of JSON:API protocol details.
/// We will use another wrapper on top of it.
final jsonApiClient = Client(httpHandler);

/// The [RoutingClient] is most likely the right choice.
/// It has methods covering many standard use cases.
final client = RoutingClient(uriDesign);
/// It is called routing because it routes the calls to the correct
/// URLs depending on the use case. Take a look at its methods, they cover
/// all the standard scenarios specified by the JSON:API standard.
final client = RoutingClient(uriDesign, jsonApiClient);

try {
/// Fetch the collection.
/// See other methods to query and manipulate resources.
final response = await client.fetchCollection('colors');

final resources = response.collection;
resources.map((resource) => resource.attributes).forEach((attr) {
final name = attr['name'];
final red = attr['red'];
final green = attr['green'];
final blue = attr['blue'];
/// The fetched collection allows us to iterate over the resources
/// and to look into their attributes
for (final resource in response.collection) {
final {
'name': name,
'red': red,
'green': green,
'blue': blue,
} = resource.attributes;
print('${resource.type}:${resource.id}');
print('$name - $red:$green:$blue');
});
}
} on RequestFailure catch (e) {
/// Catch error response
for (var error in e.errors) {
for (final error in e.errors) {
print(error.title);
}
}

/// Free up the resources before exit.
httpClient.close();
}
54 changes: 15 additions & 39 deletions example/server.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:io';

import 'package:http_interop/http_interop.dart' as interop;
import 'package:json_api/document.dart';
import 'package:json_api/http.dart';
import 'package:json_api/routing.dart';
Expand All @@ -10,19 +11,24 @@ import 'server/in_memory_repo.dart';
import 'server/json_api_server.dart';
import 'server/repository.dart';
import 'server/repository_controller.dart';
import 'server/try_catch_handler.dart';

Future<void> main() async {
final host = 'localhost';
final port = 8080;
final resources = ['colors'];
final repo = InMemoryRepo(resources);
await addColors(repo);
final repo = InMemoryRepo(['colors']);
await initRepo(repo);
final controller = RepositoryController(repo, Uuid().v4);
HttpHandler handler = Router(controller, StandardUriDesign.matchTarget);
handler = TryCatchHandler(handler, onError: convertError);
interop.Handler handler =
ControllerRouter(controller, StandardUriDesign.matchTarget);
handler = TryCatchHandler(handler,
onError: ErrorConverter(onError: (e, stack) async {
stderr.writeln(e);
return Response(500,
document: OutboundErrorDocument(
[ErrorObject(title: 'Internal Server Error')]));
}));
handler = LoggingHandler(handler,
onRequest: (r) => print('${r.method.toUpperCase()} ${r.uri}'),
onRequest: (r) => print('${r.method} ${r.uri}'),
onResponse: (r) => print('${r.statusCode}'));
final server = JsonApiServer(handler, host: host, port: port);

Expand All @@ -33,14 +39,10 @@ Future<void> main() async {

await server.start();

print('The server is listening at $host:$port.'
' Try opening the following URL(s) in your browser:');
for (var resource in resources) {
print('http://$host:$port/$resource');
}
print('The server is listening at $host:$port.');
}

Future addColors(Repository repo) async {
Future initRepo(Repository repo) async {
final models = {
{'name': 'Salmon', 'r': 250, 'g': 128, 'b': 114},
{'name': 'Pink', 'r': 255, 'g': 192, 'b': 203},
Expand All @@ -55,29 +57,3 @@ Future addColors(Repository repo) async {
await repo.persist('colors', model);
}
}

Future<HttpResponse> convertError(dynamic error) async {
if (error is MethodNotAllowed) {
return Response.methodNotAllowed();
}
if (error is UnmatchedTarget) {
return Response.badRequest();
}
if (error is CollectionNotFound) {
return Response.notFound(
OutboundErrorDocument([ErrorObject(title: 'CollectionNotFound')]));
}
if (error is ResourceNotFound) {
return Response.notFound(
OutboundErrorDocument([ErrorObject(title: 'ResourceNotFound')]));
}
if (error is RelationshipNotFound) {
return Response.notFound(
OutboundErrorDocument([ErrorObject(title: 'RelationshipNotFound')]));
}
return Response(500,
document: OutboundErrorDocument([
ErrorObject(
title: 'Error: ${error.runtimeType}', detail: error.toString())
]));
}
36 changes: 18 additions & 18 deletions example/server/cors_handler.dart
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
import 'package:http_interop/http_interop.dart';
import 'package:json_api/http.dart';

class CorsHandler implements HttpHandler {
class CorsHandler implements Handler {
CorsHandler(this._inner);

final HttpHandler _inner;
final Handler _inner;

@override
Future<HttpResponse> handle(HttpRequest request) async {
Future<Response> handle(Request request) async {
final headers = {
'Access-Control-Allow-Origin': request.headers['origin'] ?? '*',
'Access-Control-Expose-Headers': 'Location',
'Access-Control-Allow-Origin': [request.headers.last('origin') ?? '*'],
'Access-Control-Expose-Headers': ['Location'],
};

if (request.isOptions) {
if (request.method.equals('OPTIONS')) {
const methods = ['POST', 'GET', 'DELETE', 'PATCH', 'OPTIONS'];
return HttpResponse(204)
..headers.addAll({
...headers,
'Access-Control-Allow-Methods':
// TODO: Make it work for all browsers. Why is toUpperCase() needed?
request.headers['Access-Control-Request-Method']?.toUpperCase() ??
methods.join(', '),
'Access-Control-Allow-Headers':
request.headers['Access-Control-Request-Headers'] ?? '*',
});
return Response(
204,
Body.empty(),
Headers({
...headers,
'Access-Control-Allow-Methods':
request.headers['Access-Control-Request-Method'] ?? methods,
'Access-Control-Allow-Headers':
request.headers['Access-Control-Request-Headers'] ?? ['*'],
}));
}
return await _inner.handle(request)
..headers.addAll(headers);
return await _inner.handle(request..headers.addAll(headers));
}
}
53 changes: 0 additions & 53 deletions example/server/demo_handler.dart

This file was deleted.

Loading

0 comments on commit 2cb8402

Please sign in to comment.