Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v7 #138

Merged
merged 7 commits into from
Nov 13, 2023
Merged

v7 #138

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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