feat: Add Content-Type to custom encoder/decoder #1309
Conversation
a67008d to
82b046c
Compare
Sources/Kitura/Router.swift
Outdated
| let bestMediaType = MediaType(bestAccepts), | ||
| let bestEncoder = encoders[bestMediaType] | ||
| else { | ||
| let jsonEncoder = encoders[.json] ?? { return JSONEncoder() } |
There was a problem hiding this comment.
Rather than hard-coding a fallback MediaType of .json could we allow the user to specify this on the Router alongside the list of encoders?
Sources/Kitura/RouterRequest.swift
Outdated
| /// | ||
| /// - Parameter request: the server request | ||
| init(request: ServerRequest) { | ||
| convenience init(request: ServerRequest) { |
There was a problem hiding this comment.
This is internal, and it looks like (now that you've changed Router to call the 2-arg initializer) it is unused. I think we could just remove this init altogether.
Sources/Kitura/Router.swift
Outdated
| public func handle(request: ServerRequest, response: ServerResponse) { | ||
| let routeReq = RouterRequest(request: request) | ||
| var decoder: (() -> BodyDecoder)? | ||
| if let mediaType = MediaType(headers: request.headers) { |
There was a problem hiding this comment.
We should test this to see how much overhead it adds for requests such as a simple GET that does not contain a content type (or body).
Sources/Kitura/RouterRequest.swift
Outdated
| var data = Data() | ||
| _ = try serverRequest.read(into: &data) | ||
| return try JSONDecoder().decode(type, from: data) | ||
| let decoderInstance = decoder ?? JSONDecoder() |
There was a problem hiding this comment.
let decoder = self.decoder ?? JSONDecoder()
Sources/Kitura/RouterResponse.swift
Outdated
| private var lifecycle = Lifecycle() | ||
|
|
||
| private let encoder = JSONEncoder() | ||
| let encoder: BodyEncoder |
There was a problem hiding this comment.
Could this remain private?
Sources/Kitura/RouterResponse.swift
Outdated
| } | ||
| do { | ||
| headers.setType("json") | ||
| send(data: try JSONEncoder().encode(json)) |
There was a problem hiding this comment.
It would be confusing to the user if they customize their JSON encoder that we ignore it and use a default one here.
Sources/Kitura/RouterResponse.swift
Outdated
| } | ||
|
|
||
| let jsonStr = String(data: try encoder.encode(jsonp), encoding: .utf8)! | ||
| let jsonStr = String(data: try JSONEncoder().encode(jsonp), encoding: .utf8)! |
|
|
||
| return result | ||
| } | ||
|
|
There was a problem hiding this comment.
can you undo this whitespace-only change?
| /// The hashValue for the MediaTypes. Required for Hashable conformance. | ||
| public let hashValue: Int | ||
| } | ||
|
|
| XCTAssertEqual(inDate, codableDate) | ||
| respondWith(1, codableDate, nil) | ||
| } | ||
| customRouter.put("/customCoder") { (id: Int, inDate: CodableDate, respondWith: (CodableDate?, RequestError?) -> Void) in |
There was a problem hiding this comment.
After the refactor we probably only need to test GET and POST now
Sources/Kitura/Router.swift
Outdated
| * ``` | ||
| * - Parameter request: The RouterRequest to check | ||
| * - Returns: A tuple of the highest rated Encoder, or a JSONEncoder() if no encoders match the Accepts header and it's corresponding MediaType. | ||
| * - Returns: A tuple of the highest rated MediaType and it's corresponding Encoder, or a JSONEncoder() if no encoders match the Accepts header and it's corresponding . |
There was a problem hiding this comment.
it's -> its (x2)
Also, you lost the word 'MediaType' here somehow.
|
I regression tested these changes on an Ubuntu 16.04 box. The original changes regressed performance quite badly (11%) for a simple Hello World which did not require a decoder/encoder. Commit 29d83a2 delays the evaluation of the output encoder until we need one (which resolves this regression). We found a further regression when the request contains an Perf results: Hello World (no Content-Type / Accept headers)Codable GET (no Content-Type / Accept headers)Codable POST (no Accept header)Codable GET (with Accept header)Codable POST (with Accept header) |
|
We are left with a 6-8% regression for a Codable POST (regardless of whether there is an Accept header), and a smaller ~2% regression for a Codable GET. There may be other opportunities to reduce regression which we haven't explored yet. |
Description
This PR adds two fields to Router called encoders and decoders.
These contain a String to BodyEncoderGenerator/BodyDecoderGenerator dictionary.
We have also added a Read(as: Codable, decoder: BodyDecoder) function, which decodes the data from the routerRequest into a codable type using the given Decoder.
The Codable router uses the decoders dictionary to generate a decoder for the Content-type header and then use the new Read function to decode the request using that decoder.
The Codable router uses the Accepts header to select the best encoder from the Encoders dictionary and use that encoder to encode it's response.
Travis pass requirements
This requires a the Query Coders to be able to encode/decoder data which is in this (PR](Kitura/KituraContracts#28)
It also requires the addition of the BodyEncoder/BodyDecoder protocol which is currently being worked on this branch
Motivation and Context
This allows users to customize the JSON encoder and decoder in Codable routes and add their own Encoders/Decoders for encoding/decoding different Content-Type headers.
This is a fix for issue #1298
This follows on from the work in pull request 1221 but is generalized for any custom encoder/decoder
How Has This Been Tested?
This change affects all Codable and Type-Safe routes so tests have been added which use a custom JSONEncoder. The test suite has also been updated to allow custom encoders/decoders to be used.
Checklist: