devlucky / kakapo Goto Github PK
View Code? Open in Web Editor NEW🐤Dynamically Mock server behaviors and responses in Swift
Home Page: devlucky.github.io/kakapo-swift
License: MIT License
🐤Dynamically Mock server behaviors and responses in Swift
Home Page: devlucky.github.io/kakapo-swift
License: MIT License
As for now we need to use class methods
Data
type
and id
Investigate if we can use a [JSON schema](json schema) validator (e.g. https://github.com/kylef/JSONSchema.swift) for unit tests
Looks like the tool Kakapo.js uses is also available for us https://github.com/codecov/example-objc
struct Request {
let info: URLInfo
let HTTPBody: NSData?
+ let HTTPHeaders: [String: String]?
}
or (preferred by me)
struct Request {
let info: URLInfo
let URLRequest: NSURLRequest
}
In my opinion NSURLRequest makes it more flexible and not more complex.
Route handling definition:
let server = Kakapo.server()
server.get("/users/:id") (params) {
return server.db.find("user", params.id)
}
server.post("/users") {
}
server.put("/users") {
}
server.del("/users/:id") {
}
output:
28.97s$ bash <(curl -s https://codecov.io/bash)
_____ _
/ ____| | |
| | ___ __| | ___ ___ _____ __
| | / _ \ / _` |/ _ \/ __/ _ \ \ / /
| |___| (_) | (_| | __/ (_| (_) \ V /
\_____\___/ \__,_|\___|\___\___/ \_/
e2e0ba7
(url) https://codecov.io
(root) .
==> Travis CI detected.
-> Swift in /Users/travis/Library/Developer/Xcode/DerivedData
-> Speed up xcode processing by using use -J 'AppName'
+ Building reports for KakapoExample app
+ Building reports for Kakapo framework
+ Building reports for Kakapo framework
+ Building reports for Nimble framework
+ Building reports for Quick framework
+ Building reports for Nimble framework
+ Building reports for Pods_KakapoTests framework```
We should try to check ` -> Speed up xcode processing by using use -J 'AppName'`
https://travis-ci.org/devlucky/Kakapo/jobs/141653869
Might not be a timeout (Actually I'm sure it's not a timeout otherwise would be only that test) but another route handler called, either not unregistering one or a race condition
Say you register:
KakapoServer.get("/user/:id")...
We may want to support unregistering routes, this might be automatically done when Server/Routers are deallocated but this can only happen when #23 is done.
@zzarcon alignment needed ?
Since this is probably the expected behavior for JSON API and the standard
Create sample app that uses Kakapo as a Server
Behaviors
Server responsibilities (needs #56)
I'm having problems running the playground file.
I am on Xcode 7.3.1 and downloaded your latest source. I compiled the iOS scheme to get the import working in the playground. But, once I did that, I got a bunch of other errors...
I'm not sure exactly how to fix this for the playground and was hoping that you could help.
Thanks!
Errors I am getting:
Playground execution failed: README.playground:3:16: error: use of undeclared type 'Serializable'
struct Parrot: Serializable {
^~~~~~~~~~~~
README.playground:13:13: error: use of undeclared type 'Serializable'
struct Zoo: Serializable {
^~~~~~~~~~~~
README.playground:21:19: error: use of undeclared type 'CustomSerializable'
struct CustomZoo: CustomSerializable {
^~~~~~~~~~~~~~~~~~
README.playground:26:42: error: use of undeclared type 'KeyTransformer'
func customSerialize(keyTransformer: KeyTransformer?) -> AnyObject? {
^~~~~~~~~~~~~~
README.playground:41:13: error: use of undeclared type 'JSONAPIEntity'
struct Dog: JSONAPIEntity {
^~~~~~~~~~~~~
README.playground:46:16: error: use of undeclared type 'JSONAPIEntity'
struct Person: JSONAPIEntity {
^~~~~~~~~~~~~
README.playground:71:16: error: use of undeclared type 'Storable'
struct Author: Storable, Serializable {
^~~~~~~~
README.playground:71:26: error: use of undeclared type 'Serializable'
struct Author: Storable, Serializable {
^~~~~~~~~~~~
README.playground:75:26: error: use of undeclared type 'KakapoDB'
init(id: String, db: KakapoDB) {
^~~~~~~~
README.playground:81:17: error: use of undeclared type 'Storable'
struct Article: Storable, Serializable {
^~~~~~~~
README.playground:81:27: error: use of undeclared type 'Serializable'
struct Article: Storable, Serializable {
^~~~~~~~~~~~
README.playground:86:26: error: use of undeclared type 'KakapoDB'
init(id: String, db: KakapoDB) {
^~~~~~~~
README.playground:8:1: error: value of type 'Parrot' has no member 'serialize'
kakapo.serialize()
^~~~~~ ~~~~~~~~~
README.playground:18:27: error: value of type 'Zoo' has no member 'toData'
let json = NSString(data: zoo.toData()!, encoding: NSUTF8StringEncoding)!
^~~ ~~~~~~
README.playground:38:36: error: value of type 'CustomZoo' has no member 'toData'
let customZooJson = NSString(data: customZoo.toData()!, encoding: NSUTF8StringEncoding)!
^~~~~~~~~ ~~~~~~
README.playground:53:20: error: use of unresolved identifier 'JSONAPISerializer'
let serializable = JSONAPISerializer(person, topLevelMeta: ["foo": "bar"])
^~~~~~~~~~~~~~~~~
README.playground:57:14: error: use of unresolved identifier 'Router'
let router = Router.register("https://kakapo.com/api/v1")
^~~~~~
README.playground:58:42: error: use of undeclared type 'Serializable'
router.get("zoo/:animal") { (request) -> Serializable? in
^~~~~~~~~~~~
README.playground:69:10: error: use of unresolved identifier 'KakapoDB'
let db = KakapoDB()
^~~~~~~~
README.playground:98:39: error: use of undeclared type 'Serializable'
router.get("articles") { (request) -> Serializable? in
^~~~~~~~~~~~
README.playground:103:50: error: use of undeclared type 'Serializable'
router.get("articles/:author_id") { (request) -> Serializable? in
^~~~~~~~~~~~
README.playground:110:39: error: use of undeclared type 'Serializable'
router.post("article") { (request) -> Serializable? in
^~~~~~~~~~~~
README.playground:32:39: error: value of type '[Parrot]' has no member 'serialize'
let species = [key("parrot"): parrots.serialize() ?? []]
^~~~~~~ ~~~~~~~~~
To enforce style and conventions
public struct Request {
let info: URLInfo
let HTTPBody: NSData?
}
I don't find it easy to use info.params
and info.queryParams
.
what about:
public struct Request {
let parameters: [String : String]
let components: [String : String]
let HTTPBody: NSData?
let HTTPHeaders: [String: String]?
}
for /user/:id
then we would have:
components = ["id": "value"]
parameters are the url parameters
@devlucky/js-core ?
Won't have to be implemented now
We may need it to avoid boilerplate and duplicated structs.
For the serialization might be easy:
struct User {
let friends: [User]
}
During serialization we have to make sure that friends for User's inside friends is not serialized.
For creation might get more tricky because we can't provide the relationships at initialization time.
Current downsides:
To support /user/:id
You need to create two entities
struct Friend {
let userId
let friendId
}
struct User {
let userId
let friends: [Friend]
}
Gets even worst with user and friendships you need 4 entities to support /user/:id
and /friendship/:id
// for /user/:id
struct Friendship {
let userId
let friendshipId
}
struct User {
let userId
let friendships: [Friendship]
}
// for /friendship/:id
struct Friendship2 {
let user: User
let friendshipId
}
struct User2 {
let userId
let friendshipId
}
Every entity that conform to the protocol (ATM called KakapoSerializable
) will have to implement init(id: String)
. To create entities there might be 2 ways:
db.create(20, User) //pseudocode
// or
db.create(20) { (id) in
return User(name: Faker.name, friends: db.findRandom(Friends))
}
Friends is a relationship od User:
struct Friend: KakapoModel {
let id: String
let name: String
init(id: String) {
self.id = id
name = Faker.name
}
}
User will init is relationship as:
struct User: KakapoModel {
let id: String
let name: String
let friends: [Friend]
init(id: String) {
self.id = id
name = Faker.name
friends = 1...(arc4random()).each{ Friend(id: NSUUID()) }
}
}
We need to provide an easy way to define Factories
factories/user.swift
public class User: KakapoFactory {
var name = Kakapo.name.firstName
var profileImg = Kakapo.image.avatar
var friends = Kakapo.random(['paco', 'pepe'])
}
factories/comment.swift
public class Comment: KakapoFactory {
var author = Kakapo.Factories.User //TODO: is worth to support relationships in the first version?
var message = Kakapo.lorem(50)
}
Scenarios
Again, not sure if is worth it to implement scenarios now, but I think it can be useful...
let server = Kakapo.Server()
let defaultScenario = {
users: server.create('user', 20),
comments: server.create('comment', 50)
}
let performanceCheckScenario = {
users: server.create('user', 1000)
}
//server.use(defaultScenario)
server.use(performanceCheckScenario)
Other solution... just use create
method and don't support scenarios:
let server = Kakapo.Server()
server.create('user', 20),
server.create('comment', 50)
Once we have defined and seeded the DB Kakapo should just use the Factories and create as many instances as needed for the scenario
...
In swift code we usually use camelCase while for JSON other convention (mostly snake_case) are used, I would don't like forcing the user to name the property we might have easy ways to support this:
SnakeCaseTransformer(User()) // convert the serialized object to snake case
This can be done by using CustomSerializable
.
It would be good if the transformed can also be used to rename properties of an entity:
struct User: Serializable {
let id: Int
}
// you want id (Storable) to be named `userId` in the JSON
protocol KeyTransformer: CustomSerializable {
func transformedKey(for candidate: String) -> String
}
extension KeyTransformer {
// default implementation that use `transformedKey...`to transform the keys
}
... for routers with latency
should we?
We want the in-memory database to be flexible, no need to support too complex stuff (otherwise we should actually use a real database) but at least searching for properties of the entity (relationship also?)
We can check some implementation like Realm, or DSL for NSPredicate
or CoreData
.
Would be cool to do something like:
db.find(User.name, equalTo: "name")
but is not possible to do it with properties, only with instance methods.
The most simple and flexible way is to let the user specify the objects he wants... this would remove complexity from the implementation (we are not creating a database) and at the same time support all the use cases! Not sure why we made it so complicate, is just a wrapper to filter
struct DB {
let allObjects: [String: Any]
fune find<T>(filter: (T) -> Bool) -> [T] {
return allObjects[T.type].filter(filter)
}
}
db.find { (user) -> Bool in
return user.name == "Shark"
}
db.find { (user) -> Bool in
return user.friend.age == 40
}
Type won't be inferred you probably need something like let result: [User] = db.find...
or pass it as parameter. I would go for the first option
This should work /user/1?page=1
let server = KakapoServer()
server.get("/user/:id") { (request) in
print('hi')
}
let request = NSMutableURLRequest(URL: NSURL(string: "/user/1?page=1")!)
request.HTTPMethod = "GET"
NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) in
print('bye')
}.resume()
Some test are sometimes failing (https://travis-ci.org/devlucky/Kakapo/jobs/134586851). Those test are about checking that Router doesn't match routes that shouldn't be matched but this cause the request to be really performed and therefore be subject to internet connection slowness and randomness. We should change the implementation to not really perform a request (e.g. A second NSURLProtocol that capture all the request not captured by kaka and return a fixed json distinguishable ... maybe @joanromano has better ideas)
Until now, when registering twice a Router with the same baseURL:
let router = Router.register("http://www.test.com")
router.get("/users"){ request in
return foo
}
let anotherRouter = Router.register("http://www.test.com")
anotherRouter.get("/users"){ request in
return bar
}
Will result on the second handler not called at all, since the server will have two registered Routers, even if they have the same baseURL, and pick the first one from the array when startLoading
on NSURLProtocol
protocol mechanism gets triggered.
Making routers Equatable and using Set on server should fix this and have the expected behaviour.
I think we should we able to provide access to the request body in the request handler:
let server = KakapoServer()
server.get("/user", (request) {
return request.body
}
When using kakapo for prototype or tests it's important to be able to simulate the latency (also Moya provides this for their stub).
Default should be 0 :QDD:
Let's try to understand what's happening. Betting on lib sema 🤑
It hurts me
Using the APIs I noticed that:
http://subdomain.domain.com/api/v3/whatever
while now you are constrained to subdomain.domain.com
. we might skip the http(s) maybe)Bug and missing test:
let router = KakapoServer.register("hubs.runtastic.com")
router.get("/v3/applications/:app/users/:userid/friends_leaderboards/:kind/entries.json") { ... }
matches https://hubs.runtastic.com/leaderboard/v3/applications/com_runtastic_core/users/29516289/friends_leaderboards/distance:time_frame:month:2016_6/entries.json
Note the missing leaderboard
, with it it doesn't match
DB insertion can get exponentially slow (~90s for 20k entries) because the array that olds the objects is a Swift.Array
and has to be copied every time we append new objects.
A cool trick for Swift value types is that they are always copied unless uniquely referenced.
That array is hold by a dictionary and to be mutated must be extracted from it and then re-assigned (2 references) making it not uniquely referenced.
To avoid this and use the uniquely referenced trick we can use a box that holds the array:
class ArrayBox<T> {
var array: [T]
}
then the dictionary will be of type [String: ArrayBox<KStorable>]
And then we can append elements without reference more than once:
dictionary[someKey].array.appendContentOf(....)
If something should change or is not clear about it let's discuss it here
Serialization is done by Mirroring the object (reflection) and support for complex serialization like JSONAPI is done using CustomMirrorType
My thoughts on this:
Store
and Server
, Kakapo prefixes don't make sense with Swift modules as we know.... to prevent the same bug we had for cell registration. Not sure if it can happen here... maybe if you assign to a variable with a downcast
Delete and update entities is not supported.
As discussed we won't support:
user.delete()
user.save()
This would require every entity to hold a database so extra setup for the user.
The idea is to provide:
db.delete(entity)
db.save(entity) // or replace/createIfNeeded/.... [discussion]
KakapoDB
will have to find the entity, make sure that is equal (not only the id) to prevent removing another entity and/or using the wrong database.
If an entity changes and is not saved into db delete will fail because doesn't match the entity in db.
They are not intercepted by NSURLProtocol. Search issues in those project that are describing what to do to support the interception
for the lulz
Would be cool to be able to rely on the CI! Is not just a badge @zzarcon 😁
just for the future... Consider it, not sure we want to do it.
I know that this doesn't compile, but you get the idea...
let server = Kakapo.server()
server.get("/users/:id") (params) {
return server.db.find("user", params.id)
}, 401
ATM Response
is an object that can wrap other Serializable objects to customize the status code and headers.
JSONAPI for example (See #57) might need this behavior and automatically decide the status code and headers so a protocol would be needed to support this behavior and not require special handling in the server side.
Improve our CI:
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.