Skip to content

Commit

Permalink
v7 (#138)
Browse files Browse the repository at this point in the history
  • Loading branch information
f3ath committed Nov 13, 2023
1 parent e5d1aed commit faae2e5
Show file tree
Hide file tree
Showing 24 changed files with 238 additions and 175 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/dart.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ jobs:
- 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 98
run: dart run coverage:format_coverage -l -c -i .coverage --report-on=lib | dart run check_coverage:check_coverage
14 changes: 14 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: Publish to pub.dev

on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+*'

jobs:
publish:
permissions:
id-token: write # Required for authentication using OIDC
uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1
# with:
# working-directory: path/to/package/within/repository
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ 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).

## [7.0.0] - 2023-11-12
### Changed
- Migrated to `http_interop` v1.

## [6.0.1] - 2023-09-11
### Fixed
- `NewRelationship` was not exported
Expand Down Expand Up @@ -242,6 +246,7 @@ the Document model.
### Added
- Client: fetch resources, collections, related resources and relationships

[7.0.0]: https://github.com/f3ath/json-api-dart/compare/6.0.1...7.0.0
[6.0.1]: https://github.com/f3ath/json-api-dart/compare/6.0.0...6.0.1
[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
Expand Down
10 changes: 5 additions & 5 deletions example/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ Future<void> main() async {
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')]));
}));
stderr.writeln(e);
return Response(500,
document: OutboundErrorDocument(
[ErrorObject(title: 'Internal Server Error')]));
}).call);
handler = LoggingHandler(handler,
onRequest: (r) => print('${r.method} ${r.uri}'),
onResponse: (r) => print('${r.statusCode}'));
Expand Down
6 changes: 3 additions & 3 deletions example/server/cors_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ class CorsHandler implements Handler {
'Access-Control-Expose-Headers': ['Location'],
};

