Skip to content
Merged
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ 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).

## [Unreleased]
### Added
- Resource attributes update

## [0.2.0] - 2019-03-01
### Added
- Improved ResourceController error handling
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
- [x] Fetching single resources
- [x] Creating resources
- [x] Deleting resources
- [ ] Updating resource's attributes
- [ ] Updating resource's relationships
- [x] Updating resource's attributes
- [x] Updating resource's relationships
- [ ] Updating relationships
- [ ] Asynchronous processing
- [ ] Optional check for `Content-Type` header in incoming responses
Expand All @@ -19,8 +19,8 @@
- [x] Fetching single resources
- [x] Creating resources
- [x] Deleting resources
- [ ] Updating resource's attributes
- [ ] Updating resource's relationships
- [x] Updating resource's attributes
- [x] Updating resource's relationships
- [ ] Updating relationships
- [ ] Inclusion of related resources
- [ ] Sparse fieldsets
Expand Down
25 changes: 15 additions & 10 deletions example/cars_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,29 @@ void main() async {
SimpleServer createServer() {
final models = ModelDAO();
[
Model('1', 'Roadster'),
Model('2', 'Model S'),
Model('3', 'Model X'),
Model('4', 'Model 3'),
Model('1')..name = 'Roadster',
Model('2')..name = 'Model S',
Model('3')..name = 'Model X',
Model('4')..name = 'Model 3',
].forEach(models.insert);

final cities = CityDAO();
[
City('1', 'Munich'),
City('2', 'Palo Alto'),
City('3', 'Ingolstadt'),
City('1')..name = 'Munich',
City('2')..name = 'Palo Alto',
City('3')..name = 'Ingolstadt',
].forEach(cities.insert);

final companies = CompanyDAO();
[
Company('1', 'Tesla', headquarters: '2', models: ['1', '2', '3', '4']),
Company('2', 'BMW', headquarters: '1'),
Company('3', 'Audi'),
Company('1')
..name = 'Tesla'
..headquarters = '2'
..models.addAll(['1', '2', '3', '4']),
Company('2')
..name = 'BMW'
..headquarters = '1',
Company('3')..name = 'Audi',
].forEach(companies.insert);

return SimpleServer(CarsController(
Expand Down
14 changes: 14 additions & 0 deletions example/cars_server/controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,18 @@ class CarsController implements ResourceController {
}
return null;
}

@override
Future<Resource> updateResource(String type, String id, Resource resource,
JsonApiHttpRequest request) async {
if (resource.type != type) {
throw ResourceControllerException(409,
title: 'Type mismatch',
detail: 'Resource type does not match the endpoint');
}
if (dao[type].fetchById(id) == null) {
throw ResourceControllerException(404, detail: 'Resource not found');
}
return dao[type].update(id, resource);
}
}
48 changes: 43 additions & 5 deletions example/cars_server/dao.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ abstract class DAO<T> {
_collection.remove(id);
return 0;
}

Resource update(String id, Resource resource) {
throw UnimplementedError();
}
}

class ModelDAO extends DAO<Model> {
Expand All @@ -33,7 +37,13 @@ class ModelDAO extends DAO<Model> {
void insert(Model model) => _collection[model.id] = model;

Model create(Resource r) {
return Model(r.id, r.attributes['name']);
return Model(r.id)..name = r.attributes['name'];
}

@override
Resource update(String id, Resource resource) {
_collection[id].name = resource.attributes['name'];
return null;
}
}

Expand All @@ -44,14 +54,16 @@ class CityDAO extends DAO<City> {
void insert(City city) => _collection[city.id] = city;

City create(Resource r) {
return City(r.id, r.attributes['name']);
return City(r.id)..name = r.attributes['name'];
}
}

class CompanyDAO extends DAO<Company> {
Resource toResource(Company company) =>
Resource('companies', company.id, attributes: {
'name': company.name
'name': company.name,
'nasdaq': company.nasdaq,
'updatedAt': company.updatedAt.toIso8601String()
}, toOne: {
'hq': company.headquarters == null
? null
Expand All @@ -60,10 +72,15 @@ class CompanyDAO extends DAO<Company> {
'models': company.models.map((_) => Identifier('models', _)).toList()
});

void insert(Company company) => _collection[company.id] = company;
void insert(Company company) {
company.updatedAt = DateTime.now();
_collection[company.id] = company;
}

Company create(Resource r) {
return Company(r.id, r.attributes['name']);
return Company(r.id)
..name = r.attributes['name']
..updatedAt = DateTime.now();
}

@override
Expand All @@ -74,4 +91,25 @@ class CompanyDAO extends DAO<Company> {
_collection.remove(id);
return deps;
}

@override
Resource update(String id, Resource resource) {
// TODO: What is Resource type or id is changed?
final company = _collection[id];
if (resource.attributes.containsKey('name')) {
company.name = resource.attributes['name'];
}
if (resource.attributes.containsKey('nasdaq')) {
company.nasdaq = resource.attributes['nasdaq'];
}
if (resource.toOne.containsKey('hq')) {
company.headquarters = resource.toOne['hq'].id;
}
if (resource.toMany.containsKey('models')) {
company.models.clear();
company.models.addAll(resource.toMany['models'].map((_) => _.id));
}
company.updatedAt = DateTime.now();
return toResource(company);
}
}
17 changes: 12 additions & 5 deletions example/cars_server/model.dart
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
class Company {
final String id;
final String headquarters;
final List<String> models;
String headquarters;
final models = <String>[];

/// Company name
String name;

Company(this.id, this.name, {this.headquarters, this.models = const []});
/// NASDAQ symbol
String nasdaq;

DateTime updatedAt = DateTime.now();

Company(this.id);
}

class City {
final String id;
String name;

City(this.id, this.name);
City(this.id);
}

class Model {
final String id;
String name;

Model(this.id, this.name);
Model(this.id);
}
2 changes: 1 addition & 1 deletion lib/client.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export 'package:json_api/document.dart';
export 'package:json_api/src/client/client.dart';
export 'package:json_api/src/document.dart';
File renamed without changes.
45 changes: 24 additions & 21 deletions lib/src/client/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,16 @@ class JsonApiClient {

/// Creates a new resource. The resource will be added to a collection
/// according to its type.
///
/// https://jsonapi.org/format/#crud-creating
Future<Response<ResourceDocument>> createResource(Uri uri, Resource resource,
{Map<String, String> headers}) =>
_post(ResourceDocument.fromJson, uri,
ResourceDocument(ResourceObject.fromResource(resource)), headers);

/// Deletes the resource.
///
/// https://jsonapi.org/format/#crud-deleting
Future<Response<MetaDocument>> deleteResource(Uri uri,
{Map<String, String> headers}) =>
_delete(MetaDocument.fromJson, uri, headers);
Expand All @@ -76,15 +80,14 @@ class JsonApiClient {
// {Map<String, String> headers}) =>
// _post(ToMany.fromJson, uri,
// ToMany(identifiers.map(IdentifierObject.fromIdentifier)), headers);
//
// Future<Response<ResourceDocument>> updateResource(Uri uri, Resource resource,
// {Map<String, String> headers}) async =>
// _patch(
// ResourceDocument.fromJson,
// uri,
// ResourceDocument(ResourceObject(resource.type, resource.id,
// attributes: resource.attributes)),
// headers);

/// Updates the resource via PATCH request.
///
/// https://jsonapi.org/format/#crud-updating
Future<Response<ResourceDocument>> updateResource(Uri uri, Resource resource,
{Map<String, String> headers}) async =>
_patch(ResourceDocument.fromJson, uri,
ResourceDocument(ResourceObject.fromResource(resource)), headers);

Future<Response<D>> _get<D extends Document>(
ResponseParser<D> parse, uri, Map<String, String> headers) =>
Expand Down Expand Up @@ -117,18 +120,18 @@ class JsonApiClient {
'Content-Type': contentType,
})));

// Future<Response<D>> _patch<D extends Document>(ResponseParser<D> parse, uri,
// Document document, Map<String, String> headers) =>
// _call(
// parse,
// (_) => _.patch(uri,
// body: json.encode(document),
// headers: {}
// ..addAll(headers ?? {})
// ..addAll({
// 'Accept': contentType,
// 'Content-Type': contentType,
// })));
Future<Response<D>> _patch<D extends Document>(ResponseParser<D> parse, uri,
Document document, Map<String, String> headers) =>
_call(
parse,
(_) => _.patch(uri,
body: json.encode(document),
headers: {}
..addAll(headers ?? {})
..addAll({
'Accept': contentType,
'Content-Type': contentType,
})));

Future<Response<D>> _call<D extends Document>(ResponseParser<D> parse,
Future<http.Response> fn(http.Client client)) async {
Expand Down
3 changes: 3 additions & 0 deletions lib/src/server/json_api_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,7 @@ abstract class JsonApiController {

Future<ServerResponse> deleteResource(
String type, String id, JsonApiHttpRequest request);

Future<ServerResponse> updateResource(
String type, String id, JsonApiHttpRequest request);
}
5 changes: 3 additions & 2 deletions lib/src/server/request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';

enum HttpMethod { get, post, put, delete }
enum HttpMethod { get, post, put, delete, patch }

abstract class JsonApiHttpRequest {
HttpMethod get method;
Expand All @@ -23,7 +23,8 @@ class NativeHttpRequestAdapter implements JsonApiHttpRequest {
'get': HttpMethod.get,
'post': HttpMethod.post,
'delete': HttpMethod.delete,
'put': HttpMethod.put
'put': HttpMethod.put,
'patch': HttpMethod.patch,
}[request.method.toLowerCase()];

Uri get uri => request.uri;
Expand Down
4 changes: 4 additions & 0 deletions lib/src/server/resource_controller.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:async';

import 'package:json_api/document.dart';
import 'package:json_api/src/document/identifier.dart';
import 'package:json_api/src/document/resource.dart';
import 'package:json_api/src/server/page.dart';
Expand Down Expand Up @@ -29,6 +30,9 @@ abstract class ResourceController {
Future<Resource> createResource(
String type, Resource resource, JsonApiHttpRequest request);

Future<Resource> updateResource(
String type, String id, Resource resource, JsonApiHttpRequest request);

/// This method should delete the resource specified by [type] and [id].
/// It may return metadata to be sent back as 200 OK response.
/// If an empty map or null is returned, the server will respond with 204 No Content.
Expand Down
Loading