1
1
import Foundation
2
+ import HTTPTypes
2
3
import Hummingbird
3
4
import NIOFoundationCompat
4
5
import NIOHTTP1
@@ -24,124 +25,84 @@ extension HBOpenAPITransport {
24
25
/// - queryItemNames: The names of query items to be extracted
25
26
/// from the request URL that matches the provided HTTP operation.
26
27
public func register(
27
- _ handler: @escaping @Sendable ( Request, ServerRequestMetadata) async throws -> Response ,
28
- method: OpenAPIRuntime . HTTPMethod ,
29
- path: [ OpenAPIRuntime . RouterPathComponent ] ,
30
- queryItemNames: Set < String >
28
+ _ handler: @escaping @Sendable ( HTTPRequest, HTTPBody? , ServerRequestMetadata) async throws -> ( HTTPResponse , HTTPBody ? ) ,
29
+ method: HTTPRequest . Method ,
30
+ path: String
31
31
) throws {
32
32
self . application. router. on (
33
33
Self . makeHummingbirdPath ( from: path) ,
34
- method: . init( method)
34
+ method: . init( rawValue: method. rawValue) ,
35
+ options: . streamBody
35
36
) { request in
36
- let openAPIRequest = try request. makeOpenAPIRequest ( )
37
+ let ( openAPIRequest, openAPIRequestBody ) = try request. makeOpenAPIRequest ( )
37
38
let openAPIRequestMetadata = request. makeOpenAPIRequestMetadata ( )
38
- let openAPIResponse : Response = try await handler ( openAPIRequest, openAPIRequestMetadata)
39
- return openAPIResponse . makeHBResponse ( )
39
+ let ( openAPIResponse, openAPIResponseBody ) = try await handler ( openAPIRequest, openAPIRequestBody , openAPIRequestMetadata)
40
+ return HBResponse ( openAPIResponse , body : openAPIResponseBody )
40
41
}
41
42
}
42
43
43
- /// Make hummingbird path string from RouterPathComponent array
44
- static func makeHummingbirdPath( from path: [ OpenAPIRuntime . RouterPathComponent ] ) -> String {
45
- path. map ( \. hbPathComponent) . joined ( separator: " / " )
46
- }
47
- }
48
-
49
- extension RouterPathComponent {
50
- /// Return path component as String
51
- var hbPathComponent : String {
52
- switch self {
53
- case . constant( let string) :
54
- return string
55
- case . parameter( let parameter) :
56
- return " ${ \( parameter) } "
57
- }
44
+ /// Make hummingbird path string from OpenAPI path
45
+ static func makeHummingbirdPath( from path: String ) -> String {
46
+ // frustratingly hummingbird supports `${parameter}` style path which is oh so close
47
+ // to the OpenAPI `{parameter}` format
48
+ return path. replacingOccurrences ( of: " { " , with: " ${ " )
58
49
}
59
50
}
60
51
61
52
extension HBRequest {
62
53
/// Construct ``OpenAPIRuntime.Request`` from Hummingbird ``HBRequest``
63
- func makeOpenAPIRequest( ) throws -> Request {
64
- guard let method = OpenAPIRuntime . HTTPMethod ( self . method) else {
54
+ func makeOpenAPIRequest( ) throws -> ( HTTPRequest , HTTPBody ? ) {
55
+ guard let method = HTTPRequest . Method ( rawValue : self . method. rawValue ) else {
65
56
// if we cannot create an OpenAPI http method then we can't create a
66
57
// a request and there is no handler for this method
67
58
throw HBHTTPError ( . notFound)
68
59
}
69
- let headers : [ HeaderField ] = self . headers. map { . init( name: $0. name, value: $0. value) }
70
- let body = self . body. buffer. map { Data ( buffer: $0, byteTransferStrategy: . noCopy) }
71
- return . init(
72
- path: self . uri. path,
73
- query: self . uri. query,
74
- method: method,
75
- headerFields: headers,
76
- body: body
60
+ var httpFields = HTTPFields ( )
61
+ for header in self . headers {
62
+ if let fieldName = HTTPField . Name ( header. name) {
63
+ httpFields [ fieldName] = header. value
64
+ }
65
+ }
66
+ let request = HTTPRequest (
67
+ method: method,
68
+ scheme: nil ,
69
+ authority: nil ,
70
+ path: self . uri. string,
71
+ headerFields: httpFields
77
72
)
73
+ let body : HTTPBody ?
74
+ switch self . body {
75
+ case . byteBuffer( let buffer) :
76
+ body = buffer. map { HTTPBody ( [ UInt8] ( buffer: $0) ) }
77
+ case . stream( let streamer) :
78
+ body = . init( AsyncStreamerToByteChunkSequence ( streamer: streamer) , length: . unknown, iterationBehavior: . single)
79
+ }
80
+ return ( request, body)
78
81
}
79
82
80
83
/// Construct ``OpenAPIRuntime.ServerRequestMetadata`` from Hummingbird ``HBRequest``
81
84
func makeOpenAPIRequestMetadata( ) -> ServerRequestMetadata {
82
- let keyAndValues = self . parameters. map { ( key: String ( $0. 0 ) , value: String ( $0. 1 ) ) }
83
- let openAPIParameters = [ String: String] ( keyAndValues) { first, _ in first }
84
- let openAPIQueryItems = self . uri. queryParameters. map {
85
- URLQueryItem ( name: String ( $0. key) , value: String ( $0. value) )
86
- }
87
- return . init(
88
- pathParameters: openAPIParameters,
89
- queryParameters: openAPIQueryItems
90
- )
91
- }
92
- }
93
-
94
- extension Response {
95
- /// Construct Hummingbird ``HBResponse`` from ``OpenAPIRuntime.Response``
96
- func makeHBResponse( ) -> HBResponse {
97
- let statusCode = HTTPResponseStatus ( statusCode: self . statusCode)
98
- let headers = HTTPHeaders ( self . headerFields. map { ( name: $0. name, value: $0. value) } )
99
- let body = ByteBuffer ( data: self . body)
85
+ let keyAndValues = self . parameters. map { ( key: String ( $0. 0 ) , value: $0. 1 ) }
86
+ let openAPIParameters = [ String: Substring] ( keyAndValues) { first, _ in first }
100
87
return . init(
101
- status: statusCode,
102
- headers: headers,
103
- body: . byteBuffer( body)
88
+ pathParameters: openAPIParameters
104
89
)
105
90
}
106
91
}
107
92
108
- extension OpenAPIRuntime . HTTPMethod {
109
- init ? ( _ method: NIOHTTP1 . HTTPMethod ) {
110
- switch method {
111
- case . GET: self = . get
112
- case . PUT: self = . put
113
- case . POST: self = . post
114
- case . DELETE: self = . delete
115
- case . OPTIONS: self = . options
116
- case . HEAD: self = . head
117
- case . PATCH: self = . patch
118
- case . TRACE: self = . trace
119
- default : return nil
120
- }
121
- }
122
- }
123
-
124
- extension NIOHTTP1 . HTTPMethod {
125
- init ( _ method: OpenAPIRuntime . HTTPMethod ) {
126
- switch method {
127
- case . get:
128
- self = . GET
129
- case . put:
130
- self = . PUT
131
- case . post:
132
- self = . POST
133
- case . delete:
134
- self = . DELETE
135
- case . options:
136
- self = . OPTIONS
137
- case . head:
138
- self = . HEAD
139
- case . patch:
140
- self = . PATCH
141
- case . trace:
142
- self = . TRACE
143
- default :
144
- self = . RAW( value: method. name)
93
+ extension HBResponse {
94
+ init ( _ response: HTTPResponse , body: HTTPBody ? ) {
95
+ let responseBody : HBResponseBody
96
+ if let body = body {
97
+ let bufferSequence = body. map { ByteBuffer ( bytes: $0) }
98
+ responseBody = . stream( AsyncSequenceResponseBodyStreamer ( bufferSequence) )
99
+ } else {
100
+ responseBody = . empty
145
101
}
102
+ self . init (
103
+ status: . init( statusCode: response. status. code, reasonPhrase: response. status. reasonPhrase) ,
104
+ headers: . init( response. headerFields. map { ( key: $0. name. canonicalName, value: $0. value) } ) ,
105
+ body: responseBody
106
+ )
146
107
}
147
108
}
0 commit comments