GithubHelp home page GithubHelp logo

devlucky / kakapo Goto Github PK

View Code? Open in Web Editor NEW
761.0 761.0 44.0 2.01 MB

🐤Dynamically Mock server behaviors and responses in Swift

Home Page: devlucky.github.io/kakapo-swift

License: MIT License

Swift 97.54% Objective-C 1.10% Ruby 1.36%
dsl func json-api kakapo opinionated opinionated-dsl plugable plugable-interface unplugged

kakapo's Introduction

Kakapo partyparrot

Language: Swift Build Status Version DocCov codecov codebeat badge License Platform Carthage compatible

Dynamically Mock server behaviors and responses.

Contents

Kakapo is a dynamic mocking library. It allows you to replicate your backend APIs and logic.
With Kakapo you can easily prototype your application based on your API specifications.

Why Kakapo?

A common approach when testing network requests is to stub them with fake network responses from local files or recorded requests. This has some disadvantages:

  • All files need to be updated when the APIs are updated.
  • Lots of files have to be generated and included in the project.
  • Are just static responses that can only be used for unit tests since they don't reflect backend behaviors and state.

While still this approach may work good, Kakapo will be a game changer in your network tests: it will give you complete control when it comes to simulating backend behaviors. Moreover, is not just unit testing: you can even take a step further and prototype your application before having a real service behind!
With Kakapo you can just create Swift structs/classes/enums that are automatically serialized to JSON.

7 billion people on Earth

Less than 150 Kakapo

Time is critical donate to Kakapo recovery

Features

  • Dynamic mocking
  • Prototyping
  • Swift 3.0 compatible (from version 2.0.0, master branch)
  • Swift 2.2 and 2.3 compatible (from version 1.0.0, branch feature/legacy-swift)
  • Compatible with Platform
  • Protocol oriented and pluggable
  • Fully customizable by defining custom serialization and custom responses
  • Out-of-the-box serialization
  • JSONAPI support

Installation

Using CocoaPods:

use_frameworks!
pod 'Kakapo'

Using Carthage:

github "devlucky/Kakapo"

Usage

NOTE: The project also contains a README.playground. Check it out to see some examples of the key features of Kakapo.

Kakapo is made with an easy-to-use design in mind. To quickly get started, you can create a Router that intercepts network requests like this:

let router = Router.register("http://www.test.com")
router.get("/users") { request in
  return ["id" : 2, "name": "Kakapo"]
}

You might be wondering where the dynamic part is; here is when the different modules of Kakapo take place:

let store = Store()
store.create(User.self, number: 20)

router.get("/users") { request in
  return store.findAll(User.self)
}

Now, we've created 20 random User objects and mocked our request to return them.

Let's get a closer look to the different features:

Serializable protocol

Kakapo uses the Serializable protocol in order to serialize objects to JSON. Any type can be serialized as long as it conforms to this protocol:

struct User: Serializable {
  let name: String
}

let user = User(name: "Alex")
let serializedUser = user.serialized()
//  -> ["name": "Alex"]

Also, standard library types are supported: this means that Array, Dictionary or Optional can be serialized:

let serializedUserArray = [user].serialized()
// -> [["name": "Alex"]]
let serializedUserDictionary = ["test": user].serialized()
// -> ["test": ["name": "Alex"]]

Router - Register and Intercept

Kakapo uses Routers in order to keep track of the registered endpoints that have to be intercepted.
You can match any relative path from the registered base URL, as long as the components are matching the request's components. You can use wildcard components:

let router = Router.register("http://www.test.com")

// Will match http://www.test.com/users/28
router.get("/users/:id") { ... }

// Will match http://www.test.com/users/28/comments/123
router.get("/users/:id/comments/:comment_id") { ... }

The handler will have to return a Serializable object that will define the response once the URL of a request is matched. When a Router intercepts a request, it automatically serializes the Serializable object returned by the handler and converts it to Data.

router.get("/users/:id") { request in
  return ["id": request.components["id"]!, "name": "Joan"]
}

Now everything is ready to test your mocked API; you can perform your request as you usually would do:

let session = URLSession.shared
let url = URL(string: "http://www.test.com/users/1")!
session.dataTask(with: url) { (data, _, _) in
    // handle response
}.resume()

Note: query parameters are not affecting the route match http://www.test.com/users/1?foo=bar would also be matched

In the previous example the handler was returning a simple Dictionary; while this works because Dictionary is already Serializable, you can also create your own entities that conform to Serializable:

struct User: Serializable {
    let firstName: String
    let lastName: String
    let id: String
}

router.get("/users/:id") { request in
  return User(firstName: "Joan", lastName: "Romano", id: request.components["id"]!)
}

