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
5 changes: 3 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ dart:
- stable
dart_task:
- test: --platform vm
- test: --platform chrome --exclude-tags vm-only
- test: --platform chrome
- test: --platform firefox
- dartfmt: true
- dartanalyzer: --fatal-infos --fatal-warnings lib test
- dartanalyzer: --fatal-infos --fatal-warnings lib test example
10 changes: 5 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
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]

## 0.0.1 - 2019-01-08
## 0.1.0 - 2019-02-27
### Added
- Initial client implementation
- Client: fetch resources, collections, related resources and relationships

[Unreleased]: https://github.com/f3ath/json-api-dart/compare/0.0.1...HEAD
[Unreleased]: https://github.com/f3ath/json-api-dart/compare/0.1.0...HEAD
55 changes: 44 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,51 @@
# Implementation of [JSON:API v1.0](http://jsonapi.org) in Dart

**This is a work in progress. The API may change.**
#### Feature roadmap
##### Client
- [x] Fetching single resources and resource collections
- [x] Fetching relationships and related resources and collections
- [x] Fetching single resources
- [ ] Creating resources
- [ ] Updating resource's attributes
- [ ] Updating resource's relationships
- [ ] Updating relationships
- [ ] Deleting resources
- [ ] Asynchronous processing

## General architecture
##### Server (The Server API is not stable yet!)
- [x] Fetching single resources and resource collections
- [x] Fetching relationships and related resources and collections
- [x] Fetching single resources
- [ ] Creating resources
- [ ] Updating resource's attributes
- [ ] Updating resource's relationships
- [ ] Updating relationships
- [ ] Deleting resources
- [ ] Inclusion of related resources
- [ ] Sparse fieldsets
- [ ] Sorting, pagination, filtering
- [ ] Asynchronous processing

The library consists of three major parts: Document, Server, and Client.
##### Document
- [ ] Support `meta` and `jsonapi` members
- [ ] Structure Validation
- [ ] Naming Validation
- [ ] JSON:API v1.1 features

### Document
This is the core part.
It describes JSON:API Document and its components (e.g. Resource Objects, Identifiers, Relationships, Links),
validation rules (naming conventions, full linkage), and service discovery (e.g. fetching pages and related resources).
### Usage
In the VM:
```dart
import 'package:json_api/client.dart';

### Client
This is a JSON:API client based on Dart's native HttpClient.
final client = JsonApiClient();
```

### Server
A JSON:API server. Routes requests, builds responses.
In a browser:
```dart
import 'package:json_api/client.dart';
import 'package:http/browser_client.dart';

final client = JsonApiClient(factory: () => BrowserClient());
```

For usage examples see a corresponding test in `test/functional`.
2 changes: 0 additions & 2 deletions dart_test.yaml

This file was deleted.

13 changes: 13 additions & 0 deletions example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# JSON:API examples

## [Cars Server](./cars_server)
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.
You can run it locally to play around.

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

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

## [Cars Client](./cars_client.dart)
A simple client for Cars Server API. It is also used in the tests.
35 changes: 35 additions & 0 deletions example/cars_client.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'dart:async';

import 'package:json_api/client.dart';
import 'package:json_api/src/transport/collection_document.dart';
import 'package:json_api/src/transport/relationship.dart';
import 'package:json_api/src/transport/resource_object.dart';

class CarsClient {
final JsonApiClient c;
final _base = Uri.parse('http://localhost:8080');

CarsClient(this.c);

Future<CollectionDocument> fetchCollection(String type) async {
final response = await c.fetchCollection(_base.replace(path: '/$type'));
return response.document;
}

Future<ToOne> fetchToOne(String type, String id, String name) async {
final response = await c
.fetchToOne(_base.replace(path: '/$type/$id/relationships/$name'));
return response.document;
}

Future<ToMany> fetchToMany(String type, String id, String name) async {
final response = await c
.fetchToMany(_base.replace(path: '/$type/$id/relationships/$name'));
return response.document;
}

Future<ResourceObject> fetchResource(String type, String id) async {
final response = await c.fetchResource(_base.replace(path: '/$type/$id'));
return response.document.resourceObject;
}
}
23 changes: 15 additions & 8 deletions example/server/server.dart → example/cars_server.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import 'package:json_api/simple_server.dart';
import 'dart:io';

