Skip to content

Commit 5060bb9

Browse files
authored
[Multipart] Add public types (#77)
[Multipart] Add public types ### Motivation Add the public types approved in SOAR-0009. ### Modifications Added the public types and make other changes outlined in SOAR-0009. ### Result Most of the required runtime types are now in place. ### Test Plan Added unit tests for the new code, where it made sense. ⚠️ The pipeline `pull request validation (api breakage)` is failing with: ``` 1 breaking change detected in OpenAPIRuntime: 💔 API breakage: constructor Configuration.init(dateTranscoder:) has removed default argument from parameter 0 ** ERROR: ❌ Breaking API changes detected. ``` but that seems to be a false positive in the tool, as there is now a newer initializer that you can use as `.init()`, `.init(dateTranscoder:)`, `.init(multipartBoundaryGenerator:)`, or `init(dateTranscoder:multipartBoundaryGenerator:)`, so no existing code could be broken by this change. Reviewed by: simonjbeaumont Builds: ✔︎ pull request validation (5.10) - Build finished. ✔︎ pull request validation (5.8) - Build finished. ✔︎ pull request validation (5.9) - Build finished. ✔︎ pull request validation (docc test) - Build finished. ✔︎ pull request validation (integration test) - Build finished. ✔︎ pull request validation (nightly) - Build finished. ✔︎ pull request validation (soundness) - Build finished. ✖︎ pull request validation (api breakage) - Build finished. #77
1 parent d50b489 commit 5060bb9

File tree

8 files changed

+610
-110
lines changed

8 files changed

+610
-110
lines changed

Sources/OpenAPIRuntime/Base/CopyOnWriteBox.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@
2626

2727
/// Creates a new storage with the provided initial value.
2828
/// - Parameter value: The initial value to store in the box.
29-
@inlinable init(value: Wrapped) { self.value = value }
29+
@usableFromInline init(value: Wrapped) { self.value = value }
3030
}
3131

3232
/// The internal storage of the box.
3333
@usableFromInline internal var storage: Storage
3434

3535
/// Creates a new box.
3636
/// - Parameter value: The value to store in the box.
37-
@inlinable public init(value: Wrapped) { self.storage = .init(value: value) }
37+
public init(value: Wrapped) { self.storage = .init(value: value) }
3838

