Skip to content

Type safe middleware#1274

Merged
ianpartridge merged 32 commits intomasterfrom
typeSafeMiddleware
May 29, 2018
Merged

Type safe middleware#1274
ianpartridge merged 32 commits intomasterfrom
typeSafeMiddleware

Conversation

@Andrew-Lees11
Copy link
Copy Markdown
Contributor

Adds the new TypeSafeMiddleware protocol and routes for up to 3 TypeSafeMiddleware in Codable routing.

Description

TypeSafeMiddleware is a protocol for middleware where they return an instance of self instead of altering the request and response. This allows them to be used in Codable routing.

The routes have been added in a new file for Codable routing allowing a user to have up to three type safe middleware on a route by declaring them in there route signature.

Motivation and Context

type safe middleware means application gains compile-time safety and developers benefit from enhanced separation of concerns between the middleware(s) and route handlers.

It also allows middleware such as Auth and sessions them to be used in Codable routes.

How Has This Been Tested?

Has been tested manually using Sessions as a typed middleware.
Automated tests still need to be added.
Appropriate Docs also need to be added describing the new routes.

@codecov-io
Copy link
Copy Markdown

codecov-io commented May 22, 2018

Codecov Report

Merging #1274 into master will decrease coverage by 6.34%.
The diff coverage is 49.45%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1274      +/-   ##
==========================================
- Coverage   88.97%   82.62%   -6.35%     
==========================================
  Files          39       40       +1     
  Lines        2412     2872     +460     
==========================================
+ Hits         2146     2373     +227     
- Misses        266      499     +233
Flag Coverage Δ
#Kitura 82.62% <49.45%> (-6.35%) ⬇️
Impacted Files Coverage Δ
Sources/Kitura/CodableRouter.swift 80.37% <100%> (ø) ⬆️
...rces/Kitura/CodableRouter+TypeSafeMiddleware.swift 49.34% <49.34%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5252028...ab2b3b4. Read the comment docs.

@shmuelk
Copy link
Copy Markdown
Collaborator

shmuelk commented May 22, 2018

Why is one limited to three middleware? Why isn't the list of middleware an array or a variadic parameter?

Copy link
Copy Markdown
Collaborator

@ianpartridge ianpartridge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few initial comments.

@@ -0,0 +1,1020 @@
/*
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this file be called CodableRouter+TypeSafeMiddleware.swift?

extension Router {

// Used by PUT and PATCH with identifier
public typealias MiddlewareIdentifierCodableClosure<T: TypeSafeMiddleware, Id: Identifier, I: Codable, O: Codable> = (T, Id, I, @escaping CodableResultClosure<O>) -> Void
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why these typealiases are inside extension Router {}? I would have thought they should be top-level.

next()
return
}
self.handleMiddleware(T1.self, T2.self, request: request, response: response) { TypeSafeMiddleware1, TypeSafeMiddleware2 in
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please name variables with lower-case names, e.g. typeSafeMiddleware1 not TypeSafeMiddleware1 to distinguish them from type names. This comment applies across the whole PR.

@ianpartridge
Copy link
Copy Markdown
Collaborator

@shmuelk I think the feature we really want is variadic generics but they aren't here yet unfortunately. Andrew and Dave did explore using an array, I'll let them comment on why they decided against that approach.

* limitations under the License.
*/

import KituraContracts
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can revert all the changes to this file now?

@@ -0,0 +1,39 @@
/*
* Copyright IBM Corporation 2016
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2018

/// attempt to process the request, respectively.
static func handle(request: RouterRequest, response: RouterResponse, completion: @escaping (Self?, RequestError?) -> Void) -> Void

/// Decribe the type-safe middleware
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Describe not Decribe. Also, please give an example of the kind of description that would be appropriate.

@@ -0,0 +1,890 @@
/*
* Copyright IBM Corporation 2017
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2018

) {
T.handle(request: request, response: response) { (typeSafeMiddleware: T?, error: RequestError?) in
guard let typeSafeMiddleware = typeSafeMiddleware else {
print("failed typeSafeMiddleware. Error: \(String(describing: error))")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

print!

scheme: basic
```
*/
static func describe() -> String
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My only question is whether we should take this out for now.

