Skip to content

Commit

Permalink
Adds automatic calls to beforeEncode() and afterDecode() on Content
Browse files Browse the repository at this point in the history
  • Loading branch information
grosch committed Mar 26, 2020
1 parent cac009e commit 7505535
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 1 deletion.
17 changes: 17 additions & 0 deletions Sources/Vapor/Client/ClientRequest.swift
Expand Up @@ -58,6 +58,23 @@ public struct ClientRequest {
}
return try decoder.decode(D.self, from: body, headers: self.headers)
}

mutating func encode<C>(_ content: C, using encoder: ContentEncoder) throws where C : Content {
var content = content
try content.beforeEncode()
var body = ByteBufferAllocator().buffer(capacity: 0)
try encoder.encode(content, to: &body, headers: &self.headers)
self.body = body
}

func decode<C>(_ content: C.Type, using decoder: ContentDecoder) throws -> C where C : Content {
guard let body = self.body else {
throw Abort(.lengthRequired)
}
var decoded = try decoder.decode(C.self, from: body, headers: self.headers)
try decoded.afterDecode()
return decoded
}
}

public var content: ContentContainer {
Expand Down
17 changes: 17 additions & 0 deletions Sources/Vapor/Client/ClientResponse.swift
Expand Up @@ -31,6 +31,23 @@ extension ClientResponse {
}
return try decoder.decode(D.self, from: body, headers: self.headers)
}

mutating func encode<C>(_ content: C, using encoder: ContentEncoder) throws where C : Content {
var body = ByteBufferAllocator().buffer(capacity: 0)
var content = content
try content.beforeEncode()
try encoder.encode(content, to: &body, headers: &self.headers)
self.body = body
}

func decode<C>(_ content: C.Type, using decoder: ContentDecoder) throws -> C where C : Content {
guard let body = self.body else {
throw Abort(.lengthRequired)
}
var decoded = try decoder.decode(C.self, from: body, headers: self.headers)
try decoded.afterDecode()
return decoded
}
}

public var content: ContentContainer {
Expand Down
6 changes: 6 additions & 0 deletions Sources/Vapor/Content/Content.swift
Expand Up @@ -37,6 +37,9 @@ public protocol Content: Codable, RequestDecodable, ResponseEncodable {
/// }
///
static var defaultContentType: HTTPMediaType { get }

mutating func beforeEncode() throws
mutating func afterDecode() throws
}

/// MARK: Default Implementations
Expand Down Expand Up @@ -67,6 +70,9 @@ extension Content {
}
return request.eventLoop.makeSucceededFuture(response)
}

public mutating func beforeEncode() throws { }
public mutating func afterDecode() throws { }
}

// MARK: Default Conformances
Expand Down
27 changes: 26 additions & 1 deletion Sources/Vapor/Content/ContentContainer.swift
Expand Up @@ -15,6 +15,13 @@ extension ContentContainer {
return try self.decode(D.self, using: self.configuredDecoder())
}

public func decode<C>(_ decodable: C.Type) throws -> C where C: Content {
var content = try self.decode(C.self, using: self.configuredDecoder())
try content.afterDecode()

return content
}

// MARK: Encode


Expand All @@ -28,6 +35,8 @@ extension ContentContainer {
public mutating func encode<C>(_ encodable: C) throws
where C: Content
{
var encodable = encodable
try encodable.beforeEncode()
try self.encode(encodable, as: C.defaultContentType)
}

Expand All @@ -45,7 +54,23 @@ extension ContentContainer {
{
try self.encode(encodable, using: self.configuredEncoder(for: contentType))
}


/// Serializes a `Content` object to this message using specific `HTTPMessageEncoder`.
///
/// try req.content.encode(user, using: JSONEncoder())
///
/// - parameters:
/// - content: Instance of generic `Content` to serialize to this HTTP message.
/// - encoder: Specific `HTTPMessageEncoder` to use.
/// - throws: Errors during serialization.
public mutating func encode<C>(_ content: C, as contentType: HTTPMediaType) throws
where C: Content
{
var content = content
try content.beforeEncode()
try self.encode(content, using: self.configuredEncoder(for: contentType))
}

// MARK: Single Value

/// Fetches a single `Decodable` value at the supplied key-path from this HTTP request's query string.
Expand Down
18 changes: 18 additions & 0 deletions Sources/Vapor/Request/Request.swift
Expand Up @@ -78,6 +78,24 @@ public final class Request: CustomStringConvertible {
}
return try decoder.decode(D.self, from: body, headers: self.request.headers)
}

func encode<C>(_ content: C, using encoder: ContentEncoder) throws where C : Content {
var content = content
try content.beforeEncode()
var body = ByteBufferAllocator().buffer(capacity: 0)
try encoder.encode(content, to: &body, headers: &self.request.headers)
self.request.bodyStorage = .collected(body)
}

func decode<C>(_ content: C.Type, using decoder: ContentDecoder) throws -> C where C : Content {
guard let body = self.request.body.data else {
self.request.logger.error("Decoding streaming bodies not supported")
throw Abort(.unprocessableEntity)
}
var decoded = try decoder.decode(C.self, from: body, headers: self.request.headers)
try decoded.afterDecode()
return decoded
}
}

