Skip to content

Ensure AWSLambdaRuntime only links FoundationEssentials #365

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
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: 1 addition & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
32 changes: 24 additions & 8 deletions Sources/AWSLambdaRuntime/Lambda+Codable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

@_exported import AWSLambdaRuntimeCore
import NIOCore
import NIOFoundationCompat

#if canImport(FoundationEssentials)
import FoundationEssentials
Expand All @@ -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<Event>(_ 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<Output: Encodable>: LambdaOutputEncoder {
@usableFromInline let jsonEncoder: JSONEncoder
Expand All @@ -36,7 +52,7 @@ public struct LambdaJSONOutputEncoder<Output: Encodable>: 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)
}
}

Expand All @@ -55,11 +71,11 @@ extension LambdaCodableAdapter {
Output: Encodable,
Output == Handler.Output,
Encoder == LambdaJSONOutputEncoder<Output>,
Decoder == JSONDecoder
Decoder == LambdaJSONEventDecoder
{
self.init(
encoder: LambdaJSONOutputEncoder(encoder),
decoder: decoder,
decoder: LambdaJSONEventDecoder(decoder),
handler: handler
)
}
Expand All @@ -81,7 +97,7 @@ extension LambdaRuntime {
LambdaHandlerAdapter<Event, Output, ClosureHandler<Event, Output>>,
Event,
Output,
JSONDecoder,
LambdaJSONEventDecoder,
LambdaJSONOutputEncoder<Output>
>
{
Expand All @@ -106,12 +122,12 @@ extension LambdaRuntime {
LambdaHandlerAdapter<Event, Void, ClosureHandler<Event, Void>>,
Event,
Void,
JSONDecoder,
LambdaJSONEventDecoder,
VoidEncoder
>
{
let handler = LambdaCodableAdapter(
decoder: decoder,
decoder: LambdaJSONEventDecoder(decoder),
handler: LambdaHandlerAdapter(handler: ClosureHandler(body: body))
)

Expand Down
87 changes: 87 additions & 0 deletions Sources/AWSLambdaRuntime/Vendored/ByteBuffer-foundation.swift
Original file line number Diff line number Diff line change
@@ -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() }
)
}
}
}
}
141 changes: 141 additions & 0 deletions Sources/AWSLambdaRuntime/Vendored/JSON+ByteBuffer.swift
Original file line number Diff line number Diff line change
@@ -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<T: Decodable>(
_ 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<T: Encodable>(
_ 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<T: Encodable>(
_ 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<T: Decodable>(_ 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<T: Encodable>(
_ 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<T: Encodable>(_ value: T, allocator: ByteBufferAllocator) throws -> ByteBuffer {
let data = try self.encode(value)
var buffer = allocator.buffer(capacity: data.count)
buffer.writeBytes(data)
return buffer
}
}
Loading