When a request is matched, the RouteHandler receives a Request object that represents your request including components, query parameters, httpBody and httpHeaders. The Request object can be useful when building dynamic responses.

Third-Party Libraries

Third-Party libraries that use the Foundation networking APIs are also supported but you might need to set a proper URLSessionConfiguration.
For example, to setup Alamofire:

let configuration = URLSessionConfiguration.default
configuration.protocolClasses = [Server.self]
let sessionManager = SessionManager(configuration: configuration)

Leverage the Store - Dynamic mocking

Kakapo gets even more powerful when using your Routers together with the Store. You can create, insert, remove, update or find objects.

This lets you mock the APIs behaviors as if you were using a real backend. This is the dynamic side of Kakapo.

To create entities that can be used with the store, your types need to conform to the Storable protocol.

struct Article: Storable, Serializable {
    let id: String
    let text: String

    init(id: String, store: Store) {
        self.id = id
        self.text = randomString() // you might use some faker library like Fakery!
    }
}

An example usage could be to retrieve a specific Article:

let store = Store()
store.create(Article.self, number: 20)

router.get("/articles/:id") { request in
  let articleId = request.components["id"]!
  return store.find(Article.self, id: articleId)
}

Of course you can perform any logic which fits your needs:

router.post("/article/:id") { request in
    return store.insert { (id) -> Article in
        return Article(id: id, text: "text from the body")
    }
}

router.del("/article/:id") { request in
  let articleId = request.components["id"]!
  let article = store.find(Article.self, id: articleId)!
  try! store.delete(article)

  return ["status": "success"]
}

CustomSerializable

In Serializable we described how your classes can be serialized. The serialization, by default, will Mirror (using Swift's reflection) an entity by recursively serializing its properties.

Whenever a different behavior is needed, you can instead conform to CustomSerializable to provide your custom serialization.

For instance, Array uses CustomSerializable to return an Array containing its serialized elements. Dictionary, similarly, is serialized by creating a Dictionary with the same keys and serialized values.

For other examples of CustomSerializable and how to use it to create more complex serializations, take a look at the JSONAPISerializer implementation.

JSONAPI

Since Kakapo was built with JSONAPI support in mind, JSONAPISerializer is able to serialize your entity into JSON conforming to jsonapi.org.

Your entities, in order to be serialized conforming to JSONAPI, need to conform to JSONAPIEntity protocol.

Let's see an example:

struct Cat: JSONAPIEntity {
    let id: String
    let name: String
}

struct User: JSONAPIEntity {
    let id: String
    let name: String
    let cats: [Cat]
}

Note that JSONAPIEntity objects are already Serializable and you could just use them together with your Routers. However, to completely follow the JSONAPI structure in your responses, you should wrap them into a JSONAPISerializer struct:

router.get("/users/:id") { request in
  let cats = [Cat(id: "33", name: "Joan"), Cat(id: "44", name: "Hez")]
  let user = User(id: "11", name: "Alex", cats: cats)
  return JSONAPISerializer(user)
}

Expanding Null values with Property Policy

When serializing to JSON, you may want to represent a property value as null. For this, you can use the PropertyPolicy enum. It is similar to Optional, providing an additional .null case:

public enum PropertyPolicy<Wrapped>: CustomSerializable {
    case None
    case Null
    case Some(Wrapped)
}

It's only purpose is to be serialized in 3 different ways, to cover all possible behaviors of an Optional property. PropertyPolicy works exactly as Optional properties:

  • .none -> property not included in the serialization
  • .some(wrapped) -> serialize wrapped

The additional case ,.null, is serialized as null when converted to json.

PropertyPolicy<Int>.none.serialized() // nil
PropertyPolicy<Int>.null.serialized() // NSNull
PropertyPolicy<Int>.some(1).serialized() // 1

Key customization - Serialization Transformer

The keys of the JSON generated by the serialization are directly reflecting the property names of your entities. However, you might need different behaviors. For instance, many APIs use snake_case keys but almost everyone use camelCase properties in Swift.
To transform the keys you can use SerializationTransformer. Objects conforming to this protocol are able to transform the keys of a wrapped object at serialization time.

For a concrete implementation, check SnakecaseTransformer: a struct that implements SerializationTransformer to convert keys into snake case:

let user = User(userName: "Alex")
let serialized = SnakecaseTransformer(user).serialized()
print(serialized) // [ "user_name" : "Alex" ]

Customize responses with ResponseFieldsProvider

If your responses need to specify status code (which will be 200 by default) and/or header fields, you can take advantage of ResponseFieldsProvider to customize your responses.

Kakapo provides a default ResponseFieldsProvider implementation in the Response struct, which you can use to wrap your Serializable objects:

router.get("/users/:id"){ request in
    return Response(statusCode: 400, body: user, headerFields: ["access_token" : "094850348502"])
}

let url = URL(string: "http://www.test.com/users/2")!
session.dataTaskWithURL() { (data, response, _) in
    let allHeaders = response.allHeaderFields
    let statusCode = response.statusCode
    print(allHeaders["access_token"]) // 094850348502
    print(statusCode) // 400
}.resume()

Otherwise your Serializable object can directly implement the protocol: take a look at JSONAPIError to see another example.

Roadmap

Even though Kakapo is ready to use, it is not meant to be shipped to the App Store although you can also do it! In fact, you might see it in action in some Apple stores since it was used to mock some features of Runtastic's demo app; however, it's at its early stage and we would love to hear your thoughts. We encourage you to open an issue if you have any questions, feedbacks or you just want to propose new features.

  • Full JSON API support #67
  • Reverse and Recursive relationships #16
  • Custom Serializers for common json specifications

Examples

Newsfeed BuddyBuild

Make sure you check the demo app we created using Kakapo: a prototyped newsfeed app which lets the user create new posts and like/unlike them.
To quickly try it use: pod try Kakapo

Authors

@MP0w - @zzarcon - @joanromano

kakapo's People

Contributors

bryant1410 avatar hernangonzalez avatar joanromano avatar leviathan avatar mp0w avatar russellbstephens avatar tovkal avatar zzarcon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

kakapo's Issues

In memory database fetching

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

Pseudo Implementation

struct DB {
    let allObjects: [String: Any]

    fune find<T>(filter: (T) -> Bool) -> [T] {
        return allObjects[T.type].filter(filter)
    }
}

Usage

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

CI: CodeCov might be optimized

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'`

README.playground Errors

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() ?? []]
^~~~~~~ ~~~~~~~~~