public var content: ContentContainer {
Expand Down
17 changes: 17 additions & 0 deletions Sources/Vapor/Response/Response.swift
Expand Up @@ -81,6 +81,23 @@ public final class Response: CustomStringConvertible {
}
return try decoder.decode(D.self, from: body, headers: self.response.headers)
}

func encode<C>(_ content: C, using encoder: ContentEncoder) throws where C : Content {
var content = content
try content.beforeEncode()
var body = ByteBufferAllocator().buffer(capacity: 0)
try encoder.encode(content, to: &body, headers: &self.response.headers)
self.response.body = .init(buffer: body)
}

func decode<C>(_ content: C.Type, using decoder: ContentDecoder) throws -> C where C : Content {
guard let body = self.response.body.buffer else {
throw Abort(.unprocessableEntity)
}
var decoded = try decoder.decode(C.self, from: body, headers: self.response.headers)
try decoded.afterDecode()
return decoded
}
}

public var content: ContentContainer {
Expand Down
6 changes: 6 additions & 0 deletions Sources/XCTVapor/XCTHTTPRequest.swift
Expand Up @@ -19,6 +19,12 @@ public struct XCTHTTPRequest {
func decode<D>(_ decodable: D.Type, using decoder: ContentDecoder) throws -> D where D : Decodable {
fatalError("Decoding from test request is not supported.")
}

mutating func encode<C>(_ content: C, using encoder: ContentEncoder) throws where C : Content {
var content = content
try content.beforeEncode()
try encoder.encode(content, to: &self.body, headers: &self.headers)
}
}

public var content: ContentContainer {
Expand Down
6 changes: 6 additions & 0 deletions Sources/XCTVapor/XCTHTTPResponse.swift
Expand Up @@ -20,6 +20,12 @@ extension XCTHTTPResponse {
func decode<D>(_ decodable: D.Type, using decoder: ContentDecoder) throws -> D where D : Decodable {
try decoder.decode(D.self, from: self.body, headers: self.headers)
}

func decode<C>(_ content: C.Type, using decoder: ContentDecoder) throws -> C where C : Content {
var decoded = try decoder.decode(C.self, from: self.body, headers: self.headers)
try decoded.afterDecode()
return decoded
}
}

public var content: ContentContainer {
Expand Down
48 changes: 48 additions & 0 deletions Tests/VaporTests/ContentTests.swift
@@ -0,0 +1,48 @@
import Vapor
import XCTVapor
import COperatingSystem
import AsyncHTTPClient

class ContentTests: XCTestCase {
func testBeforeEncodeContent() throws {
let content = SampleContent()
XCTAssertEqual(content.name, "old name")

let response = Response(status: .ok)
try response.content.encode(content)

let body = try XCTUnwrap(response.body.string)
XCTAssertEqual(body, #"{"name":"new name"}"#)
}

func testAfterContentEncode() throws {
let app = Application()
defer { app.shutdown() }

var body = ByteBufferAllocator().buffer(capacity: 0)
body.writeString(#"{"name": "before decode"}"#)

let request = Request(
application: app,
collectedBody: body,
on: EmbeddedEventLoop()
)

request.headers.contentType = .json

let content = try request.content.decode(SampleContent.self)
XCTAssertEqual(content.name, "new name after decode")
}
}

private struct SampleContent: Content {
var name = "old name"

mutating func beforeEncode() throws {
name = "new name"
}

mutating func afterDecode() throws {
name = "new name after decode"
}
}

0 comments on commit 7505535

Please sign in to comment.