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

HBRouteHandler cleanup #332

Merged
merged 2 commits into from
Jan 7, 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
41 changes: 0 additions & 41 deletions Sources/Hummingbird/Codable/RequestDecodable.swift

This file was deleted.

44 changes: 22 additions & 22 deletions Sources/Hummingbird/Router/RouteHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,88 +28,88 @@
/// let update: Request
/// let id: String
///
/// init(from request: HBRequest) throws {
/// self.update = try request.decode(as: Request.self)
/// init(from request: HBRequest, context: some HBBaseRequestContext) throws {
/// self.update = try await request.decode(as: Request.self, context: context)
/// self.id = try request.parameters.require("id")
/// }
/// func handle(request: HBRequest) -> EventLoopFuture<HTTPResponseStatus> {
/// func handle(context: some HBBaseRequestContext) async throws -> HTTPResponse.Status {
/// let reminder = Reminder(id: id, update: update)
/// return reminder.update(on: request.db)
/// return reminder.update(on: db)
/// .map { _ in .ok }
/// }
/// }
/// ```
public protocol HBRouteHandler {
associatedtype _Output
associatedtype Output
init(from: HBRequest, context: some HBBaseRequestContext) async throws
func handle(request: HBRequest, context: some HBBaseRequestContext) async throws -> _Output
func handle(context: some HBBaseRequestContext) async throws -> Output
}