if (request.method.equals('OPTIONS')) {
if (request.method == 'options') {
const methods = ['POST', 'GET', 'DELETE', 'PATCH', 'OPTIONS'];
return Response(
204,
Body.empty(),
Headers({
Body(),
Headers.from({
...headers,
'Access-Control-Allow-Methods':
request.headers['Access-Control-Request-Method'] ?? methods,
Expand Down
4 changes: 1 addition & 3 deletions lib/http.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import 'dart:convert';

import 'package:http_interop/http_interop.dart';

class StatusCode {
Expand Down Expand Up @@ -29,7 +27,7 @@ class StatusCode {
}

class Json extends Body {
Json(Map<String, Object?> json) : super(jsonEncode(json), utf8);
Json(Map<String, Object?> super.object) : super.json();
}

class LoggingHandler implements Handler {
Expand Down
8 changes: 4 additions & 4 deletions lib/src/client/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,17 @@ class Client {
/// Sends the [request] to the given [uri].
Future<Response> send(Uri uri, Request request) async {
final json = await _encode(request.document);
final body = http.Body(json, utf8);
final headers = http.Headers({
final body = http.Body.text(json, utf8);
final headers = http.Headers.from({
'Accept': [mediaType],
if (json.isNotEmpty) 'Content-Type': [mediaType],
...request.headers
});
final url = request.query.isEmpty
? uri
: uri.replace(queryParameters: request.query.toQuery());
final response = await _handler
.handle(http.Request(http.Method(request.method), url, body, headers));
final response =
await _handler.handle(http.Request(request.method, url, body, headers));

final document = await _decode(response);
return Response(response, document);
Expand Down
2 changes: 1 addition & 1 deletion lib/src/client/request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ class Request {
final query = Query();

/// HTTP headers.
final headers = Headers({});
final headers = Headers();
}
44 changes: 18 additions & 26 deletions lib/src/document/inbound_document.dart
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,11 @@ class _Parser {

/// Decodes Link from [json]. Returns the decoded object.
/// If the [json] has incorrect format, throws [FormatException].
Link _link(Object json) {
if (json is String) return Link(Uri.parse(json));
if (json is Map) {
return Link(Uri.parse(json['href']))..meta.addAll(meta(json));
}
throw FormatException('Invalid JSON');
}
Link _link(Object json) => switch (json) {
String() => Link(Uri.parse(json)),
Map() => Link(Uri.parse(json['href']))..meta.addAll(meta(json)),
_ => throw FormatException('Invalid JSON')
};

Map<String, Object?> _getAttributes(Map json) =>
json.get<Map<String, Object?>>('attributes', orGet: () => {});
Expand All @@ -166,25 +164,19 @@ class _Parser {
.get<Map>('relationships', orGet: () => {})
.map((key, value) => MapEntry(key, newRelationship(value)));

Relationship _rel(data) {
if (data == null) return ToOne.empty();
if (data is Map) return ToOne(identifier(data));
if (data is List) return ToMany(data.whereType<Map>().map(identifier));
throw FormatException('Invalid relationship object');
}

NewRelationship _newRel(data) {
if (data == null) {
return NewToOne.empty();
}
if (data is Map) {
return NewToOne(newIdentifier(data));
}
if (data is List) {
return NewToMany(data.whereType<Map>().map(newIdentifier));
}
throw FormatException('Invalid relationship object');
}
Relationship _rel(data) => switch (data) {
null => ToOne.empty(),
Map() => ToOne(identifier(data)),
List() => ToMany(data.whereType<Map>().map(identifier)),
_ => throw FormatException('Invalid relationship object')
};

NewRelationship _newRel(data) => switch (data) {
null => NewToOne.empty(),
Map() => NewToOne(newIdentifier(data)),
List() => NewToMany(data.whereType<Map>().map(newIdentifier)),
_ => throw FormatException('Invalid relationship object')
};
}

extension _TypedGeter on Map {
Expand Down
2 changes: 0 additions & 2 deletions lib/src/document/new_relationship.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ class NewRelationship with IterableMixin<NewIdentifier> {
if (meta.isNotEmpty) 'meta': meta,
};

// coverage:ignore-start
@override
Iterator<NewIdentifier> get iterator => <NewIdentifier>[].iterator;
// coverage:ignore-end
}
21 changes: 8 additions & 13 deletions lib/src/document/new_resource.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ class NewResource {
if (r is NewToMany) {
return ToMany(r.map((identifier) => _toIdentifier(identifier, id)));
}
// coverage:ignore-line
throw StateError('Unexpected relationship type: ${r.runtimeType}');
}

Expand All @@ -70,16 +69,12 @@ class NewResource {
return _toIdentifier(identifier, id);
}

Identifier _toIdentifier(NewIdentifier identifier, String id) {
switch (identifier) {
case Identifier():
return identifier;
case LocalIdentifier():
if (identifier.type == type && identifier.lid == lid) {
return identifier.toIdentifier(id);
}
throw StateError(
'Unmatched local id: "${identifier.lid}". Expected "$lid".');
}
}
Identifier _toIdentifier(NewIdentifier identifier, String id) =>
switch (identifier) {
Identifier() => identifier,
LocalIdentifier() => (identifier.type == type && identifier.lid == lid)
? identifier.toIdentifier(id)
: throw StateError(
'Unmatched local id: "${identifier.lid}". Expected "$lid".')
};
}
24 changes: 8 additions & 16 deletions lib/src/routing/standard_uri_design.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,14 @@ class StandardUriDesign implements UriDesign {
/// `/books`, `/books/42`, `/books/42/authors`
static final pathOnly = StandardUriDesign(Uri(path: '/'));

static Target? matchTarget(Uri uri) {
final s = uri.pathSegments;
if (s.length == 1) {
return Target(s.first);
}
if (s.length == 2) {
return ResourceTarget(s.first, s.last);
}
if (s.length == 3) {
return RelatedTarget(s.first, s[1], s.last);
}
if (s.length == 4 && s[2] == 'relationships') {
return RelationshipTarget(s.first, s[1], s.last);
}
return null;
}
static Target? matchTarget(Uri uri) => switch ((uri.pathSegments)) {
[var type] => Target(type),
[var type, var id] => ResourceTarget(type, id),
[var type, var id, var rel] => RelatedTarget(type, id, rel),
[var type, var id, 'relationships', var rel] =>
RelationshipTarget(type, id, rel),
_ => null
};

final Uri base;

Expand Down
68 changes: 25 additions & 43 deletions lib/src/server/controller_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,49 +18,31 @@ class ControllerRouter implements Handler {
Future<Response> handle(Request request) async {
_validate(request);
final target = _matchTarget(request.uri);
if (target is RelationshipTarget) {
if (request.method.equals('GET')) {
return await _controller.fetchRelationship(request, target);
}
if (request.method.equals('POST')) {
return await _controller.addMany(request, target);
}
if (request.method.equals('PATCH')) {
return await _controller.replaceRelationship(request, target);
}
if (request.method.equals('DELETE')) {
return await _controller.deleteMany(request, target);
}
throw MethodNotAllowed(request.method.value);
}
if (target is RelatedTarget) {
if (request.method.equals('GET')) {
return await _controller.fetchRelated(request, target);
}
throw MethodNotAllowed(request.method.value);
}
if (target is ResourceTarget) {
if (request.method.equals('GET')) {
return await _controller.fetchResource(request, target);
}
if (request.method.equals('PATCH')) {
return await _controller.updateResource(request, target);
}
if (request.method.equals('DELETE')) {
return await _controller.deleteResource(request, target);
}
throw MethodNotAllowed(request.method.value);
}
if (target is Target) {
if (request.method.equals('GET')) {
return await _controller.fetchCollection(request, target);
}
if (request.method.equals('POST')) {
return await _controller.createResource(request, target);
}
throw MethodNotAllowed(request.method.value);
}
throw UnmatchedTarget(request.uri);
return await switch (target) {
RelationshipTarget() => switch (request.method) {
'get' => _controller.fetchRelationship(request, target),
'post' => _controller.addMany(request, target),
'patch' => _controller.replaceRelationship(request, target),
'delete' => _controller.deleteMany(request, target),
_ => throw MethodNotAllowed(request.method)
},
RelatedTarget() => switch (request.method) {
'get' => _controller.fetchRelated(request, target),
_ => throw MethodNotAllowed(request.method)
},
ResourceTarget() => switch (request.method) {
'get' => _controller.fetchResource(request, target),
'patch' => _controller.updateResource(request, target),
'delete' => _controller.deleteResource(request, target),
_ => throw MethodNotAllowed(request.method)
},
Target() => switch (request.method) {
'get' => _controller.fetchCollection(request, target),
'post' => _controller.createResource(request, target),
_ => throw MethodNotAllowed(request.method)
},
_ => throw UnmatchedTarget(request.uri)
};
}

void _validate(Request request) {
Expand Down
8 changes: 2 additions & 6 deletions lib/src/server/response.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import 'dart:convert';

import 'package:http_interop/http_interop.dart' as http;
import 'package:json_api/document.dart';
import 'package:json_api/http.dart';
Expand All @@ -10,10 +8,8 @@ class Response<D extends OutboundDocument> extends http.Response {
Response(int statusCode, {D? document})
: super(
statusCode,
document != null
? http.Body(jsonEncode(document), utf8)
: http.Body.empty(),
http.Headers({})) {
document != null ? http.Body.json(document) : http.Body(),
http.Headers()) {
if (document != null) {
headers['Content-Type'] = [mediaType];
}
Expand Down
14 changes: 7 additions & 7 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
name: json_api
version: 6.0.1
version: 7.0.0
homepage: https://github.com/f3ath/json-api-dart
description: A framework-agnostic implementations of JSON:API Client and Server. Supports JSON:API v1.0 (https://jsonapi.org)
environment:
sdk: '>=3.0.0 <4.0.0'
sdk: '>=3.1.0 <4.0.0'

dependencies:
http_parser: ^4.0.0
http_interop: ^0.8.0
http_interop: ^1.0.0

dev_dependencies:
lints: ^2.1.1
lints: ^3.0.0
test: ^1.21.1
stream_channel: ^2.1.0
uuid: ^3.0.0
uuid: ^4.2.0
coverage: ^1.3.0
check_coverage: ^0.0.4
http: ^1.1.0
http_interop_http: ^0.6.0
http_interop_io: ^0.6.0
http_interop_http: ^0.7.0
http_interop_io: ^0.7.0


cider:
Expand Down
Loading

0 comments on commit faae2e5

Please sign in to comment.