JSON API Server Responsibilities

Server responsibilities (needs #56)

  • Servers MUST send all JSON API data in response documents with the header Content-Type: application/vnd.api+json without any media type parameters
  • Servers MUST respond with a 415 Unsupported Media Type status code if a request specifies the header Content-Type: application/vnd.api+json with any media type parameters.
  • Servers MUST respond with a 406 Not Acceptable status code if a request's Accept header contains the JSON API media type and all instances of that media type are modified with media type parameters.

http://jsonapi.org/format/

Init entities and relationships

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()) }
    }
}

Fix test that perform real HTTP requests

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)

Factory proposal

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...

Access request body from route handler

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
}

Route handling proposal

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") {

}

Use Set on Server instead of Array to retain Routers

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.

Suport query params in route handling

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()

Support JSON API part 2

Syntax

  • links
  • links of relationships
  • Support included
  • Errors
  • meta

Not in the scope: #57 Server responsibilities and behaviors #67

Allow the user to retrieve the HTTPHeaders of a request

 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.

Rename KakapoDB and KakapoServer

My thoughts on this:

  • We should rename them to Store and Server, Kakapo prefixes don't make sense with Swift modules as we know.
  • This shouldn't mean a BCH but I guess we can't avoid it. Still, I think having to release 1.0 just because of this is way too much.

Optimize db insertions

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(....)

  • Create a performance measure block to avoid regressions
  • Document in the code and link to the issue

HTTP Status code support

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

Entities Serialization

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

CI improvements

Improve our CI:

  • Explore fatslane possibilities
  • Build Matrix
  • Multiple platforms #44

Reverse and recursive Relantionships

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
}

Support Deletion and Overwrite in database

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.

Support serialization key transformer

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:

Detailed design

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
}

Unregister routes

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 ?

  • Implement #23
  • Test unregister routes
  • Test replacing routes
  • Test dealloc unregister all routes

Return Optional in serialize()

By returning Optional we will avoid special handling for PropertyPolicy and Optional (required once #50 is implemented) and we will avoid duplicating the handling for #27

Refactor URL matching and improve Router APIs

Using the APIs I noticed that:

  • I was confused about what baseURL I should pass (still think it should be any part of the url, not only the base e.g. http://subdomain.domain.com/api/v3/whatever while now you are constrained to subdomain.domain.com. we might skip the http(s) maybe)
  • Documentation can be improved for the baseURL (how to init the Router and what part of the URL to pass in the Routes handlers)
  • Implementation of the matcher is really hard to understand and debug

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

Request info refactor

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 ?

README and Documentation

  • Code Documentation
  • Kakapo description
  • Update README
  • README.playground (?)
  • podspec description
  • Discuss for devlucky.github.io

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.