extension HBRouterMethods {
/// Add path for `HBRouteHandler` that returns a value conforming to `HBResponseGenerator`
@discardableResult public func on<Handler: HBRouteHandler, _Output: HBResponseGenerator>(
@discardableResult public func on<Handler: HBRouteHandler, Output: HBResponseGenerator>(
_ path: String,
method: HTTPRequest.Method,
options: HBRouterMethodOptions = [],
use handlerType: Handler.Type
) -> Self where Handler._Output == _Output {
return self.on(path, method: method, options: options) { request, context -> _Output in
) -> Self where Handler.Output == Output {
return self.on(path, method: method, options: options) { request, context -> Output in
let handler = try await Handler(from: request, context: context)
return try await handler.handle(request: request, context: context)
return try await handler.handle(context: context)
}
}

/// GET path for closure returning type conforming to HBResponseGenerator
@discardableResult public func get<Handler: HBRouteHandler, _Output: HBResponseGenerator>(
@discardableResult public func get<Handler: HBRouteHandler, Output: HBResponseGenerator>(
_ path: String = "",
options: HBRouterMethodOptions = [],
use handler: Handler.Type
) -> Self where Handler._Output == _Output {
) -> Self where Handler.Output == Output {
return self.on(path, method: .get, options: options, use: handler)
}

/// PUT path for closure returning type conforming to HBResponseGenerator
@discardableResult public func put<Handler: HBRouteHandler, _Output: HBResponseGenerator>(
@discardableResult public func put<Handler: HBRouteHandler, Output: HBResponseGenerator>(
_ path: String = "",
options: HBRouterMethodOptions = [],
use handler: Handler.Type
) -> Self where Handler._Output == _Output {
) -> Self where Handler.Output == Output {
return self.on(path, method: .put, options: options, use: handler)
}

/// POST path for closure returning type conforming to HBResponseGenerator
@discardableResult public func post<Handler: HBRouteHandler, _Output: HBResponseGenerator>(
@discardableResult public func post<Handler: HBRouteHandler, Output: HBResponseGenerator>(
_ path: String = "",
options: HBRouterMethodOptions = [],
use handler: Handler.Type
) -> Self where Handler._Output == _Output {
) -> Self where Handler.Output == Output {
return self.on(path, method: .post, options: options, use: handler)
}

/// HEAD path for closure returning type conforming to HBResponseGenerator
@discardableResult public func head<Handler: HBRouteHandler, _Output: HBResponseGenerator>(
@discardableResult public func head<Handler: HBRouteHandler, Output: HBResponseGenerator>(
_ path: String = "",
options: HBRouterMethodOptions = [],
use handler: Handler.Type
) -> Self where Handler._Output == _Output {
) -> Self where Handler.Output == Output {

Check warning on line 94 in Sources/Hummingbird/Router/RouteHandler.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Hummingbird/Router/RouteHandler.swift#L94

Added line #L94 was not covered by tests
return self.on(path, method: .head, options: options, use: handler)
}

/// DELETE path for closure returning type conforming to HBResponseGenerator
@discardableResult public func delete<Handler: HBRouteHandler, _Output: HBResponseGenerator>(
@discardableResult public func delete<Handler: HBRouteHandler, Output: HBResponseGenerator>(
_ path: String = "",
options: HBRouterMethodOptions = [],
use handler: Handler.Type
) -> Self where Handler._Output == _Output {
) -> Self where Handler.Output == Output {

Check warning on line 103 in Sources/Hummingbird/Router/RouteHandler.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Hummingbird/Router/RouteHandler.swift#L103

Added line #L103 was not covered by tests
return self.on(path, method: .delete, options: options, use: handler)
}

/// PATCH path for closure returning type conforming to HBResponseGenerator
@discardableResult public func patch<Handler: HBRouteHandler, _Output: HBResponseGenerator>(
@discardableResult public func patch<Handler: HBRouteHandler, Output: HBResponseGenerator>(
_ path: String = "",
options: HBRouterMethodOptions = [],
use handler: Handler.Type
) -> Self where Handler._Output == _Output {
) -> Self where Handler.Output == Output {

Check warning on line 112 in Sources/Hummingbird/Router/RouteHandler.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Hummingbird/Router/RouteHandler.swift#L112

Added line #L112 was not covered by tests
return self.on(path, method: .patch, options: options, use: handler)
}
}
2 changes: 1 addition & 1 deletion Sources/Hummingbird/Server/Request.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import HummingbirdCore
extension HBRequest {
/// Decode request using decoder stored at `HBApplication.decoder`.
/// - Parameter type: Type you want to decode to
public func decode<Type: Decodable>(as type: Type.Type, using context: some HBBaseRequestContext) async throws -> Type {
public func decode<Type: Decodable>(as type: Type.Type, context: some HBBaseRequestContext) async throws -> Type {
do {
return try await context.requestDecoder.decode(type, from: self, context: context)
} catch DecodingError.dataCorrupted(_) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class HummingbirdJSONTests: XCTestCase {
let router = HBRouter()
router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder()))
router.put("/user") { request, context -> HTTPResponse.Status in
guard let user = try? await request.decode(as: User.self, using: context) else { throw HBHTTPError(.badRequest) }
guard let user = try? await request.decode(as: User.self, context: context) else { throw HBHTTPError(.badRequest) }
XCTAssertEqual(user.name, "John Smith")
XCTAssertEqual(user.email, "john.smith@email.com")
XCTAssertEqual(user.age, 25)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class HummingBirdURLEncodedTests: XCTestCase {
let router = HBRouter()
router.middlewares.add(HBSetCodableMiddleware(decoder: URLEncodedFormDecoder(), encoder: URLEncodedFormEncoder()))
router.put("/user") { request, context -> HTTPResponse.Status in
guard let user = try? await request.decode(as: User.self, using: context) else { throw HBHTTPError(.badRequest) }
guard let user = try? await request.decode(as: User.self, context: context) else { throw HBHTTPError(.badRequest) }
XCTAssertEqual(user.name, "John Smith")
XCTAssertEqual(user.email, "john.smith@email.com")
XCTAssertEqual(user.age, 25)
Expand Down
100 changes: 23 additions & 77 deletions Tests/HummingbirdTests/HandlerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,22 @@ import HummingbirdXCT
import XCTest

final class HandlerTests: XCTestCase {
func testDecodeKeyError() async throws {
struct DecodeTest: HBRequestDecodable {
let name: String
struct DecodeTest<Value: Decodable>: HBRouteHandler, Decodable {
let value: Value

func handle(request: HBRequest, context: some HBBaseRequestContext) -> String {
return "Hello \(self.name)"
}
init(from request: HBRequest, context: some HBBaseRequestContext) async throws {
self = try await request.decode(as: Self.self, context: context)
}

func handle(context: some HBBaseRequestContext) -> String {
return "\(Value.self): \(self.value)"
}
}

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

try await app.test(.router) { client in
Expand All @@ -42,24 +46,16 @@ final class HandlerTests: XCTestCase {
) { response in
XCTAssertEqual(response.status, .badRequest)
let body = try XCTUnwrap(response.body)
let expectation = "Coding key `name` not found."
let expectation = "Coding key `value` not found."
XCTAssertEqual(String(buffer: body), expectation)
}
}
}

func testDecodeTypeError() async throws {
struct DecodeTest: HBRequestDecodable {
let value: Int

func handle(request: HBRequest, context: some HBBaseRequestContext) -> String {
return "Value: \(self.value)"
}
}

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

try await app.test(.router) { client in
Expand All @@ -79,21 +75,13 @@ final class HandlerTests: XCTestCase {
}

func testDecodeValueError() async throws {
struct DecodeTest: HBRequestDecodable {
let name: String

func handle(request: HBRequest, context: some HBBaseRequestContext) -> String {
return "Hello \(self.name)"
}
}

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

try await app.test(.router) { client in
let body = ByteBufferAllocator().buffer(string: #"{"name": null}"#)
let body = ByteBufferAllocator().buffer(string: #"{"value": null}"#)

try await client.XCTExecute(
uri: "/hello",
Expand All @@ -104,27 +92,19 @@ final class HandlerTests: XCTestCase {
let body = try XCTUnwrap(response.body)
#if os(Linux)
// NOTE: a type mismatch error occures under Linux for null values
let expectation = "Type mismatch for `name` key, expected `String` type."
let expectation = "Type mismatch for `value` key, expected `String` type."
#else
let expectation = "Value not found for `name` key."
let expectation = "Value not found for `value` key."
#endif
XCTAssertEqual(String(buffer: body), expectation)
}
}
}

func testDecodeInputError() async throws {
struct DecodeTest: HBRequestDecodable {
let name: String

func handle(request: HBRequest, context: some HBBaseRequestContext) -> String {
return "Hello \(self.name)"
}
}

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

try await app.test(.router) { client in
Expand All @@ -144,58 +124,24 @@ final class HandlerTests: XCTestCase {
}

func testDecode() async throws {
struct DecodeTest: HBRequestDecodable {
let name: String
func handle(request: HBRequest, context: some HBBaseRequestContext) -> String {
return "Hello \(self.name)"
}
}
let router = HBRouter()
router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder()))
router.post("/hello", use: DecodeTest.self)
let app = HBApplication(responder: router.buildResponder())

try await app.test(.router) { client in

try await client.XCTExecute(uri: "/hello", method: .post, body: ByteBufferAllocator().buffer(string: #"{"name": "Adam"}"#)) { response in
let body = try XCTUnwrap(response.body)
XCTAssertEqual(String(buffer: body), "Hello Adam")
}
}
}

func testDecodeFutureResponse() async throws {
struct DecodeTest: HBRequestDecodable {
let name: String
func handle(request: HBRequest, context: some HBBaseRequestContext) -> String {
"Hello \(self.name)"
}
}
let router = HBRouter()
router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder()))
router.put("/hello", use: DecodeTest.self)
router.post("/hello", use: DecodeTest<String>.self)
let app = HBApplication(responder: router.buildResponder())

try await app.test(.router) { client in

try await client.XCTExecute(uri: "/hello", method: .put, body: ByteBufferAllocator().buffer(string: #"{"name": "Adam"}"#)) { response in
try await client.XCTExecute(uri: "/hello", method: .post, body: ByteBufferAllocator().buffer(string: #"{"value": "Adam"}"#)) { response in
let body = try XCTUnwrap(response.body)
XCTAssertEqual(String(buffer: body), "Hello Adam")
XCTAssertEqual(String(buffer: body), "String: Adam")
}
}
}

func testDecodeFail() async throws {
struct DecodeTest: HBRequestDecodable {
let name: String

func handle(request: HBRequest, context: some HBBaseRequestContext) -> HTTPResponse.Status {
return .ok
}
}
let router = HBRouter()
router.middlewares.add(HBSetCodableMiddleware(decoder: JSONDecoder(), encoder: JSONEncoder()))
router.get("/hello", use: DecodeTest.self)
router.get("/hello", use: DecodeTest<String>.self)
let app = HBApplication(responder: router.buildResponder())

try await app.test(.router) { client in
Expand All @@ -212,7 +158,7 @@ final class HandlerTests: XCTestCase {
self.parameter = try context.parameters.require("test", as: Int.self)
}

func handle(request: HBRequest, context: some HBBaseRequestContext) -> String {
func handle(context: some HBBaseRequestContext) -> String {
return "\(self.parameter)"
}
}
Expand Down
Loading