/// Defines the protocol which all Kitura type-safe middleware must implement.
///
/// TypeSafeMiddleware are classes or structs that process an incoming request
/// and on success create an instance of self, which is passed to the users route handler.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

users route handler -> user's route handler


extension Router {

// MARK: Codable Routing With Type-Safe Middleware
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least the With should be lowercase in the // MARK here as it looks strange with a capital 'W' underneath the previous MARK'd title of Parameter handling (which has a lowercase 'h').

```
- Parameter route: A String specifying the URL path that will invoke the handler.
- Parameter handler: A closure that receives a TypeSafeMiddleware instance and a QueryParams instance, and returns an array of Codable objects or a RequestError.
*/ public func get<T: TypeSafeMiddleware, Q: QueryParams, O: Codable>(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This */ is being displayed in the API doc. public func... needs to move onto the next line to fix this.

The handler contains the developer's logic, which determines the server's response.
### Usage Example: ###
In this example, MySession is a struct that conforms to the TypeSafeMiddleware protocol.
session is an instance of MySession, that contains [Int: User] dictionary.
Copy link
Copy Markdown
Contributor

@helenmasters helenmasters May 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to add the word 'an' before [Int: User] dictionary. Same on line 978.

id: Int, respondWith: (User?, RequestError?) -> Void) in
```
- Parameter route: A String specifying the URL path that will invoke the handler.
- Parameter handler: A closure that receives a TypeSafeMiddleware instance a Codable object and an identifier, and returns a Codable object or a RequestError.
Copy link
Copy Markdown
Contributor

@helenmasters helenmasters May 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a comma after the word instance. The same is true in the usage example below.... I haven't checked others.

The handler contains the developer's logic, which determines the server's response.
### Usage Example: ###
In this example, MySession is a struct that conforms to the TypeSafeMiddleware protocol.
session is an instance of MySession, that contains optional Users.
Copy link
Copy Markdown
Contributor

@helenmasters helenmasters May 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anywhere where we are starting a sentence with a variable name which is lowercase it looks poor in the jazzy API doc. Try the following instead:

In this example, MySession is a struct that conforms to the TypeSafeMiddleware protocol and specifies an optional User, where User conforms to Codable.

@Andrew-Lees11
Copy link
Copy Markdown
Contributor Author

Tests for all routes and documentation has now been added.
Two and Three middleware are marked as no doc so they don't show up in jazzy for clarity.

Copy link
Copy Markdown
Collaborator

@ianpartridge ianpartridge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome work guys.

@ianpartridge ianpartridge merged commit 179356f into master May 29, 2018
@ianpartridge ianpartridge deleted the typeSafeMiddleware branch May 29, 2018 12:45
@djones6
Copy link
Copy Markdown
Collaborator

djones6 commented May 29, 2018

@shmuelk To answer your earlier question on the 'up to 3' TypeSafeMiddleware parameters: Ian is correct, variadic generics is what we'd need to implement this in a nice way.

The reason we can't use an array is that the array would lose the type information of the specific instance types the user is expecting: the router would only know that the user's handler was expecting an array of [TypeSafeMiddleware]. We'd have to pass that type information separately.

Here is an example of how we could do it, but it's pretty hairy (and not very type-safe):

func get(_ route: String, middlewareTypes array: [MyProto.Type], handler: ([MyProto]) -> Void) {
    var result: [MyProto] = []
    for elem in array {
        var constructedType = elem.init()
        result.append(constructedType)
    }
    handler(result)
}

get("/foo", middlewareTypes: [ImplA.self, ImplB.self]) {
    (impls: [MyProto]) in
    guard let implA = impls[0] as? ImplA, let implB = impls[1] as? ImplB else {
        fatalError("Unexpected types passed")
    }
}

The biggest problem with this approach is requiring the user to restore the lost type information as a series of attempted downcasts, exactly matching the types in the array they passed to the router.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants