Encoding and Decoding
Hummingbird can make use of Codable
to decode requests and encode responses. HBApplication
has two member variables decoder
and encoder
which define how requests/responses are decoded/encoded. The decoder
must conform to HBRequestDecoder
which requires a decode(_:from)
function that decodes a HBRequest
.
public protocol HBRequestDecoder {
func decode<T: Decodable>(_ type: T.Type, from request: HBRequest) throws -> T
}
The encoder
must conform to HBResponseEncoder
which requires a encode(_:from)
function that creates a HBResponse
from a Codable
value and the original request that generated it.
public protocol HBResponseEncoder {
func encode<T: Encodable>(_ value: T, from request: HBRequest) throws -> HBResponse
}
Both of these look very similar to the Encodable
and Decodable
protocol that come with the Codable
system except you have additional information from the HBRequest
class on how you might want to decode/encode your data.
Setting up HBApplication
The default implementations of decoder
and encoder
are Null
implementations that will assert if used. So you have to setup your decoder
and encoder
before you can use Codable
in Hummingbird. HummingbirdFoundation
includes two such implementations. JSONEncoder
and JSONDecoder
have been extended to conform to the relevant protocols so you can have JSON decoding/encoding by adding the following when creating your application
let app = HBApplication()
app.decoder = JSONDecoder()
app.encoder = JSONEncoder()
HummingbirdFoundation
also includes a decoder and encoder for url encoded form data. To use this you setup the application as follows
let app = HBApplication()
app.decoder = URLEncodedFormDecoder()
app.encoder = URLEncodedFormEncoder()
Decoding Requests
Once you have a decoder you can implement decoding in your routes using the HBRequest.decode
method in the following manner
struct User: Decodable {
let email: String
let firstName: String
let surname: String
}
app.router.post("user") { request -> EventLoopFuture<HTTPResponseStatus> in
// decode user from request
guard let user = try? request.decode(as: User.self) else {
return request.failure(.badRequest)
}
// create user and if ok return `.ok` status
return createUser(user, on: request.eventLoop)
.map { _ in .ok }
}
Like the standard Decoder.decode
functions HBRequest.decode
can throw an error if decoding fails. In this situation when I received a decode error I return a failed EventLoopFuture
. I use the function HBRequest.failure
to generate the failed EventLoopFuture
.
Encoding Responses
To have an object encoded in the response we have to conform it to HBResponseEncodable
. This then allows you to create a route handler that returns this object and it will automatically get encoded. If we extend the User
object from the above example we can do this
extension User: HBResponseEncodable {}
app.router.get("user") { request -> User in
let user = User(email: "js@email.com", name: "John Smith")
return user
}
Decoding/Encoding based on Request headers
Because the full request is supplied to the HBRequestDecoder
. You can make decoding decisions based on headers in the request. In the example below we are decoding using either the JSONDecoder
or URLEncodedFormDecoder
based on the “content-type” header.
struct MyRequestDecoder: HBRequestDecoder {
func decode<T>(_ type: T.Type, from request: HBRequest) throws -> T where T : Decodable {
switch request.headers["content-type"].first {
case "json/application", "application/json; charset=utf-8":
return try JSONDecoder().decode(type, from: request)
case "application/x-www-form-urlencoded":
return try URLEncodedFormDecoder().decode(type, from: request)
default:
throw HBHTTPError(.badRequest)
}
}
}
Using a similar manner you could also create a HBResponseEncoder
based on the “accepts” header in the request.