Skip to content

Commit 76ff30e

Browse files
committed
Drop NIOFoundationCompat dependency
1 parent 0af509b commit 76ff30e

File tree

4 files changed

+252
-9
lines changed

4 files changed

+252
-9
lines changed

Package.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ let package = Package(
3333
dependencies: [
3434
.byName(name: "AWSLambdaRuntimeCore"),
3535
.product(name: "NIOCore", package: "swift-nio"),
36-
.product(name: "NIOFoundationCompat", package: "swift-nio"),
3736
],
3837
swiftSettings: [.swiftLanguageMode(.v5)]
3938
),

Sources/AWSLambdaRuntime/Lambda+Codable.swift

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
@_exported import AWSLambdaRuntimeCore
1616
import NIOCore
17-
import NIOFoundationCompat
1817

1918
#if canImport(FoundationEssentials)
2019
import FoundationEssentials
@@ -24,7 +23,24 @@ import class Foundation.JSONDecoder
2423
import class Foundation.JSONEncoder
2524
#endif
2625

27-
extension JSONDecoder: AWSLambdaRuntimeCore.LambdaEventDecoder {}
26+
public struct LambdaJSONEventDecoder: LambdaEventDecoder {
27+
@usableFromInline let jsonDecoder: JSONDecoder
28+
29+
@inlinable
30+
public init(_ jsonDecoder: JSONDecoder) {
31+
self.jsonDecoder = jsonDecoder
32+
}
33+
34+
@inlinable
35+
public func decode<Event>(_ type: Event.Type, from buffer: NIOCore.ByteBuffer) throws -> Event where Event : Decodable {
36+
try buffer.getJSONDecodable(
37+
Event.self,
38+
decoder: self.jsonDecoder,
39+
at: buffer.readerIndex,
40+
length: buffer.readableBytes
41+
)! // must work, enough readable bytes
42+
}
43+
}
2844

