Created
June 22, 2014 20:40
-
-
Save vsavkin/e80d68d8bfc0b2074c37 to your computer and use it in GitHub Desktop.
AngularDart REST Client Spike
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
library rest_api_spec; | |
import 'package:guinness/guinness.dart'; | |
import 'package:unittest/unittest.dart' hide expect; | |
import 'dart:async'; | |
import 'dart:mirrors'; | |
import 'dart:convert' as cvt; | |
import 'package:angular/angular.dart'; | |
import 'package:angular/mock/module.dart'; | |
/** | |
* ------------------------------------- | |
* -------------Library----------------- | |
* ------------------------------------- | |
*/ | |
class Resource { | |
final Object type, id; | |
final Map content; | |
Resource(this.type, this.id, this.content); | |
} | |
Resource res(type, id, [content]) => new Resource(type, id, content); | |
class ResourceStore { | |
Http http; | |
Resource parent; | |
Map config = {}; | |
ResourceStore(this.http); | |
ResourceStore scope(parent) => new ResourceStore(http)..config = config..parent = parent; | |
Future<Resource> one(resourceType, resourceId) { | |
final url = "${_route(resourceType)}/$resourceId"; | |
final parse = (resp) => _parse(resourceType, resp.data); | |
return http.get(url).then(parse); | |
} | |
Future<List<Resource>> list(resourceType) { | |
final url = _route(resourceType); | |
final parseOne = (d) => _parse(resourceType, d); | |
final parse = (resp) => resp.data.map(parseOne).toList(); | |
return http.get(url).then(parse); | |
} | |
Future save(Resource resource) { | |
final content = new cvt.JsonEncoder().convert(resource.content); | |
final url = "${_route(resource.type)}/${resource.id}"; | |
return http.put(url, content); | |
} | |
_parse(resourceType, data) => res(resourceType, data["id"], data); | |
_route(resourceType) { | |
if (parent != null) { | |
return "/${_resourceRoute(parent.type)}/${parent.id}/${_resourceRoute(resourceType)}"; | |
} else { | |
return "/${_resourceRoute(resourceType)}"; | |
} | |
} | |
_resourceRoute(resourceType) => | |
config[resourceType] == null ? resourceType : config[resourceType]['route']; | |
} | |
class ObjectStore { | |
ResourceStore resourceStore; | |
ObjectStore(this.resourceStore); | |
ObjectStore scope(obj) => | |
new ObjectStore(resourceStore.scope(_wrapInResource(obj))); | |
Future one(type, id) => | |
resourceStore.one(type, id).then(_deserialize); | |
Future<List> list(type) => | |
resourceStore.list(type).then((list) => list.map(_deserialize).toList()); | |
Future save(object) => | |
resourceStore.save(_wrapInResource(object)); | |
_deserialize(resource) { | |
final c = resourceStore.config[resource.type]; | |
return c["deserialize"](resource); | |
} | |
_wrapInResource(object) { | |
final c = resourceStore.config[object.runtimeType]; | |
return c["serialize"](object); | |
} | |
} | |
class SimpleObjectStore { | |
ResourceStore resourceStore; | |
SimpleObjectStore(this.resourceStore); | |
SimpleObjectStore scope(type, id) => | |
new SimpleObjectStore(resourceStore.scope(res(type, id))); | |
Future one(type, id) => | |
resourceStore.one(type, id).then(_deserialize); | |
Future<List> list(type) => | |
resourceStore.list(type).then((list) => list.map(_deserialize).toList()); | |
Future save(type, id, content) => | |
resourceStore.save(res(type, id, content)); | |
_deserialize(resource) => resource.content; | |
} | |
class MirrorBasedSerializer { | |
List<String> attrs; | |
MirrorBasedSerializer(this.attrs); | |
Resource call(obj) { | |
final m = reflect(obj); | |
final id = m.getField(#id).reflectee; | |
final content = attrs.fold({}, (res, String attr) { | |
res[attr] = m.getField(new Symbol(attr)).reflectee; | |
return res; | |
}); | |
return res(obj.runtimeType, id, content); | |
} | |
} | |
class MirrorBasedDeserializer { | |
List<String> attrs; | |
MirrorBasedDeserializer(this.attrs); | |
Object call(Resource r) { | |
final m = reflectClass(r.type).newInstance(const Symbol(''), []); | |
r.content.forEach((name, value) { | |
m.setField(new Symbol(name), value); | |
}); | |
return m.reflectee; | |
} | |
} | |
/** | |
* ------------------------------------- | |
* -------------Example----------------- | |
* ------------------------------------- | |
*/ | |
class Post { | |
int id; | |
String title; | |
} | |
class Comment { | |
int id; | |
String text; | |
} | |
Post deserializePost(Resource r) => new Post() | |
..id = r.id | |
..title = r.content["title"]; | |
Resource serializePost(Post post) => | |
res(Post, post.id, {"id" : post.id, "title" : post.title}); | |
Comment deserializeComment(Resource r) => new Comment() | |
..id = r.id | |
..text = r.content["text"]; | |
Resource serializeComment(Comment comment) => | |
res(Comment, comment.id, {"id" : comment.id, "text" : comment.text}); | |
/** | |
* ------------------------------------- | |
* --------------Tests------------------ | |
* ------------------------------------- | |
*/ | |
main() { | |
beforeEach(setUpInjector); | |
afterEach(tearDownInjector); | |
beforeEach(module((Module m) => m..bind(ResourceStore)..bind(ObjectStore)..bind(SimpleObjectStore))); | |
describe("ObjectStore", () { | |
beforeEach(inject((ResourceStore store) { | |
store.config = { | |
Post : { | |
"route": 'posts', | |
"deserialize" : deserializePost, | |
"serialize" : serializePost | |
}, | |
Comment : { | |
"route": "comments", | |
"deserialize" : deserializeComment, | |
"serialize" : serializeComment | |
} | |
}; | |
})); | |
it("returns a post", inject((MockHttpBackend hb, ObjectStore store) { | |
hb.when("/posts/123").respond('{"id": 123, "title" : "SampleTitle"}'); | |
waitForHttp(store.one(Post, 123), (Post post) { | |
expect(post.title).toEqual("SampleTitle"); | |
}); | |
})); | |
it("returns many posts", inject((MockHttpBackend hb, ObjectStore store) { | |
hb.when("/posts").respond('[{"id": 123, "title" : "SampleTitle"}]'); | |
waitForHttp(store.list(Post), (List<Post> posts) { | |
expect(posts.length).toEqual(1); | |
expect(posts[0].title).toEqual("SampleTitle"); | |
}); | |
})); | |
it("returns a comment", inject((MockHttpBackend hb, ObjectStore store) { | |
final post = new Post()..id = 123; | |
hb.when("/posts/123/comments/456").respond('{"id": 123, "text" : "SampleComment"}'); | |
waitForHttp(store.scope(post).one(Comment, 456), (Comment comment) { | |
expect(comment.text).toEqual("SampleComment"); | |
}); | |
})); | |
it("saves a post", inject((MockHttpBackend hb, ObjectStore store) { | |
hb.expectPUT("/posts/123", '{"id":123,"title":"New"}').respond(['OK']); | |
final post = new Post()..id = 123..title = "New"; | |
waitForHttp(store.save(post)); | |
})); | |
it("saves a comment", inject((MockHttpBackend hb, ObjectStore store) { | |
hb.expectPUT("/posts/123/comments/456", '{"id":456,"text":"New"}').respond(['OK']); | |
final post = new Post()..id = 123; | |
final comment = new Comment()..id = 456..text = "New"; | |
waitForHttp(store.scope(post).save(comment)); | |
})); | |
}); | |
describe("SimpleObjectStore", () { | |
it("returns a comment", inject((MockHttpBackend hb, SimpleObjectStore store) { | |
hb.when("/posts/123/comments/456").respond('{"id":123, "text" : "SampleComment"}'); | |
waitForHttp(store.scope('posts', 123).one("coment", 456), (Map comment) { | |
expect(comment["text"]).toEqual("SampleComment"); | |
}); | |
})); | |
it("saves a comment", inject((MockHttpBackend hb, SimpleObjectStore store) { | |
hb.expectPUT("/posts/123/comments/456", '{"id":456,"text":"New"}').respond(['OK']); | |
final comment = {"id" : 456, "text": 'New'}; | |
waitForHttp(store.scope('posts', 123).save("comments", 456, comment)); | |
})); | |
}); | |
describe("ObjectStore extras", () { | |
beforeEach(inject((ResourceStore store) { | |
store.config = { | |
Post : { | |
"route": 'posts', | |
"deserialize" : new MirrorBasedDeserializer(["id", "title"]), | |
"serialize" : new MirrorBasedSerializer(["id", "title"]) | |
} | |
}; | |
})); | |
it("returns a post", inject((MockHttpBackend hb, ObjectStore store) { | |
hb.when("/posts/123").respond('{"id": 123, "title" : "SampleTitle"}'); | |
waitForHttp(store.one(Post, 123), (Post post) { | |
expect(post.title).toEqual("SampleTitle"); | |
}); | |
})); | |
it("saves a post", inject((MockHttpBackend hb, ObjectStore store) { | |
hb.expectPUT("/posts/123", '{"id":123,"title":"New"}').respond(['OK']); | |
final post = new Post()..id = 123..title = "New"; | |
waitForHttp(store.save(post)); | |
})); | |
}); | |
describe("ResourceStore", () { | |
beforeEach(inject((ResourceStore store) { | |
store.config = { | |
"posts" : {"route": 'posts'}, | |
"comments": {"route": "comments"} | |
}; | |
})); | |
it("returns a post", inject((MockHttpBackend hb, ResourceStore store) { | |
hb.when("/posts/123").respond('{"id": 123, "title" : "SampleTitle"}'); | |
waitForHttp(store.one("posts", 123), (Resource post) { | |
expect(post.id).toEqual(123); | |
expect(post.content["title"]).toEqual("SampleTitle"); | |
}); | |
})); | |
it("returns many posts", inject((MockHttpBackend hb, ResourceStore store) { | |
hb.when("/posts").respond('[{"id": 123, "title" : "SampleTitle"}]'); | |
waitForHttp(store.list("posts"), (List<Resource> posts) { | |
expect(posts.length).toEqual(1); | |
expect(posts[0].content["title"]).toEqual("SampleTitle"); | |
}); | |
})); | |
it("returns a comment", inject((MockHttpBackend hb, ResourceStore store) { | |
hb.when("/posts/123/comments/456").respond('{"id": 456, "text" : "SampleComment"}'); | |
waitForHttp(store.scope(res("posts", 123)).one("comments", 456), (Resource comment) { | |
expect(comment.id).toEqual(456); | |
expect(comment.content["text"]).toEqual("SampleComment"); | |
}); | |
})); | |
it("saves a post", inject((MockHttpBackend hb, ResourceStore store) { | |
hb.expectPUT("/posts/123", '{"id":123,"title":"New"}').respond(['OK']); | |
waitForHttp(store.save(res("posts", 123, {"id": 123, "title": "New"}))); | |
})); | |
it("saves a comment", inject((MockHttpBackend hb, ResourceStore store) { | |
hb.expectPUT("/posts/123/comments/456", '{"id":456,"text":"New"}').respond(['OK']); | |
final post = res("posts", 123); | |
final comment = res("comments", 456, {"id": 456, "text" : "New"}); | |
waitForHttp(store.scope(post).save(comment)); | |
})); | |
it("deafults route to resource name", inject((MockHttpBackend hb, ResourceStore store) { | |
hb.when("/some/123").respond('{"id": 123, "field" : "value"}'); | |
waitForHttp(store.one("some", 123), (Resource post) { | |
expect(post.id).toEqual(123); | |
expect(post.content["field"]).toEqual("value"); | |
}); | |
})); | |
}); | |
guinness.initSpecs(); | |
} | |
waitForHttp(future, [callback]) => | |
scheduleMicrotask(() { | |
callback = callback != null ? callback : (_){}; | |
inject((MockHttpBackend http) => http.flush()); | |
future.then(callback); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment