diff --git a/Package.swift b/Package.swift index 2550ad34..5a5798f3 100644 --- a/Package.swift +++ b/Package.swift @@ -32,8 +32,7 @@ let package = Package( dependencies: [ .byName(name: "AWSLambdaRuntimeCore"), .product(name: "NIOCore", package: "swift-nio"), - .product(name: "NIOFoundationCompat", package: "swift-nio"), - ] + ], ), .target( name: "AWSLambdaRuntimeCore", diff --git a/Sources/AWSLambdaRuntime/Lambda+Codable.swift b/Sources/AWSLambdaRuntime/Lambda+Codable.swift index 2ab63855..9193deff 100644 --- a/Sources/AWSLambdaRuntime/Lambda+Codable.swift +++ b/Sources/AWSLambdaRuntime/Lambda+Codable.swift @@ -14,7 +14,6 @@ @_exported import AWSLambdaRuntimeCore import NIOCore -import NIOFoundationCompat #if canImport(FoundationEssentials) import FoundationEssentials @@ -24,7 +23,24 @@ import class Foundation.JSONDecoder import class Foundation.JSONEncoder #endif -extension JSONDecoder: AWSLambdaRuntimeCore.LambdaEventDecoder {} +public struct LambdaJSONEventDecoder: LambdaEventDecoder { + @usableFromInline let jsonDecoder: JSONDecoder + + @inlinable + public init(_ jsonDecoder: JSONDecoder) { + self.jsonDecoder = jsonDecoder + } + + @inlinable + public func decode(_ type: Event.Type, from buffer: NIOCore.ByteBuffer) throws -> Event where Event : Decodable { + try buffer.getJSONDecodable( + Event.self, + decoder: self.jsonDecoder, + at: buffer.readerIndex, + length: buffer.readableBytes + )! // must work, enough readable bytes + } +} public struct LambdaJSONOutputEncoder: LambdaOutputEncoder { @usableFromInline let jsonEncoder: JSONEncoder @@ -36,7 +52,7 @@ public struct LambdaJSONOutputEncoder: LambdaOutputEncoder { @inlinable public func encode(_ value: Output, into buffer: inout ByteBuffer) throws { - try self.jsonEncoder.encode(value, into: &buffer) + try buffer.writeJSONEncodable(value, encoder: self.jsonEncoder) } } @@ -55,11 +71,11 @@ extension LambdaCodableAdapter { Output: Encodable, Output == Handler.Output, Encoder == LambdaJSONOutputEncoder, - Decoder == JSONDecoder + Decoder == LambdaJSONEventDecoder { self.init( encoder: LambdaJSONOutputEncoder(encoder), - decoder: decoder, + decoder: LambdaJSONEventDecoder(decoder), handler: handler ) } @@ -81,7 +97,7 @@ extension LambdaRuntime { LambdaHandlerAdapter>, Event, Output, - JSONDecoder, + LambdaJSONEventDecoder, LambdaJSONOutputEncoder > { @@ -106,12 +122,12 @@ extension LambdaRuntime { LambdaHandlerAdapter>, Event, Void, - JSONDecoder, + LambdaJSONEventDecoder, VoidEncoder > { let handler = LambdaCodableAdapter( - decoder: decoder, + decoder: LambdaJSONEventDecoder(decoder), handler: LambdaHandlerAdapter(handler: ClosureHandler(body: body)) ) diff --git a/Sources/AWSLambdaRuntime/Vendored/ByteBuffer-foundation.swift b/Sources/AWSLambdaRuntime/Vendored/ByteBuffer-foundation.swift new file mode 100644 index 00000000..6c289275 --- /dev/null +++ b/Sources/AWSLambdaRuntime/Vendored/ByteBuffer-foundation.swift @@ -0,0 +1,87 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftNIO project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Foundation +import NIOCore + +// This is NIO's `NIOFoundationCompat` module which at the moment only adds `ByteBuffer` utility methods +// for Foundation's `Data` type. +// +// The reason that it's not in the `NIO` module is that we don't want to have any direct Foundation dependencies +// in `NIO` as Foundation is problematic for a few reasons: +// +// - its implementation is different on Linux and on macOS which means our macOS tests might be inaccurate +// - on macOS Foundation is mostly written in ObjC which means the autorelease pool might get populated +// - `swift-corelibs-foundation` (the OSS Foundation used on Linux) links the world which will prevent anyone from +// having static binaries. It can also cause problems in the choice of an SSL library as Foundation already brings +// the platforms OpenSSL in which might cause problems. + +extension ByteBuffer { + /// Controls how bytes are transferred between `ByteBuffer` and other storage types. + @usableFromInline + enum ByteTransferStrategy: Sendable { + /// Force a copy of the bytes. + case copy + + /// Do not copy the bytes if at all possible. + case noCopy + + /// Use a heuristic to decide whether to copy the bytes or not. + case automatic + } + + // MARK: - Data APIs + + /// Return `length` bytes starting at `index` and return the result as `Data`. This will not change the reader index. + /// The selected bytes must be readable or else `nil` will be returned. + /// + /// - parameters: + /// - index: The starting index of the bytes of interest into the `ByteBuffer` + /// - length: The number of bytes of interest + /// - byteTransferStrategy: Controls how to transfer the bytes. See `ByteTransferStrategy` for an explanation + /// of the options. + /// - returns: A `Data` value containing the bytes of interest or `nil` if the selected bytes are not readable. + @usableFromInline + func getData(at index0: Int, length: Int, byteTransferStrategy: ByteTransferStrategy) -> Data? { + let index = index0 - self.readerIndex + guard index >= 0 && length >= 0 && index <= self.readableBytes - length else { + return nil + } + let doCopy: Bool + switch byteTransferStrategy { + case .copy: + doCopy = true + case .noCopy: + doCopy = false + case .automatic: + doCopy = length <= 256 * 1024 + } + + return self.withUnsafeReadableBytesWithStorageManagement { ptr, storageRef in + if doCopy { + return Data( + bytes: UnsafeMutableRawPointer(mutating: ptr.baseAddress!.advanced(by: index)), + count: Int(length) + ) + } else { + _ = storageRef.retain() + return Data( + bytesNoCopy: UnsafeMutableRawPointer(mutating: ptr.baseAddress!.advanced(by: index)), + count: Int(length), + deallocator: .custom { _, _ in storageRef.release() } + ) + } + } + } +} diff --git a/Sources/AWSLambdaRuntime/Vendored/JSON+ByteBuffer.swift b/Sources/AWSLambdaRuntime/Vendored/JSON+ByteBuffer.swift new file mode 100644 index 00000000..63f05405 --- /dev/null +++ b/Sources/AWSLambdaRuntime/Vendored/JSON+ByteBuffer.swift @@ -0,0 +1,141 @@ +// +// JSON+ByteBuffer.swift +// swift-aws-lambda-runtime +// +// Created by Fabian Fett on 06.09.24. +// + +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2019-2021 Apple Inc. and the SwiftNIO project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftNIO project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif +import NIOCore + +extension ByteBuffer { + /// Attempts to decode the `length` bytes from `index` using the `JSONDecoder` `decoder` as `T`. + /// + /// - parameters: + /// - type: The type type that is attempted to be decoded. + /// - decoder: The `JSONDecoder` that is used for the decoding. + /// - index: The index of the first byte to decode. + /// - length: The number of bytes to decode. + /// - returns: The decoded value if successful or `nil` if there are not enough readable bytes available. + @inlinable + func getJSONDecodable( + _ type: T.Type, + decoder: JSONDecoder = JSONDecoder(), + at index: Int, + length: Int + ) throws -> T? { + guard let data = self.getData(at: index, length: length, byteTransferStrategy: .noCopy) else { + return nil + } + return try decoder.decode(T.self, from: data) + } + + /// Encodes `value` using the `JSONEncoder` `encoder` and set the resulting bytes into this `ByteBuffer` at the + /// given `index`. + /// + /// - note: The `writerIndex` remains unchanged. + /// + /// - parameters: + /// - value: An `Encodable` value to encode. + /// - encoder: The `JSONEncoder` to encode `value` with. + /// - returns: The number of bytes written. + @inlinable + @discardableResult + mutating func setJSONEncodable( + _ value: T, + encoder: JSONEncoder = JSONEncoder(), + at index: Int + ) throws -> Int { + let data = try encoder.encode(value) + return self.setBytes(data, at: index) + } + + /// Encodes `value` using the `JSONEncoder` `encoder` and writes the resulting bytes into this `ByteBuffer`. + /// + /// If successful, this will move the writer index forward by the number of bytes written. + /// + /// - parameters: + /// - value: An `Encodable` value to encode. + /// - encoder: The `JSONEncoder` to encode `value` with. + /// - returns: The number of bytes written. + @inlinable + @discardableResult + mutating func writeJSONEncodable( + _ value: T, + encoder: JSONEncoder = JSONEncoder() + ) throws -> Int { + let result = try self.setJSONEncodable(value, encoder: encoder, at: self.writerIndex) + self.moveWriterIndex(forwardBy: result) + return result + } +} + +extension JSONDecoder { + /// Returns a value of the type you specify, decoded from a JSON object inside the readable bytes of a `ByteBuffer`. + /// + /// If the `ByteBuffer` does not contain valid JSON, this method throws the + /// `DecodingError.dataCorrupted(_:)` error. If a value within the JSON + /// fails to decode, this method throws the corresponding error. + /// + /// - note: The provided `ByteBuffer` remains unchanged, neither the `readerIndex` nor the `writerIndex` will move. + /// If you would like the `readerIndex` to move, consider using `ByteBuffer.readJSONDecodable(_:length:)`. + /// + /// - parameters: + /// - type: The type of the value to decode from the supplied JSON object. + /// - buffer: The `ByteBuffer` that contains JSON object to decode. + /// - returns: The decoded object. + func decode(_ type: T.Type, from buffer: ByteBuffer) throws -> T { + try buffer.getJSONDecodable( + T.self, + decoder: self, + at: buffer.readerIndex, + length: buffer.readableBytes + )! // must work, enough readable bytes// must work, enough readable bytes + } +} + +extension JSONEncoder { + /// Writes a JSON-encoded representation of the value you supply into the supplied `ByteBuffer`. + /// + /// - parameters: + /// - value: The value to encode as JSON. + /// - buffer: The `ByteBuffer` to encode into. + @inlinable + func encode( + _ value: T, + into buffer: inout ByteBuffer + ) throws { + try buffer.writeJSONEncodable(value, encoder: self) + } + + /// Writes a JSON-encoded representation of the value you supply into a `ByteBuffer` that is freshly allocated. + /// + /// - parameters: + /// - value: The value to encode as JSON. + /// - allocator: The `ByteBufferAllocator` which is used to allocate the `ByteBuffer` to be returned. + /// - returns: The `ByteBuffer` containing the encoded JSON. + func encodeAsByteBuffer(_ value: T, allocator: ByteBufferAllocator) throws -> ByteBuffer { + let data = try self.encode(value) + var buffer = allocator.buffer(capacity: data.count) + buffer.writeBytes(data) + return buffer + } +}