3939
/// The stored value whose accessors enforce copy-on-write semantics.
4040
@inlinable public var value: Wrapped {

Sources/OpenAPIRuntime/Conversion/Configuration.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,20 @@ public struct Configuration: Sendable {
7474
/// The transcoder used when converting between date and string values.
7575
public var dateTranscoder: any DateTranscoder
7676

77+
/// The generator to use when creating mutlipart bodies.
78+
public var multipartBoundaryGenerator: any MultipartBoundaryGenerator
79+
7780
/// Creates a new configuration with the specified values.
7881
///
79-
/// - Parameter dateTranscoder: The transcoder to use when converting between date
82+
/// - Parameters:
83+
/// - dateTranscoder: The transcoder to use when converting between date
8084
/// and string values.
81-
public init(dateTranscoder: any DateTranscoder = .iso8601) { self.dateTranscoder = dateTranscoder }
85+
/// - multipartBoundaryGenerator: The generator to use when creating mutlipart bodies.
86+
public init(
87+
dateTranscoder: any DateTranscoder = .iso8601,
88+
multipartBoundaryGenerator: any MultipartBoundaryGenerator = .random
89+
) {
90+
self.dateTranscoder = dateTranscoder
91+
self.multipartBoundaryGenerator = multipartBoundaryGenerator
92+
}
8293
}

Sources/OpenAPIRuntime/Deprecated/Deprecated.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,24 @@ extension DecodingError {
195195
}
196196
}
197197
}
198+
199+
extension Configuration {
200+
/// Creates a new configuration with the specified values.
201+
///
202+
/// - Parameter dateTranscoder: The transcoder to use when converting between date
203+
/// and string values.
204+
@available(*, deprecated, renamed: "init(dateTranscoder:multipartBoundaryGenerator:)") @_disfavoredOverload
205+
public init(dateTranscoder: any DateTranscoder) {
206+
self.init(dateTranscoder: dateTranscoder, multipartBoundaryGenerator: .random)
207+
}
208+
}
209+
210+
extension HTTPBody {
211+
/// Describes how many times the provided sequence can be iterated.
212+
@available(
213+
*,
214+
deprecated,
215+
renamed: "IterationBehavior",
216+
message: "Use the top level IterationBehavior directly instead of HTTPBody.IterationBehavior."
217+
) public typealias IterationBehavior = OpenAPIRuntime.IterationBehavior
218+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftOpenAPIGenerator open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator 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 SwiftOpenAPIGenerator project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
/// Describes how many times the provided sequence can be iterated.
16+
public enum IterationBehavior: Sendable {
17+
18+
/// The input sequence can only be iterated once.
19+
///
20+
/// If a retry or a redirect is encountered, fail the call with
21+
/// a descriptive error.
22+
case single
23+
24+
/// The input sequence can be iterated multiple times.
25+
///
26+
/// Supports retries and redirects, as a new iterator is created each
27+
/// time.
28+
case multiple
29+
}
30+
31+
// MARK: - Internal
32+
33+
/// A type-erasing closure-based iterator.
34+
@usableFromInline struct AnyIterator<Element: Sendable>: AsyncIteratorProtocol {
35+
36+
/// The closure that produces the next element.
37+
private let produceNext: () async throws -> Element?
38+
39+
/// Creates a new type-erased iterator from the provided iterator.
40+
/// - Parameter iterator: The iterator to type-erase.
41+
@usableFromInline init<Iterator: AsyncIteratorProtocol>(_ iterator: Iterator) where Iterator.Element == Element {
42+
var iterator = iterator
43+
self.produceNext = { try await iterator.next() }
44+
}
45+
46+
/// Advances the iterator to the next element and returns it asynchronously.
47+
///
48+
/// - Returns: The next element in the sequence, or `nil` if there are no more elements.
49+
/// - Throws: An error if there is an issue advancing the iterator or retrieving the next element.
50+
public mutating func next() async throws -> Element? { try await produceNext() }
51+
}
52+
53+
/// A type-erased async sequence that wraps input sequences.
54+
@usableFromInline struct AnySequence<Element: Sendable>: AsyncSequence, Sendable {
55+
56+
/// The type of the type-erased iterator.
57+
@usableFromInline typealias AsyncIterator = AnyIterator<Element>
58+
59+
/// A closure that produces a new iterator.
60+
@usableFromInline let produceIterator: @Sendable () -> AsyncIterator
61+
62+
/// Creates a new sequence.
63+
/// - Parameter sequence: The input sequence to type-erase.
64+
@usableFromInline init<Upstream: AsyncSequence>(_ sequence: Upstream)
65+
where Upstream.Element == Element, Upstream: Sendable {
66+
self.produceIterator = { .init(sequence.makeAsyncIterator()) }
67+
}
68+
69+
@usableFromInline func makeAsyncIterator() -> AsyncIterator { produceIterator() }
70+
}
71+
72+
/// An async sequence wrapper for a sync sequence.
73+
@usableFromInline struct WrappedSyncSequence<Upstream: Sequence & Sendable>: AsyncSequence, Sendable
74+
where Upstream.Element: Sendable {
75+
76+
/// The type of the iterator.
77+
@usableFromInline typealias AsyncIterator = Iterator<Element>
78+
79+
/// The element type.
80+
@usableFromInline typealias Element = Upstream.Element
81+
82+
/// An iterator type that wraps a sync sequence iterator.
83+
@usableFromInline struct Iterator<IteratorElement: Sendable>: AsyncIteratorProtocol {
84+
85+
/// The element type.
86+
@usableFromInline typealias Element = IteratorElement
87+
88+
/// The underlying sync sequence iterator.
89+
var iterator: any IteratorProtocol<Element>
90+
91+
@usableFromInline mutating func next() async throws -> IteratorElement? { iterator.next() }
92+
}
93+
94+
/// The underlying sync sequence.
95+
@usableFromInline let sequence: Upstream
96+
97+
/// Creates a new async sequence with the provided sync sequence.
98+
/// - Parameter sequence: The sync sequence to wrap.
99+
@usableFromInline init(sequence: Upstream) { self.sequence = sequence }
100+
101+
@usableFromInline func makeAsyncIterator() -> AsyncIterator { Iterator(iterator: sequence.makeIterator()) }
102+
}
103+
104+
/// An empty async sequence.
105+
@usableFromInline struct EmptySequence<Element: Sendable>: AsyncSequence, Sendable {
106+
107+
/// The type of the empty iterator.
108+
@usableFromInline typealias AsyncIterator = EmptyIterator<Element>
109+
110+
/// An async iterator of an empty sequence.
111+
@usableFromInline struct EmptyIterator<IteratorElement: Sendable>: AsyncIteratorProtocol {
112+
113+
@usableFromInline mutating func next() async throws -> IteratorElement? { nil }
114+
}
115+
116+
/// Creates a new empty async sequence.
117+
@usableFromInline init() {}
118+
119+
@usableFromInline func makeAsyncIterator() -> AsyncIterator { EmptyIterator() }
120+
}

Sources/OpenAPIRuntime/Interface/HTTPBody.swift

Lines changed: 14 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -121,25 +121,9 @@ public final class HTTPBody: @unchecked Sendable {
121121
/// The underlying byte chunk type.
122122
public typealias ByteChunk = ArraySlice<UInt8>
123123

124-
/// Describes how many times the provided sequence can be iterated.
125-
public enum IterationBehavior: Sendable {
126-
127-
/// The input sequence can only be iterated once.
128-
///
129-
/// If a retry or a redirect is encountered, fail the call with
130-
/// a descriptive error.
131-
case single
132-
133-
/// The input sequence can be iterated multiple times.
134-
///
135-
/// Supports retries and redirects, as a new iterator is created each
136-
/// time.
137-
case multiple
138-
}
139-
140-
/// The body's iteration behavior, which controls how many times
124+
/// The iteration behavior, which controls how many times
141125
/// the input sequence can be iterated.
142-
public let iterationBehavior: IterationBehavior
126+
public let iterationBehavior: OpenAPIRuntime.IterationBehavior
143127

144128
/// Describes the total length of the body, if known.
145129
public enum Length: Sendable, Equatable {
@@ -155,7 +139,7 @@ public final class HTTPBody: @unchecked Sendable {
155139
public let length: Length
156140

157141
/// The underlying type-erased async sequence.
158-
private let sequence: BodySequence
142+
private let sequence: AnySequence<ByteChunk>
159143

160144
/// A lock for shared mutable state.
161145
private let lock: NSLock = {
@@ -205,7 +189,11 @@ public final class HTTPBody: @unchecked Sendable {
205189
/// length of all the byte chunks.
206190
/// - iterationBehavior: The sequence's iteration behavior, which
207191
/// indicates whether the sequence can be iterated multiple times.
208-
@usableFromInline init(_ sequence: BodySequence, length: Length, iterationBehavior: IterationBehavior) {
192+
@usableFromInline init(
193+
_ sequence: AnySequence<ByteChunk>,
194+
length: Length,
195+
iterationBehavior: OpenAPIRuntime.IterationBehavior
196+
) {
209197
self.sequence = sequence
210198
self.length = length
211199
self.iterationBehavior = iterationBehavior
@@ -220,7 +208,7 @@ public final class HTTPBody: @unchecked Sendable {
220208
@usableFromInline convenience init(
221209
_ byteChunks: some Sequence<ByteChunk> & Sendable,
222210
length: Length,
223-
iterationBehavior: IterationBehavior
211+
iterationBehavior: OpenAPIRuntime.IterationBehavior
224212
) {
225213
self.init(
226214
.init(WrappedSyncSequence(sequence: byteChunks)),
@@ -281,7 +269,7 @@ extension HTTPBody {
281269
@inlinable public convenience init(
282270
_ bytes: some Sequence<UInt8> & Sendable,
283271
length: Length,
284-
iterationBehavior: IterationBehavior
272+
iterationBehavior: OpenAPIRuntime.IterationBehavior
285273
) { self.init([ArraySlice(bytes)], length: length, iterationBehavior: iterationBehavior) }
286274

287275
/// Creates a new body with the provided byte collection.
@@ -323,7 +311,7 @@ extension HTTPBody {
323311
@inlinable public convenience init<Bytes: AsyncSequence>(
324312
_ sequence: Bytes,
325313
length: HTTPBody.Length,
326-
iterationBehavior: IterationBehavior
314+
iterationBehavior: OpenAPIRuntime.IterationBehavior
327315
) where Bytes.Element == ByteChunk, Bytes: Sendable {
328316
self.init(.init(sequence), length: length, iterationBehavior: iterationBehavior)
329317
}
@@ -337,7 +325,7 @@ extension HTTPBody {
337325
@inlinable public convenience init<Bytes: AsyncSequence>(
338326
_ sequence: Bytes,
339327
length: HTTPBody.Length,
340-
iterationBehavior: IterationBehavior
328+
iterationBehavior: OpenAPIRuntime.IterationBehavior
341329
) where Bytes: Sendable, Bytes.Element: Sequence & Sendable, Bytes.Element.Element == UInt8 {
342330
self.init(sequence.map { ArraySlice($0) }, length: length, iterationBehavior: iterationBehavior)
343331
}
@@ -356,7 +344,7 @@ extension HTTPBody: AsyncSequence {
356344
public func makeAsyncIterator() -> AsyncIterator {
357345
// The crash on error is intentional here.
358346
try! tryToMarkIteratorCreated()
359-
return sequence.makeAsyncIterator()
347+
return .init(sequence.makeAsyncIterator())
360348
}
361349
}
362350

@@ -482,7 +470,7 @@ extension HTTPBody {
482470
@inlinable public convenience init<Strings: AsyncSequence>(
483471
_ sequence: Strings,
484472
length: HTTPBody.Length,
485-
iterationBehavior: IterationBehavior
473+
iterationBehavior: OpenAPIRuntime.IterationBehavior
486474
) where Strings.Element: StringProtocol & Sendable, Strings: Sendable {
487475
self.init(.init(sequence.map { ByteChunk.init($0) }), length: length, iterationBehavior: iterationBehavior)
488476
}
@@ -583,83 +571,3 @@ extension HTTPBody {
583571
public mutating func next() async throws -> Element? { try await produceNext() }
584572
}
585573
}
586-
587-
extension HTTPBody {
588-
589-
/// A type-erased async sequence that wraps input sequences.
590-
@usableFromInline struct BodySequence: AsyncSequence, Sendable {
591-
592-
/// The type of the type-erased iterator.
593-
@usableFromInline typealias AsyncIterator = HTTPBody.Iterator
594-
595-
/// The byte chunk element type.
596-
@usableFromInline typealias Element = ByteChunk
597-
598-
/// A closure that produces a new iterator.
599-
@usableFromInline let produceIterator: @Sendable () -> AsyncIterator
600-
601-
/// Creates a new sequence.
602-
/// - Parameter sequence: The input sequence to type-erase.
603-
@inlinable init<Bytes: AsyncSequence>(_ sequence: Bytes) where Bytes.Element == Element, Bytes: Sendable {
604-
self.produceIterator = { .init(sequence.makeAsyncIterator()) }
605-
}
606-
607-
@usableFromInline func makeAsyncIterator() -> AsyncIterator { produceIterator() }
608-
}
609-
610-
/// An async sequence wrapper for a sync sequence.
611-
@usableFromInline struct WrappedSyncSequence<Bytes: Sequence>: AsyncSequence, Sendable
612-
where Bytes.Element == ByteChunk, Bytes.Iterator.Element == ByteChunk, Bytes: Sendable {
613-
614-
/// The type of the iterator.
615-
@usableFromInline typealias AsyncIterator = Iterator
616-
617-
/// The byte chunk element type.
618-
@usableFromInline typealias Element = ByteChunk
619-
620-
/// An iterator type that wraps a sync sequence iterator.
621-
@usableFromInline struct Iterator: AsyncIteratorProtocol {
622-
623-
/// The byte chunk element type.
624-
@usableFromInline typealias Element = ByteChunk
625-
626-
/// The underlying sync sequence iterator.
627-
var iterator: any IteratorProtocol<Element>
628-
629-
@usableFromInline mutating func next() async throws -> HTTPBody.ByteChunk? { iterator.next() }
630-
}
631-
632-
/// The underlying sync sequence.
633-
@usableFromInline let sequence: Bytes
634-
635-
/// Creates a new async sequence with the provided sync sequence.
636-
/// - Parameter sequence: The sync sequence to wrap.
637-
@inlinable init(sequence: Bytes) { self.sequence = sequence }
638-
639-
@usableFromInline func makeAsyncIterator() -> Iterator { Iterator(iterator: sequence.makeIterator()) }
640-
}
641-
642-
/// An empty async sequence.
643-
@usableFromInline struct EmptySequence: AsyncSequence, Sendable {
644-
645-
/// The type of the empty iterator.
646-
@usableFromInline typealias AsyncIterator = EmptyIterator
647-
648-
/// The byte chunk element type.
649-
@usableFromInline typealias Element = ByteChunk
650-
651-
/// An async iterator of an empty sequence.
652-
@usableFromInline struct EmptyIterator: AsyncIteratorProtocol {
653-
654-
/// The byte chunk element type.
655-
@usableFromInline typealias Element = ByteChunk
656-
657-
@usableFromInline mutating func next() async throws -> HTTPBody.ByteChunk? { nil }
658-
}
659-
660-
/// Creates a new empty async sequence.
661-
@inlinable init() {}
662-
663-
@usableFromInline func makeAsyncIterator() -> EmptyIterator { EmptyIterator() }
664-
}
665-
}

0 commit comments

Comments
 (0)