Skip to content

Use Swift HTTP types package for status and method #47

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

Merged
merged 2 commits into from
May 23, 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
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