2945
public struct LambdaJSONOutputEncoder<Output: Encodable>: LambdaOutputEncoder {
3046
@usableFromInline let jsonEncoder: JSONEncoder
@@ -36,7 +52,7 @@ public struct LambdaJSONOutputEncoder<Output: Encodable>: LambdaOutputEncoder {
3652

3753
@inlinable
3854
public func encode(_ value: Output, into buffer: inout ByteBuffer) throws {
39-
try self.jsonEncoder.encode(value, into: &buffer)
55+
try buffer.writeJSONEncodable(value, encoder: self.jsonEncoder)
4056
}
4157
}
4258

@@ -55,11 +71,11 @@ extension LambdaCodableAdapter {
5571
Output: Encodable,
5672
Output == Handler.Output,
5773
Encoder == LambdaJSONOutputEncoder<Output>,
58-
Decoder == JSONDecoder
74+
Decoder == LambdaJSONEventDecoder
5975
{
6076
self.init(
6177
encoder: LambdaJSONOutputEncoder(encoder),
62-
decoder: decoder,
78+
decoder: LambdaJSONEventDecoder(decoder),
6379
handler: handler
6480
)
6581
}
@@ -80,7 +96,7 @@ extension LambdaRuntime {
8096
LambdaHandlerAdapter<Event, Output, ClosureHandler<Event, Output>>,
8197
Event,
8298
Output,
83-
JSONDecoder,
99+
LambdaJSONEventDecoder,
84100
LambdaJSONOutputEncoder<Output>
85101
>
86102
{
@@ -105,12 +121,12 @@ extension LambdaRuntime {
105121
LambdaHandlerAdapter<Event, Void, ClosureHandler<Event, Void>>,
106122
Event,
107123
Void,
108-
JSONDecoder,
124+
LambdaJSONEventDecoder,
109125
VoidEncoder
110126
>
111127
{
112128
let handler = LambdaCodableAdapter(
113-
decoder: decoder,
129+
decoder: LambdaJSONEventDecoder(decoder),
114130
handler: LambdaHandlerAdapter(handler: ClosureHandler(body: body))
115131
)
116132

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftNIO open source project
4+
//
5+
// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import Foundation
16+
import NIOCore
17+
18+
// This is NIO's `NIOFoundationCompat` module which at the moment only adds `ByteBuffer` utility methods
19+
// for Foundation's `Data` type.
20+
//
21+
// The reason that it's not in the `NIO` module is that we don't want to have any direct Foundation dependencies
22+
// in `NIO` as Foundation is problematic for a few reasons:
23+
//
24+
// - its implementation is different on Linux and on macOS which means our macOS tests might be inaccurate
25+
// - on macOS Foundation is mostly written in ObjC which means the autorelease pool might get populated
26+
// - `swift-corelibs-foundation` (the OSS Foundation used on Linux) links the world which will prevent anyone from
27+
// having static binaries. It can also cause problems in the choice of an SSL library as Foundation already brings
28+
// the platforms OpenSSL in which might cause problems.
29+
30+
extension ByteBuffer {
31+
/// Controls how bytes are transferred between `ByteBuffer` and other storage types.
32+
@usableFromInline
33+
enum ByteTransferStrategy: Sendable {
34+
/// Force a copy of the bytes.
35+
case copy
36+
37+
/// Do not copy the bytes if at all possible.
38+
case noCopy
39+
40+
/// Use a heuristic to decide whether to copy the bytes or not.
41+
case automatic
42+
}
43+
44+
// MARK: - Data APIs
45+
46+
/// Return `length` bytes starting at `index` and return the result as `Data`. This will not change the reader index.
47+
/// The selected bytes must be readable or else `nil` will be returned.
48+
///
49+
/// - parameters:
50+
/// - index: The starting index of the bytes of interest into the `ByteBuffer`
51+
/// - length: The number of bytes of interest
52+
/// - byteTransferStrategy: Controls how to transfer the bytes. See `ByteTransferStrategy` for an explanation
53+
/// of the options.
54+
/// - returns: A `Data` value containing the bytes of interest or `nil` if the selected bytes are not readable.
55+
@usableFromInline
56+
func getData(at index0: Int, length: Int, byteTransferStrategy: ByteTransferStrategy) -> Data? {
57+
let index = index0 - self.readerIndex
58+
guard index >= 0 && length >= 0 && index <= self.readableBytes - length else {
59+
return nil
60+
}
61+
let doCopy: Bool
62+
switch byteTransferStrategy {
63+
case .copy:
64+
doCopy = true
65+
case .noCopy:
66+
doCopy = false
67+
case .automatic:
68+
doCopy = length <= 256 * 1024
69+
}
70+
71+
return self.withUnsafeReadableBytesWithStorageManagement { ptr, storageRef in
72+
if doCopy {
73+
return Data(
74+
bytes: UnsafeMutableRawPointer(mutating: ptr.baseAddress!.advanced(by: index)),
75+
count: Int(length)
76+
)
77+
} else {
78+
_ = storageRef.retain()
79+
return Data(
80+
bytesNoCopy: UnsafeMutableRawPointer(mutating: ptr.baseAddress!.advanced(by: index)),
81+
count: Int(length),
82+
deallocator: .custom { _, _ in storageRef.release() }
83+
)
84+
}
85+
}
86+
}
87+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
//
2+
// JSON+ByteBuffer.swift
3+
// swift-aws-lambda-runtime
4+
//
5+
// Created by Fabian Fett on 06.09.24.
6+
//
7+
8+
//===----------------------------------------------------------------------===//
9+
//
10+
// This source file is part of the SwiftNIO open source project
11+
//
12+
// Copyright (c) 2019-2021 Apple Inc. and the SwiftNIO project authors
13+
// Licensed under Apache License v2.0
14+
//
15+
// See LICENSE.txt for license information
16+
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
17+
//
18+
// SPDX-License-Identifier: Apache-2.0
19+
//
20+
//===----------------------------------------------------------------------===//
21+
22+
#if canImport(FoundationEssentials)
23+
import FoundationEssentials
24+
#else
25+
import Foundation
26+
#endif
27+
import NIOCore
28+
29+
extension ByteBuffer {
30+
/// Attempts to decode the `length` bytes from `index` using the `JSONDecoder` `decoder` as `T`.
31+
///
32+
/// - parameters:
33+
/// - type: The type type that is attempted to be decoded.
34+
/// - decoder: The `JSONDecoder` that is used for the decoding.
35+
/// - index: The index of the first byte to decode.
36+
/// - length: The number of bytes to decode.
37+
/// - returns: The decoded value if successful or `nil` if there are not enough readable bytes available.
38+
@inlinable
39+
func getJSONDecodable<T: Decodable>(
40+
_ type: T.Type,
41+
decoder: JSONDecoder = JSONDecoder(),
42+
at index: Int,
43+
length: Int
44+
) throws -> T? {
45+
guard let data = self.getData(at: index, length: length, byteTransferStrategy: .noCopy) else {
46+
return nil
47+
}
48+
return try decoder.decode(T.self, from: data)
49+
}
50+
51+
/// Encodes `value` using the `JSONEncoder` `encoder` and set the resulting bytes into this `ByteBuffer` at the
52+
/// given `index`.
53+
///
54+
/// - note: The `writerIndex` remains unchanged.
55+
///
56+
/// - parameters:
57+
/// - value: An `Encodable` value to encode.
58+
/// - encoder: The `JSONEncoder` to encode `value` with.
59+
/// - returns: The number of bytes written.
60+
@inlinable
61+
@discardableResult
62+
mutating func setJSONEncodable<T: Encodable>(
63+
_ value: T,
64+
encoder: JSONEncoder = JSONEncoder(),
65+
at index: Int
66+
) throws -> Int {
67+
let data = try encoder.encode(value)
68+
return self.setBytes(data, at: index)
69+
}
70+
71+
/// Encodes `value` using the `JSONEncoder` `encoder` and writes the resulting bytes into this `ByteBuffer`.
72+
///
73+
/// If successful, this will move the writer index forward by the number of bytes written.
74+
///
75+
/// - parameters:
76+
/// - value: An `Encodable` value to encode.
77+
/// - encoder: The `JSONEncoder` to encode `value` with.
78+
/// - returns: The number of bytes written.
79+
@inlinable
80+
@discardableResult
81+
mutating func writeJSONEncodable<T: Encodable>(
82+
_ value: T,
83+
encoder: JSONEncoder = JSONEncoder()
84+
) throws -> Int {
85+
let result = try self.setJSONEncodable(value, encoder: encoder, at: self.writerIndex)
86+
self.moveWriterIndex(forwardBy: result)
87+
return result
88+
}
89+
}
90+
91+
extension JSONDecoder {
92+
/// Returns a value of the type you specify, decoded from a JSON object inside the readable bytes of a `ByteBuffer`.
93+
///
94+
/// If the `ByteBuffer` does not contain valid JSON, this method throws the
95+
/// `DecodingError.dataCorrupted(_:)` error. If a value within the JSON
96+
/// fails to decode, this method throws the corresponding error.
97+
///
98+
/// - note: The provided `ByteBuffer` remains unchanged, neither the `readerIndex` nor the `writerIndex` will move.
99+
/// If you would like the `readerIndex` to move, consider using `ByteBuffer.readJSONDecodable(_:length:)`.
100+
///
101+
/// - parameters:
102+
/// - type: The type of the value to decode from the supplied JSON object.
103+
/// - buffer: The `ByteBuffer` that contains JSON object to decode.
104+
/// - returns: The decoded object.
105+
func decode<T: Decodable>(_ type: T.Type, from buffer: ByteBuffer) throws -> T {
106+
try buffer.getJSONDecodable(
107+
T.self,
108+
decoder: self,
109+
at: buffer.readerIndex,
110+
length: buffer.readableBytes
111+
)! // must work, enough readable bytes// must work, enough readable bytes
112+
}
113+
}
114+
115+
extension JSONEncoder {
116+
/// Writes a JSON-encoded representation of the value you supply into the supplied `ByteBuffer`.
117+
///
118+
/// - parameters:
119+
/// - value: The value to encode as JSON.
120+
/// - buffer: The `ByteBuffer` to encode into.
121+
@inlinable
122+
func encode<T: Encodable>(
123+
_ value: T,
124+
into buffer: inout ByteBuffer
125+
) throws {
126+
try buffer.writeJSONEncodable(value, encoder: self)
127+
}
128+
129+
/// Writes a JSON-encoded representation of the value you supply into a `ByteBuffer` that is freshly allocated.
130+
///
131+
/// - parameters:
132+
/// - value: The value to encode as JSON.
133+
/// - allocator: The `ByteBufferAllocator` which is used to allocate the `ByteBuffer` to be returned.
134+
/// - returns: The `ByteBuffer` containing the encoded JSON.
135+
func encodeAsByteBuffer<T: Encodable>(_ value: T, allocator: ByteBufferAllocator) throws -> ByteBuffer {
136+
let data = try self.encode(value)
137+
var buffer = allocator.buffer(capacity: data.count)
138+
buffer.writeBytes(data)
139+
return buffer
140+
}
141+
}

0 commit comments

Comments
 (0)