Skip to content

Commit

Permalink
Use Swift HTTP types package for status and method (#47)
Browse files Browse the repository at this point in the history
* Use Swift HTTP types package for status and method

* Fix formatting
  • Loading branch information
jsonfry authored May 23, 2024
1 parent 5b4633d commit f015dbf
Show file tree
Hide file tree
Showing 14 changed files with 57 additions and 188 deletions.
3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-http-types.git", from: "1.0.0"),
],
targets: [
.target(name: "AWSLambdaEvents", dependencies: []),
.target(name: "AWSLambdaEvents", dependencies: [.product(name: "HTTPTypes", package: "swift-http-types")]),
.testTarget(name: "AWSLambdaEventsTests", dependencies: ["AWSLambdaEvents"]),
]
)
8 changes: 5 additions & 3 deletions Sources/AWSLambdaEvents/ALB.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
//
//===----------------------------------------------------------------------===//

import HTTPTypes

import class Foundation.JSONEncoder

// https://github.com/aws/aws-lambda-go/blob/master/events/alb.go
Expand All @@ -22,7 +24,7 @@ public struct ALBTargetGroupRequest: Codable {
public let elb: ELBContext
}

public let httpMethod: HTTPMethod
public let httpMethod: HTTPRequest.Method
public let path: String
public let queryStringParameters: [String: String]

Expand Down Expand Up @@ -50,15 +52,15 @@ public struct ALBTargetGroupRequest: Codable {
}

