Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic request decoder and response encoder #353

Merged
merged 2 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Benchmarks/Benchmarks/Router/RouterBenchmarks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import NIOCore
import NIOPosix

/// Implementation of a basic request context that supports everything the Hummingbird library needs
struct BasicBenchmarkContext: HBBaseRequestContext {
struct BasicBenchmarkContext: HBRequestContext {
var coreContext: HBCoreRequestContext

init(
Expand All @@ -40,7 +40,7 @@ struct BenchmarkBodyWriter: Sendable, HBResponseBodyWriter {

extension Benchmark {
@discardableResult
convenience init?<Context: HBBaseRequestContext>(
convenience init?<Context: HBRequestContext>(
name: String,
context: Context.Type = BasicBenchmarkContext.self,
configuration: Benchmark.Configuration = Benchmark.defaultConfiguration,
Expand Down
30 changes: 0 additions & 30 deletions Sources/Hummingbird/Middleware/SetCodableMiddleware.swift

This file was deleted.

2 changes: 1 addition & 1 deletion Sources/Hummingbird/Middleware/TracingMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public struct HBTracingMiddleware<Context: HBBaseRequestContext>: HBMiddlewarePr
attributes["http.user_agent"] = request.headers[.userAgent]
attributes["http.request_content_length"] = request.headers[.contentLength].map { Int($0) } ?? nil

if let remoteAddress = (context as? HBRemoteAddressRequestContext)?.remoteAddress {
if let remoteAddress = (context as? any HBRemoteAddressRequestContext)?.remoteAddress {
attributes["net.sock.peer.port"] = remoteAddress.port

switch remoteAddress.protocol {
Expand Down
32 changes: 15 additions & 17 deletions Sources/Hummingbird/Server/RequestContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,6 @@

/// Request context values required by Hummingbird itself.
public struct HBCoreRequestContext: Sendable {
/// Request decoder
@usableFromInline
var requestDecoder: HBRequestDecoder
/// Response encoder
@usableFromInline
var responseEncoder: HBResponseEncoder
/// ByteBuffer allocator used by request
@usableFromInline
let allocator: ByteBufferAllocator
Expand All @@ -54,13 +48,9 @@

@inlinable
public init(
requestDecoder: HBRequestDecoder = NullDecoder(),
responseEncoder: HBResponseEncoder = NullEncoder(),
allocator: ByteBufferAllocator,
logger: Logger
) {
self.requestDecoder = requestDecoder
self.responseEncoder = responseEncoder
self.allocator = allocator
self.logger = logger
self.endpointPath = .init()
Expand All @@ -71,21 +61,21 @@
/// Protocol that all request contexts should conform to. Holds data associated with
/// a request. Provides context for request processing
public protocol HBBaseRequestContext: Sendable {
associatedtype Decoder: HBRequestDecoder = NullDecoder
associatedtype Encoder: HBResponseEncoder = NullEncoder
Comment on lines +64 to +65
Copy link
Member

Choose a reason for hiding this comment

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

Does it make sense to make these primary associated types?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure that is necessary. When are you going to need to specify the encoder in a parameter type

Comment on lines +64 to +65
Copy link
Member

Choose a reason for hiding this comment

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

This will need fixing in HBLambda

Copy link
Member Author

Choose a reason for hiding this comment

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

yep, PR on its way


/// Core context
var coreContext: HBCoreRequestContext { get set }
/// Maximum upload size allowed for routes that don't stream the request payload. This
/// limits how much memory would be used for one request
var maxUploadSize: Int { get }
/// Request decoder
var requestDecoder: Decoder { get }
/// Response encoder
var responseEncoder: Encoder { get }
}

extension HBBaseRequestContext {
/// Request decoder
@inlinable
public var requestDecoder: HBRequestDecoder { coreContext.requestDecoder }
/// Response encoder
@inlinable
public var responseEncoder: HBResponseEncoder { coreContext.responseEncoder }
/// ByteBuffer allocator used by request
@inlinable
public var allocator: ByteBufferAllocator { coreContext.allocator }
/// Logger to use with Request
Expand All @@ -109,6 +99,14 @@
public var id: String { self.logger[metadataKey: "hb_id"]!.description }
}

extension HBBaseRequestContext where Decoder == NullDecoder {
public var requestDecoder: Decoder { NullDecoder() }

Check warning on line 103 in Sources/Hummingbird/Server/RequestContext.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Hummingbird/Server/RequestContext.swift#L103

Added line #L103 was not covered by tests
}

extension HBBaseRequestContext where Encoder == NullEncoder {
public var responseEncoder: Encoder { NullEncoder() }
}

/// Protocol for a request context that can be created from a NIO Channel
public protocol HBRequestContext: HBBaseRequestContext {
/// initialize an `HBRequestContext`
Expand Down
5 changes: 3 additions & 2 deletions Sources/PerformanceTest/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ struct PerformanceTestRequestContext: HBRequestContext {

init(allocator: ByteBufferAllocator, logger: Logger) {
self.coreContext = .init(
requestDecoder: JSONDecoder(),
responseEncoder: JSONEncoder(),
allocator: allocator,
logger: logger
)
}

var requestDecoder: JSONDecoder { .init() }
var responseEncoder: JSONEncoder { .init() }
}

// get environment
Expand Down
24 changes: 18 additions & 6 deletions Tests/HummingbirdFoundationTests/HummingBirdJSONTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import Hummingbird
import HummingbirdFoundation
import HummingbirdXCT
import Logging
import XCTest

class HummingbirdJSONTests: XCTestCase {
Expand All @@ -26,9 +27,22 @@ class HummingbirdJSONTests: XCTestCase {

struct Error: Swift.Error {}

struct JSONCodingRequestContext: HBRequestContext {
var coreContext: HBCoreRequestContext

init(allocator: ByteBufferAllocator, logger: Logger) {
self.coreContext = .init(
allocator: allocator,
logger: logger
)
}

var requestDecoder: JSONDecoder { .init() }
var responseEncoder: JSONEncoder { .init() }
}

func testDecode() async throws {
let router = HBRouter()
router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder()))
let router = HBRouter(context: JSONCodingRequestContext.self)
router.put("/user") { request, context -> HTTPResponse.Status in
guard let user = try? await request.decode(as: User.self, context: context) else { throw HBHTTPError(.badRequest) }
XCTAssertEqual(user.name, "John Smith")
Expand All @@ -46,8 +60,7 @@ class HummingbirdJSONTests: XCTestCase {
}

func testEncode() async throws {
let router = HBRouter()
router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder()))
let router = HBRouter(context: JSONCodingRequestContext.self)
router.get("/user") { _, _ -> User in
return User(name: "John Smith", email: "john.smith@email.com", age: 25)
}
Expand All @@ -63,8 +76,7 @@ class HummingbirdJSONTests: XCTestCase {
}

func testEncode2() async throws {
let router = HBRouter()
router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder()))
let router = HBRouter(context: JSONCodingRequestContext.self)
router.get("/json") { _, _ in
return ["message": "Hello, world!"]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import Hummingbird
import HummingbirdFoundation
import HummingbirdXCT
import Logging
import XCTest

class HummingBirdURLEncodedTests: XCTestCase {
Expand All @@ -24,11 +25,24 @@ class HummingBirdURLEncodedTests: XCTestCase {
let age: Int
}

struct URLEncodedCodingRequestContext: HBRequestContext {
var coreContext: HBCoreRequestContext

init(allocator: ByteBufferAllocator, logger: Logger) {
self.coreContext = .init(
allocator: allocator,
logger: logger
)
}

var requestDecoder: URLEncodedFormDecoder { .init() }
var responseEncoder: URLEncodedFormEncoder { .init() }
}

struct Error: Swift.Error {}

func testDecode() async throws {
let router = HBRouter()
router.middlewares.add(HBSetCodableMiddleware(decoder: URLEncodedFormDecoder(), encoder: URLEncodedFormEncoder()))
let router = HBRouter(context: URLEncodedCodingRequestContext.self)
router.put("/user") { request, context -> HTTPResponse.Status in
guard let user = try? await request.decode(as: User.self, context: context) else { throw HBHTTPError(.badRequest) }
XCTAssertEqual(user.name, "John Smith")
Expand All @@ -46,8 +60,7 @@ class HummingBirdURLEncodedTests: XCTestCase {
}

func testEncode() async throws {
let router = HBRouter()
router.middlewares.add(HBSetCodableMiddleware(decoder: URLEncodedFormDecoder(), encoder: URLEncodedFormEncoder()))
let router = HBRouter(context: URLEncodedCodingRequestContext.self)
router.get("/user") { _, _ -> User in
return User(name: "John Smith", email: "john.smith@email.com", age: 25)
}
Expand Down
5 changes: 3 additions & 2 deletions Tests/HummingbirdFoundationTests/UUIDTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@ final class UUIDTests: XCTestCase {

init(allocator: ByteBufferAllocator, logger: Logger) {
self.coreContext = .init(
requestDecoder: JSONDecoder(),
responseEncoder: JSONEncoder(),
allocator: allocator,
logger: logger
)
}

var requestDecoder: JSONDecoder { .init() }
var responseEncoder: JSONEncoder { .init() }
}

func testGetUUID() async throws {
Expand Down
18 changes: 15 additions & 3 deletions Tests/HummingbirdTests/ApplicationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ import ServiceLifecycle
import XCTest

final class ApplicationTests: XCTestCase {
struct JSONCodingRequestContext: HBRequestContext {
var coreContext: HBCoreRequestContext

init(allocator: ByteBufferAllocator, logger: Logger) {
self.coreContext = .init(
allocator: allocator,
logger: logger
)
}

var requestDecoder: JSONDecoder { .init() }
var responseEncoder: JSONEncoder { .init() }
}
func randomBuffer(size: Int) -> ByteBuffer {
var data = [UInt8](repeating: 0, count: size)
data = data.map { _ in UInt8.random(in: 0...255) }
Expand Down Expand Up @@ -325,7 +338,7 @@ final class ApplicationTests: XCTestCase {
}

func testTypedResponse() async throws {
let router = HBRouter()
let router = HBRouter(context: JSONCodingRequestContext.self)
router.delete("/hello") { _, _ in
return HBEditedResponse(
status: .preconditionRequired,
Expand All @@ -349,8 +362,7 @@ final class ApplicationTests: XCTestCase {
struct Result: HBResponseEncodable {
let value: String
}
let router = HBRouter()
router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder()))
let router = HBRouter(context: JSONCodingRequestContext.self)
router.patch("/hello") { _, _ in
return HBEditedResponse(
status: .multipleChoices,
Expand Down
33 changes: 21 additions & 12 deletions Tests/HummingbirdTests/HandlerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,24 @@
import Hummingbird
import HummingbirdFoundation
import HummingbirdXCT
import Logging
import XCTest

final class HandlerTests: XCTestCase {
struct JSONCodingRequestContext: HBRequestContext {
var coreContext: HBCoreRequestContext

init(allocator: ByteBufferAllocator, logger: Logger) {
self.coreContext = .init(
allocator: allocator,
logger: logger
)
}

var requestDecoder: JSONDecoder { .init() }
var responseEncoder: JSONEncoder { .init() }
}

struct DecodeTest<Value: Decodable>: HBRouteHandler, Decodable {
let value: Value

Expand All @@ -31,8 +46,7 @@ final class HandlerTests: XCTestCase {
}

func testDecodeKeyError() async throws {
let router = HBRouter()
router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder()))
let router = HBRouter(context: JSONCodingRequestContext.self)
router.post("/hello", use: DecodeTest<String>.self)
let app = HBApplication(responder: router.buildResponder())

Expand All @@ -52,8 +66,7 @@ final class HandlerTests: XCTestCase {
}

func testDecodeTypeError() async throws {
let router = HBRouter()
router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder()))
let router = HBRouter(context: JSONCodingRequestContext.self)
router.post("/hello", use: DecodeTest<Int>.self)
let app = HBApplication(responder: router.buildResponder())

Expand All @@ -73,8 +86,7 @@ final class HandlerTests: XCTestCase {
}

func testDecodeValueError() async throws {
let router = HBRouter()
router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder()))
let router = HBRouter(context: JSONCodingRequestContext.self)
router.post("/hello", use: DecodeTest<String>.self)
let app = HBApplication(responder: router.buildResponder())

Expand All @@ -99,8 +111,7 @@ final class HandlerTests: XCTestCase {
}

func testDecodeInputError() async throws {
let router = HBRouter()
router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder()))
let router = HBRouter(context: JSONCodingRequestContext.self)
router.post("/hello", use: DecodeTest<String>.self)
let app = HBApplication(responder: router.buildResponder())

Expand All @@ -120,8 +131,7 @@ final class HandlerTests: XCTestCase {
}

func testDecode() async throws {
let router = HBRouter()
router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder()))
let router = HBRouter(context: JSONCodingRequestContext.self)
router.post("/hello", use: DecodeTest<String>.self)
let app = HBApplication(responder: router.buildResponder())

Expand All @@ -134,8 +144,7 @@ final class HandlerTests: XCTestCase {
}

func testDecodeFail() async throws {
let router = HBRouter()
router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder()))
let router = HBRouter(context: JSONCodingRequestContext.self)
router.get("/hello", use: DecodeTest<String>.self)
let app = HBApplication(responder: router.buildResponder())

Expand Down
Loading