import 'controller.dart';
import 'dao.dart';
import 'model.dart';
import 'package:json_api/src/server/simple_server.dart';

import 'cars_server/controller.dart';
import 'cars_server/dao.dart';
import 'cars_server/model.dart';

void main() async {
final addr = InternetAddress.loopbackIPv4;
final port = 8080;
await createServer().start(addr, port);
print('Listening on ${addr.host}:$port');
}

SimpleServer createServer() {
final cars = CarDAO();
Expand All @@ -29,8 +38,6 @@ SimpleServer createServer() {
Brand('5', 'Toyota')
].forEach(brands.insert);

final controller =
CarsController({'brands': brands, 'cities': cities, 'cars': cars});

return SimpleServer(controller);
return SimpleServer(
CarsController({'brands': brands, 'cities': cities, 'cars': cars}));
}
36 changes: 36 additions & 0 deletions example/cars_server/controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import 'dart:async';

import 'package:json_api/src/identifier.dart';
import 'package:json_api/src/resource.dart';
import 'package:json_api/src/server/numbered_page.dart';
import 'package:json_api/src/server/resource_controller.dart';

import 'dao.dart';

class CarsController implements ResourceController {
final Map<String, DAO> dao;

CarsController(this.dao);

@override
bool supports(String type) => dao.containsKey(type);

Future<Collection<Resource>> fetchCollection(
String type, Map<String, String> params) async {
final page =
NumberedPage.fromQueryParameters(params, total: dao[type].length);
return Collection(
dao[type]
.fetchCollection(offset: page.number - 1)
.map(dao[type].toResource),
page: page);
}

@override
Stream<Resource> fetchResources(Iterable<Identifier> ids) async* {
for (final id in ids) {
final obj = dao[id.type].fetchById(id.id);
yield obj == null ? null : dao[id.type].toResource(obj);
}
}
}
47 changes: 47 additions & 0 deletions example/cars_server/dao.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'package:json_api/src/identifier.dart';
import 'package:json_api/src/resource.dart';

import 'model.dart';

abstract class DAO<T> {
final _collection = <String, T>{};

int get length => _collection.length;

Resource toResource(T t);

T fetchById(String id) => _collection[id];

void insert(T t); // => collection[t.id] = t;

Iterable<T> fetchCollection({int offset = 0, int limit = 1}) =>
_collection.values.skip(offset).take(limit);
}

class CarDAO extends DAO<Car> {
Resource toResource(Car _) =>
Resource('cars', _.id, attributes: {'name': _.name});

void insert(Car car) => _collection[car.id] = car;
}

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

void insert(City city) => _collection[city.id] = city;
}

class BrandDAO extends DAO<Brand> {
Resource toResource(Brand brand) => Resource('brands', brand.id, attributes: {
'name': brand.name
}, toOne: {
'hq': brand.headquarters == null
? null
: Identifier('cities', brand.headquarters)
}, toMany: {
'models': brand.models.map((_) => Identifier('cars', _)).toList()
});

void insert(Brand brand) => _collection[brand.id] = brand;
}
16 changes: 6 additions & 10 deletions example/server/model.dart → example/cars_server/model.dart
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
abstract class HasId {
String get id;
}

class Brand implements HasId {
final String name;
class Brand {
final String id;
final String headquarters;
final List<String> models;
String name;

Brand(this.id, this.name, {this.headquarters, this.models = const []});
}

class City implements HasId {
final String name;
class City {
final String id;
String name;

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

class Car implements HasId {
final String name;
class Car {
final String id;
String name;

Car(this.id, this.name);
}
10 changes: 0 additions & 10 deletions example/server.dart

This file was deleted.

49 changes: 0 additions & 49 deletions example/server/controller.dart

This file was deleted.

Loading