Skip to content

Commit 869ce1a

Browse files
authored
Resource creation (#26)
1 parent c9bdde2 commit 869ce1a

33 files changed

+606
-311
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
### Added
9+
- Improved ResourceController error handling
10+
- Resource creation
811

912
## 0.1.0 - 2019-02-27
1013
### Added

README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,19 @@
55
- [x] Fetching single resources and resource collections
66
- [x] Fetching relationships and related resources and collections
77
- [x] Fetching single resources
8-
- [ ] Creating resources
8+
- [x] Creating resources
99
- [ ] Updating resource's attributes
1010
- [ ] Updating resource's relationships
1111
- [ ] Updating relationships
1212
- [ ] Deleting resources
1313
- [ ] Asynchronous processing
14+
- [ ] Optional check for `Content-Type` header in incoming responses
1415

1516
##### Server (The Server API is not stable yet!)
1617
- [x] Fetching single resources and resource collections
1718
- [x] Fetching relationships and related resources and collections
1819
- [x] Fetching single resources
19-
- [ ] Creating resources
20+
- [x] Creating resources
2021
- [ ] Updating resource's attributes
2122
- [ ] Updating resource's relationships
2223
- [ ] Updating relationships
@@ -25,10 +26,13 @@
2526
- [ ] Sparse fieldsets
2627
- [ ] Sorting, pagination, filtering
2728
- [ ] Asynchronous processing
29+
- [ ] Optional check for `Content-Type` header in incoming requests
30+
- [ ] Support annotations in resource mappers (?)
2831

2932
##### Document
3033
- [ ] Support `meta` and `jsonapi` members
31-
- [ ] Structure Validation
34+
- [ ] Structure Validation including compound documents
35+
- [ ] Support relationship objects lacking the `data` member
3236
- [ ] Naming Validation
3337
- [ ] JSON:API v1.1 features
3438

@@ -48,4 +52,4 @@ import 'package:http/browser_client.dart';
4852
final client = JsonApiClient(factory: () => BrowserClient());
4953
```
5054

51-
For usage examples see a corresponding test in `test/functional`.
55+
For usage examples see the [functional tests](https://github.com/f3ath/json-api-dart/tree/master/test/functional).

dart_test.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
concurrency: 1

example/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# JSON:API examples
22

33
## [Cars Server](./cars_server)
4-
This is a simple JSON:API server which is used in the tests. It provides an API to a collection to car brands ad models.
4+
This is a simple JSON:API server which is used in the tests. It provides an API to a collection to car companies ad models.
55
You can run it locally to play around.
66

77
- In you console run `dart example/cars_server.dart`, this will start the server at port 8080.
8-
- Open http://localhost:8080/brands in the browser.
8+
- Open http://localhost:8080/companies in the browser.
99

1010
**Warning: Server API is not stable yet!**
1111

example/cars_client.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import 'dart:async';
22

33
import 'package:json_api/client.dart';
4-
import 'package:json_api/src/transport/collection_document.dart';
5-
import 'package:json_api/src/transport/relationship.dart';
6-
import 'package:json_api/src/transport/resource_object.dart';
4+
import 'package:json_api/src/document/collection_document.dart';
5+
import 'package:json_api/src/document/relationship.dart';
6+
import 'package:json_api/src/document/resource_object.dart';
77

88
class CarsClient {
99
final JsonApiClient c;

example/cars_server.dart

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ void main() async {
1414
}
1515

1616
SimpleServer createServer() {
17-
final cars = CarDAO();
17+
final models = ModelDAO();
1818
[
19-
Car('1', 'Roadster'),
20-
Car('2', 'Model S'),
21-
Car('3', 'Model X'),
22-
Car('4', 'Model 3'),
23-
].forEach(cars.insert);
19+
Model('1', 'Roadster'),
20+
Model('2', 'Model S'),
21+
Model('3', 'Model X'),
22+
Model('4', 'Model 3'),
23+
].forEach(models.insert);
2424

2525
final cities = CityDAO();
2626
[
@@ -29,15 +29,27 @@ SimpleServer createServer() {
2929
City('3', 'Ingolstadt'),
3030
].forEach(cities.insert);
3131

32-
final brands = BrandDAO();
32+
final companies = CompanyDAO();
3333
[
34-
Brand('1', 'Tesla', headquarters: '2', models: ['1', '2', '3', '4']),
35-
Brand('2', 'BMW', headquarters: '1'),
36-
Brand('3', 'Audi', headquarters: '3'),
37-
Brand('4', 'Ford'),
38-
Brand('5', 'Toyota')
39-
].forEach(brands.insert);
40-
41-
return SimpleServer(
42-
CarsController({'brands': brands, 'cities': cities, 'cars': cars}));
34+
Company('1', 'Tesla', headquarters: '2', models: ['1', '2', '3', '4']),
35+
Company('2', 'BMW', headquarters: '1'),
36+
Company('3', 'Audi'),
37+
].forEach(companies.insert);
38+
39+
return SimpleServer(CarsController(
40+
{'companies': companies, 'cities': cities, 'models': models}));
41+
}
42+
43+
class Url {
44+
static final _base = Uri.parse('http://localhost:8080');
45+
46+
static collection(String type) => _base.replace(path: '/$type');
47+
48+
static resource(String type, String id) => _base.replace(path: '/$type/$id');
49+
50+
static related(String type, String id, String rel) =>
51+
_base.replace(path: '/$type/$id/$rel');
52+
53+
static relationship(String type, String id, String rel) =>
54+
_base.replace(path: '/$type/$id/relationships/$rel');
4355
}

example/cars_server/controller.dart

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:json_api/src/identifier.dart';
44
import 'package:json_api/src/resource.dart';
55
import 'package:json_api/src/server/numbered_page.dart';
66
import 'package:json_api/src/server/resource_controller.dart';
7+
import 'package:uuid/uuid.dart';
78

89
import 'dao.dart';
910

@@ -30,7 +31,30 @@ class CarsController implements ResourceController {
3031
Stream<Resource> fetchResources(Iterable<Identifier> ids) async* {
3132
for (final id in ids) {
3233
final obj = dao[id.type].fetchById(id.id);
33-
yield obj == null ? null : dao[id.type].toResource(obj);
34+
if (obj == null) {
35+
throw ResourceControllerException(404, detail: 'Resource not found');
36+
}
37+
yield dao[id.type].toResource(obj);
3438
}
3539
}
40+
41+
@override
42+
Future<Resource> createResource(
43+
String type, Resource resource, Map<String, String> params) async {
44+
if (type != resource.type) {
45+
throw ResourceControllerException(409, detail: 'Incompatible type');
46+
}
47+
Object obj;
48+
if (resource.hasId) {
49+
if (dao[type].fetchById(resource.id) != null) {
50+
throw ResourceControllerException(409,
51+
detail: 'Resource already exists');
52+
}
53+
obj = dao[type].create(resource);
54+
} else {
55+
obj = dao[type].create(resource.replace(id: Uuid().v4()));
56+
}
57+
dao[type].insert(obj);
58+
return dao[type].toResource(obj);
59+
}
3660
}

example/cars_server/dao.dart

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ abstract class DAO<T> {
1010

1111
Resource toResource(T t);
1212

13+
T create(Resource resource);
14+
1315
T fetchById(String id) => _collection[id];
1416

1517
void insert(T t); // => collection[t.id] = t;
@@ -18,30 +20,43 @@ abstract class DAO<T> {
1820
_collection.values.skip(offset).take(limit);
1921
}
2022

21-
class CarDAO extends DAO<Car> {
22-
Resource toResource(Car _) =>
23-
Resource('cars', _.id, attributes: {'name': _.name});
23+
class ModelDAO extends DAO<Model> {
24+
Resource toResource(Model _) =>
25+
Resource('models', _.id, attributes: {'name': _.name});
26+
27+
void insert(Model model) => _collection[model.id] = model;
2428

25-
void insert(Car car) => _collection[car.id] = car;
29+
Model create(Resource r) {
30+
return Model(r.id, r.attributes['name']);
31+
}
2632
}
2733

2834
class CityDAO extends DAO<City> {
2935
Resource toResource(City _) =>
3036
Resource('cities', _.id, attributes: {'name': _.name});
3137

3238
void insert(City city) => _collection[city.id] = city;
39+
40+
City create(Resource r) {
41+
return City(r.id, r.attributes['name']);
42+
}
3343
}
3444

35-
class BrandDAO extends DAO<Brand> {
36-
Resource toResource(Brand brand) => Resource('brands', brand.id, attributes: {
37-
'name': brand.name
45+
class CompanyDAO extends DAO<Company> {
46+
Resource toResource(Company company) =>
47+
Resource('companies', company.id, attributes: {
48+
'name': company.name
3849
}, toOne: {
39-
'hq': brand.headquarters == null
50+
'hq': company.headquarters == null
4051
? null
41-
: Identifier('cities', brand.headquarters)
52+
: Identifier('cities', company.headquarters)
4253
}, toMany: {
43-
'models': brand.models.map((_) => Identifier('cars', _)).toList()
54+
'models': company.models.map((_) => Identifier('models', _)).toList()
4455
});
4556

46-
void insert(Brand brand) => _collection[brand.id] = brand;
57+
void insert(Company company) => _collection[company.id] = company;
58+
59+
Company create(Resource r) {
60+
return Company(r.id, r.attributes['name']);
61+
}
4762
}

example/cars_server/model.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
class Brand {
1+
class Company {
22
final String id;
33
final String headquarters;
44
final List<String> models;
55
String name;
66

7-
Brand(this.id, this.name, {this.headquarters, this.models = const []});
7+
Company(this.id, this.name, {this.headquarters, this.models = const []});
88
}
99

1010
class City {
@@ -14,9 +14,9 @@ class City {
1414
City(this.id, this.name);
1515
}
1616

17-
class Car {
17+
class Model {
1818
final String id;
1919
String name;
2020

21-
Car(this.id, this.name);
21+
Model(this.id, this.name);
2222
}

lib/client.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
export 'package:json_api/src/client/client.dart';
2+
export 'package:json_api/src/identifier.dart';
3+
export 'package:json_api/src/resource.dart';

0 commit comments

Comments
 (0)