public struct ALBTargetGroupResponse: Codable {
public var statusCode: HTTPResponseStatus
public var statusCode: HTTPResponse.Status
public var statusDescription: String?
public var headers: HTTPHeaders?
public var multiValueHeaders: HTTPMultiValueHeaders?
public var body: String
public var isBase64Encoded: Bool

public init(
statusCode: HTTPResponseStatus,
statusCode: HTTPResponse.Status,
statusDescription: String? = nil,
headers: HTTPHeaders? = nil,
multiValueHeaders: HTTPMultiValueHeaders? = nil,
Expand Down
8 changes: 5 additions & 3 deletions Sources/AWSLambdaEvents/APIGateway+V2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
//
//===----------------------------------------------------------------------===//

import HTTPTypes

/// `APIGatewayV2Request` contains data coming from the new HTTP API Gateway.
public struct APIGatewayV2Request: Codable {
/// `Context` contains information to identify the AWS account and resources invoking the Lambda function.
public struct Context: Codable {
public struct HTTP: Codable {
public let method: HTTPMethod
public let method: HTTPRequest.Method
public let path: String
public let `protocol`: String
public let sourceIp: String
Expand Down Expand Up @@ -125,14 +127,14 @@ public struct APIGatewayV2Request: Codable {
}

public struct APIGatewayV2Response: Codable {
public var statusCode: HTTPResponseStatus
public var statusCode: HTTPResponse.Status
public var headers: HTTPHeaders?
public var body: String?
public var isBase64Encoded: Bool?
public var cookies: [String]?

public init(
statusCode: HTTPResponseStatus,
statusCode: HTTPResponse.Status,
headers: HTTPHeaders? = nil,
body: String? = nil,
isBase64Encoded: Bool? = nil,
Expand Down
8 changes: 5 additions & 3 deletions Sources/AWSLambdaEvents/APIGateway.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
//
//===----------------------------------------------------------------------===//

import HTTPTypes

import class Foundation.JSONEncoder

// https://docs.aws.amazon.com/lambda/latest/dg/services-apigateway.html
Expand Down Expand Up @@ -56,7 +58,7 @@ public struct APIGatewayRequest: Codable {

public let resource: String
public let path: String
public let httpMethod: HTTPMethod
public let httpMethod: HTTPRequest.Method

public let queryStringParameters: [String: String]?
public let multiValueQueryStringParameters: [String: [String]]?
Expand All @@ -73,14 +75,14 @@ public struct APIGatewayRequest: Codable {
// MARK: - Response -

public struct APIGatewayResponse: Codable {
public var statusCode: HTTPResponseStatus
public var statusCode: HTTPResponse.Status
public var headers: HTTPHeaders?
public var multiValueHeaders: HTTPMultiValueHeaders?
public var body: String?
public var isBase64Encoded: Bool?

public init(
statusCode: HTTPResponseStatus,
statusCode: HTTPResponse.Status,
headers: HTTPHeaders? = nil,
multiValueHeaders: HTTPMultiValueHeaders? = nil,
body: String? = nil,
Expand Down
4 changes: 3 additions & 1 deletion Sources/AWSLambdaEvents/APIGatewayLambdaAuthorizers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
//
//===----------------------------------------------------------------------===//

import HTTPTypes

/// `LambdaAuthorizerContext` contains authorizer informations passed to a Lambda function authorizer
public typealias LambdaAuthorizerContext = [String: String]

Expand All @@ -29,7 +31,7 @@ public struct APIGatewayLambdaAuthorizerRequest: Codable {
/// `Context` contains information to identify the AWS account and resources invoking the Lambda function.
public struct Context: Codable {
public struct HTTP: Codable {
public let method: HTTPMethod
public let method: HTTPRequest.Method
public let path: String
public let `protocol`: String
public let sourceIp: String
Expand Down
2 changes: 2 additions & 0 deletions Sources/AWSLambdaEvents/AppSync.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
//
//===----------------------------------------------------------------------===//

import HTTPTypes

// https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference.html
public struct AppSyncEvent: Decodable {
public let arguments: [String: ArgumentValue]
Expand Down
7 changes: 4 additions & 3 deletions Sources/AWSLambdaEvents/FunctionURL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import HTTPTypes

import class Foundation.JSONEncoder

Expand All @@ -37,7 +38,7 @@ public struct FunctionURLRequest: Codable {
}

public struct HTTP: Codable {
public let method: HTTPMethod
public let method: HTTPRequest.Method
public let path: String
public let `protocol`: String
public let sourceIp: String
Expand Down Expand Up @@ -81,14 +82,14 @@ public struct FunctionURLRequest: Codable {
// MARK: - Response -

public struct FunctionURLResponse: Codable {
public var statusCode: HTTPResponseStatus
public var statusCode: HTTPResponse.Status
public var headers: HTTPHeaders?
public var body: String?
public let cookies: [String]?
public var isBase64Encoded: Bool?

public init(
statusCode: HTTPResponseStatus,
statusCode: HTTPResponse.Status,
headers: HTTPHeaders? = nil,
body: String? = nil,
cookies: [String]? = nil,
Expand Down
4 changes: 3 additions & 1 deletion Sources/AWSLambdaEvents/LambdaGatewayProxyEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
//
//===----------------------------------------------------------------------===//

import HTTPTypes

/// LambdaGatewayProxyEvent contains data coming from the new HTTP API Gateway Proxy
public struct LambdaGatewayProxyEvent: Decodable {
public struct RequestContext: Decodable {
Expand All @@ -28,7 +30,7 @@ public struct LambdaGatewayProxyEvent: Decodable {
public let stage: String
public let requestID: String

public let httpMethod: HTTPMethod
public let httpMethod: HTTPRequest.Method
public let authorizer: Authorizer?

public let resourcePath: String?
Expand Down
181 changes: 18 additions & 163 deletions Sources/AWSLambdaEvents/Utils/HTTP.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

// MARK: HTTPHeaders

import HTTPTypes

public typealias HTTPHeaders = [String: String]
public typealias HTTPMultiValueHeaders = [String: [String]]

Expand Down Expand Up @@ -72,178 +74,31 @@ extension String.UTF8View {
}
}

// MARK: HTTPMethod

public struct HTTPMethod: RawRepresentable, Equatable {
public var rawValue: String

public init?(rawValue: String) {
guard rawValue.isValidHTTPToken else {
return nil
}
self.rawValue = rawValue
}

public static var GET: HTTPMethod { HTTPMethod(rawValue: "GET")! }
public static var POST: HTTPMethod { HTTPMethod(rawValue: "POST")! }
public static var PUT: HTTPMethod { HTTPMethod(rawValue: "PUT")! }
public static var PATCH: HTTPMethod { HTTPMethod(rawValue: "PATCH")! }
public static var DELETE: HTTPMethod { HTTPMethod(rawValue: "DELETE")! }
public static var OPTIONS: HTTPMethod { HTTPMethod(rawValue: "OPTIONS")! }
public static var HEAD: HTTPMethod { HTTPMethod(rawValue: "HEAD")! }

public static func RAW(value: String) -> HTTPMethod? { HTTPMethod(rawValue: value) }
}

extension HTTPMethod: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let rawMethod = try container.decode(String.self)

guard let method = HTTPMethod(rawValue: rawMethod) else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: #"Method "\#(rawMethod)" does not conform to allowed http method syntax defined in RFC 7230 Section 3.2.6"#
)
}

self = method
}

public func encode(to encoder: Encoder) throws {
extension HTTPResponse.Status: Codable {
public func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.rawValue)
}
}

// MARK: HTTPResponseStatus

public struct HTTPResponseStatus {
public let code: UInt
public let reasonPhrase: String?

public init(code: UInt, reasonPhrase: String? = nil) {
self.code = code
self.reasonPhrase = reasonPhrase
try container.encode(self.code)
}

public static var `continue`: HTTPResponseStatus { HTTPResponseStatus(code: 100) }
public static var switchingProtocols: HTTPResponseStatus { HTTPResponseStatus(code: 101) }
public static var processing: HTTPResponseStatus { HTTPResponseStatus(code: 102) }
public static var earlyHints: HTTPResponseStatus { HTTPResponseStatus(code: 103) }

public static var ok: HTTPResponseStatus { HTTPResponseStatus(code: 200) }
public static var created: HTTPResponseStatus { HTTPResponseStatus(code: 201) }
public static var accepted: HTTPResponseStatus { HTTPResponseStatus(code: 202) }
public static var nonAuthoritativeInformation: HTTPResponseStatus { HTTPResponseStatus(code: 203) }
public static var noContent: HTTPResponseStatus { HTTPResponseStatus(code: 204) }
public static var resetContent: HTTPResponseStatus { HTTPResponseStatus(code: 205) }
public static var partialContent: HTTPResponseStatus { HTTPResponseStatus(code: 206) }
public static var multiStatus: HTTPResponseStatus { HTTPResponseStatus(code: 207) }
public static var alreadyReported: HTTPResponseStatus { HTTPResponseStatus(code: 208) }
public static var imUsed: HTTPResponseStatus { HTTPResponseStatus(code: 226) }

public static var multipleChoices: HTTPResponseStatus { HTTPResponseStatus(code: 300) }
public static var movedPermanently: HTTPResponseStatus { HTTPResponseStatus(code: 301) }
public static var found: HTTPResponseStatus { HTTPResponseStatus(code: 302) }
public static var seeOther: HTTPResponseStatus { HTTPResponseStatus(code: 303) }
public static var notModified: HTTPResponseStatus { HTTPResponseStatus(code: 304) }
public static var useProxy: HTTPResponseStatus { HTTPResponseStatus(code: 305) }
public static var temporaryRedirect: HTTPResponseStatus { HTTPResponseStatus(code: 307) }
public static var permanentRedirect: HTTPResponseStatus { HTTPResponseStatus(code: 308) }

public static var badRequest: HTTPResponseStatus { HTTPResponseStatus(code: 400) }
public static var unauthorized: HTTPResponseStatus { HTTPResponseStatus(code: 401) }
public static var paymentRequired: HTTPResponseStatus { HTTPResponseStatus(code: 402) }
public static var forbidden: HTTPResponseStatus { HTTPResponseStatus(code: 403) }
public static var notFound: HTTPResponseStatus { HTTPResponseStatus(code: 404) }
public static var methodNotAllowed: HTTPResponseStatus { HTTPResponseStatus(code: 405) }
public static var notAcceptable: HTTPResponseStatus { HTTPResponseStatus(code: 406) }
public static var proxyAuthenticationRequired: HTTPResponseStatus { HTTPResponseStatus(code: 407) }
public static var requestTimeout: HTTPResponseStatus { HTTPResponseStatus(code: 408) }
public static var conflict: HTTPResponseStatus { HTTPResponseStatus(code: 409) }
public static var gone: HTTPResponseStatus { HTTPResponseStatus(code: 410) }
public static var lengthRequired: HTTPResponseStatus { HTTPResponseStatus(code: 411) }
public static var preconditionFailed: HTTPResponseStatus { HTTPResponseStatus(code: 412) }
public static var payloadTooLarge: HTTPResponseStatus { HTTPResponseStatus(code: 413) }
public static var uriTooLong: HTTPResponseStatus { HTTPResponseStatus(code: 414) }
public static var unsupportedMediaType: HTTPResponseStatus { HTTPResponseStatus(code: 415) }
public static var rangeNotSatisfiable: HTTPResponseStatus { HTTPResponseStatus(code: 416) }
public static var expectationFailed: HTTPResponseStatus { HTTPResponseStatus(code: 417) }
public static var imATeapot: HTTPResponseStatus { HTTPResponseStatus(code: 418) }
public static var misdirectedRequest: HTTPResponseStatus { HTTPResponseStatus(code: 421) }
public static var unprocessableEntity: HTTPResponseStatus { HTTPResponseStatus(code: 422) }
public static var locked: HTTPResponseStatus { HTTPResponseStatus(code: 423) }
public static var failedDependency: HTTPResponseStatus { HTTPResponseStatus(code: 424) }
public static var upgradeRequired: HTTPResponseStatus { HTTPResponseStatus(code: 426) }
public static var preconditionRequired: HTTPResponseStatus { HTTPResponseStatus(code: 428) }
public static var tooManyRequests: HTTPResponseStatus { HTTPResponseStatus(code: 429) }
public static var requestHeaderFieldsTooLarge: HTTPResponseStatus { HTTPResponseStatus(code: 431) }
public static var unavailableForLegalReasons: HTTPResponseStatus { HTTPResponseStatus(code: 451) }

public static var internalServerError: HTTPResponseStatus { HTTPResponseStatus(code: 500) }
public static var notImplemented: HTTPResponseStatus { HTTPResponseStatus(code: 501) }
public static var badGateway: HTTPResponseStatus { HTTPResponseStatus(code: 502) }
public static var serviceUnavailable: HTTPResponseStatus { HTTPResponseStatus(code: 503) }
public static var gatewayTimeout: HTTPResponseStatus { HTTPResponseStatus(code: 504) }
public static var httpVersionNotSupported: HTTPResponseStatus { HTTPResponseStatus(code: 505) }
public static var variantAlsoNegotiates: HTTPResponseStatus { HTTPResponseStatus(code: 506) }
public static var insufficientStorage: HTTPResponseStatus { HTTPResponseStatus(code: 507) }
public static var loopDetected: HTTPResponseStatus { HTTPResponseStatus(code: 508) }
public static var notExtended: HTTPResponseStatus { HTTPResponseStatus(code: 510) }
public static var networkAuthenticationRequired: HTTPResponseStatus { HTTPResponseStatus(code: 511) }
}

extension HTTPResponseStatus: Equatable {
public static func == (lhs: Self, rhs: Self) -> Bool {
lhs.code == rhs.code
public init(from decoder: any Decoder) throws {
let code = try decoder.singleValueContainer().decode(Int.self)
self.init(code: code)
}
}

extension HTTPResponseStatus: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.code = try container.decode(UInt.self)
self.reasonPhrase = nil
}

public func encode(to encoder: Encoder) throws {
extension HTTPRequest.Method: Codable {
public func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.code)
try container.encode(self.rawValue)
}
}

extension String {
internal var isValidHTTPToken: Bool {
self.utf8.allSatisfy { char -> Bool in
switch char {
case UInt8(ascii: "a") ... UInt8(ascii: "z"),
UInt8(ascii: "A") ... UInt8(ascii: "Z"),
UInt8(ascii: "0") ... UInt8(ascii: "9"),
UInt8(ascii: "!"),
UInt8(ascii: "#"),
UInt8(ascii: "$"),
UInt8(ascii: "%"),
UInt8(ascii: "&"),
UInt8(ascii: "'"),
UInt8(ascii: "*"),
UInt8(ascii: "+"),
UInt8(ascii: "-"),
UInt8(ascii: "."),
UInt8(ascii: "^"),
UInt8(ascii: "_"),
UInt8(ascii: "`"),
UInt8(ascii: "|"),
UInt8(ascii: "~"):
return true
default:
return false
}
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
let rawMethod = try container.decode(String.self)
guard let method = HTTPRequest.Method(rawMethod) else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "\"\(rawMethod)\" is not a valid method")
}

self = method
}
}

#if swift(>=5.6)
extension HTTPMethod: Sendable {}
extension HTTPResponseStatus: Sendable {}
#endif
Loading

0 comments on commit f015dbf

Please sign in to comment.