Skip to content
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

[Multipart] Add public types #77

Merged
merged 10 commits into from
Nov 24, 2023
4 changes: 2 additions & 2 deletions Sources/OpenAPIRuntime/Base/CopyOnWriteBox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@

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

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

/// Creates a new box.
/// - Parameter value: The value to store in the box.
@inlinable public init(value: Wrapped) { self.storage = .init(value: value) }
public init(value: Wrapped) { self.storage = .init(value: value) }
czechboy0 marked this conversation as resolved.
Show resolved Hide resolved

/// The stored value whose accessors enforce copy-on-write semantics.
@inlinable public var value: Wrapped {
Expand Down
15 changes: 13 additions & 2 deletions Sources/OpenAPIRuntime/Conversion/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,20 @@ public struct Configuration: Sendable {
/// The transcoder used when converting between date and string values.
public var dateTranscoder: any DateTranscoder

/// The generator to use when creating mutlipart bodies.
public var multipartBoundaryGenerator: any MultipartBoundaryGenerator

/// Creates a new configuration with the specified values.
///
/// - Parameter dateTranscoder: The transcoder to use when converting between date
/// - Parameters:
/// - dateTranscoder: The transcoder to use when converting between date
/// and string values.
public init(dateTranscoder: any DateTranscoder = .iso8601) { self.dateTranscoder = dateTranscoder }
/// - multipartBoundaryGenerator: The generator to use when creating mutlipart bodies.
public init(
dateTranscoder: any DateTranscoder = .iso8601,
multipartBoundaryGenerator: any MultipartBoundaryGenerator = .random
) {
self.dateTranscoder = dateTranscoder
self.multipartBoundaryGenerator = multipartBoundaryGenerator
}
}
11 changes: 11 additions & 0 deletions Sources/OpenAPIRuntime/Deprecated/Deprecated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,14 @@ extension DecodingError {
}
}
}

extension Configuration {
/// Creates a new configuration with the specified values.
///
/// - Parameter dateTranscoder: The transcoder to use when converting between date
/// and string values.
@available(*, deprecated, renamed: "init(dateTranscoder:multipartBoundaryGenerator:)") @_disfavoredOverload
public init(dateTranscoder: any DateTranscoder) {
self.init(dateTranscoder: dateTranscoder, multipartBoundaryGenerator: .random)
}
}
120 changes: 120 additions & 0 deletions Sources/OpenAPIRuntime/Interface/AsyncSequenceCommon.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftOpenAPIGenerator open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

czechboy0 marked this conversation as resolved.
Show resolved Hide resolved
/// Describes how many times the provided sequence can be iterated.
public enum IterationBehavior: Sendable {

/// The input sequence can only be iterated once.
///
/// If a retry or a redirect is encountered, fail the call with
/// a descriptive error.
case single

/// The input sequence can be iterated multiple times.
///
/// Supports retries and redirects, as a new iterator is created each
/// time.
case multiple
}

// MARK: - Internal

/// A type-erasing closure-based iterator.
@usableFromInline struct AnyIterator<Element: Sendable>: AsyncIteratorProtocol {

/// The closure that produces the next element.
private let produceNext: () async throws -> Element?

/// Creates a new type-erased iterator from the provided iterator.
/// - Parameter iterator: The iterator to type-erase.
@usableFromInline init<Iterator: AsyncIteratorProtocol>(_ iterator: Iterator) where Iterator.Element == Element {
var iterator = iterator
self.produceNext = { try await iterator.next() }
}

/// Advances the iterator to the next element and returns it asynchronously.
///
/// - Returns: The next element in the sequence, or `nil` if there are no more elements.
/// - Throws: An error if there is an issue advancing the iterator or retrieving the next element.
public mutating func next() async throws -> Element? { try await produceNext() }
}

/// A type-erased async sequence that wraps input sequences.
@usableFromInline struct AnySequence<Element: Sendable>: AsyncSequence, Sendable {

/// The type of the type-erased iterator.
@usableFromInline typealias AsyncIterator = AnyIterator<Element>

/// A closure that produces a new iterator.
@usableFromInline let produceIterator: @Sendable () -> AsyncIterator

/// Creates a new sequence.
/// - Parameter sequence: The input sequence to type-erase.
@usableFromInline init<Upstream: AsyncSequence>(_ sequence: Upstream)
where Upstream.Element == Element, Upstream: Sendable {
self.produceIterator = { .init(sequence.makeAsyncIterator()) }
}

@usableFromInline func makeAsyncIterator() -> AsyncIterator { produceIterator() }
}

/// An async sequence wrapper for a sync sequence.
@usableFromInline struct WrappedSyncSequence<Upstream: Sequence & Sendable>: AsyncSequence, Sendable
where Upstream.Element: Sendable {

/// The type of the iterator.
@usableFromInline typealias AsyncIterator = Iterator<Element>

/// The element type.
@usableFromInline typealias Element = Upstream.Element

/// An iterator type that wraps a sync sequence iterator.
@usableFromInline struct Iterator<IteratorElement: Sendable>: AsyncIteratorProtocol {

/// The element type.
@usableFromInline typealias Element = IteratorElement

/// The underlying sync sequence iterator.
var iterator: any IteratorProtocol<Element>

@usableFromInline mutating func next() async throws -> IteratorElement? { iterator.next() }
}

/// The underlying sync sequence.
@usableFromInline let sequence: Upstream

/// Creates a new async sequence with the provided sync sequence.
/// - Parameter sequence: The sync sequence to wrap.
@usableFromInline init(sequence: Upstream) { self.sequence = sequence }

@usableFromInline func makeAsyncIterator() -> AsyncIterator { Iterator(iterator: sequence.makeIterator()) }
}

/// An empty async sequence.
@usableFromInline struct EmptySequence<Element: Sendable>: AsyncSequence, Sendable {

/// The type of the empty iterator.
@usableFromInline typealias AsyncIterator = EmptyIterator<Element>

/// An async iterator of an empty sequence.
@usableFromInline struct EmptyIterator<IteratorElement: Sendable>: AsyncIteratorProtocol {

@usableFromInline mutating func next() async throws -> IteratorElement? { nil }
}

/// Creates a new empty async sequence.
@usableFromInline init() {}

@usableFromInline func makeAsyncIterator() -> AsyncIterator { EmptyIterator() }
}
126 changes: 21 additions & 105 deletions Sources/OpenAPIRuntime/Interface/HTTPBody.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,24 +122,16 @@ public final class HTTPBody: @unchecked Sendable {
public typealias ByteChunk = ArraySlice<UInt8>

/// Describes how many times the provided sequence can be iterated.
public enum IterationBehavior: Sendable {

/// The input sequence can only be iterated once.
///
/// If a retry or a redirect is encountered, fail the call with
/// a descriptive error.
case single

/// The input sequence can be iterated multiple times.
///
/// Supports retries and redirects, as a new iterator is created each
/// time.
case multiple
}

/// The body's iteration behavior, which controls how many times
@available(
*,
deprecated,
renamed: "IterationBehavior",
message: "Use the top level IterationBehavior directly instead of HTTPBody.IterationBehavior."
) public typealias IterationBehavior = OpenAPIRuntime.IterationBehavior
czechboy0 marked this conversation as resolved.
Show resolved Hide resolved

/// The iteration behavior, which controls how many times
/// the input sequence can be iterated.
public let iterationBehavior: IterationBehavior
public let iterationBehavior: OpenAPIRuntime.IterationBehavior

/// Describes the total length of the body, if known.
public enum Length: Sendable, Equatable {
Expand All @@ -155,7 +147,7 @@ public final class HTTPBody: @unchecked Sendable {
public let length: Length

/// The underlying type-erased async sequence.
private let sequence: BodySequence
private let sequence: AnySequence<ByteChunk>

/// A lock for shared mutable state.
private let lock: NSLock = {
Expand Down Expand Up @@ -205,7 +197,11 @@ public final class HTTPBody: @unchecked Sendable {
/// length of all the byte chunks.
/// - iterationBehavior: The sequence's iteration behavior, which
/// indicates whether the sequence can be iterated multiple times.
@usableFromInline init(_ sequence: BodySequence, length: Length, iterationBehavior: IterationBehavior) {
@usableFromInline init(
_ sequence: AnySequence<ByteChunk>,
length: Length,
iterationBehavior: OpenAPIRuntime.IterationBehavior
) {
self.sequence = sequence
self.length = length
self.iterationBehavior = iterationBehavior
Expand All @@ -220,7 +216,7 @@ public final class HTTPBody: @unchecked Sendable {
@usableFromInline convenience init(
_ byteChunks: some Sequence<ByteChunk> & Sendable,
length: Length,
iterationBehavior: IterationBehavior
iterationBehavior: OpenAPIRuntime.IterationBehavior
) {
self.init(
.init(WrappedSyncSequence(sequence: byteChunks)),
Expand Down Expand Up @@ -281,7 +277,7 @@ extension HTTPBody {
@inlinable public convenience init(
_ bytes: some Sequence<UInt8> & Sendable,
length: Length,
iterationBehavior: IterationBehavior
iterationBehavior: OpenAPIRuntime.IterationBehavior
) { self.init([ArraySlice(bytes)], length: length, iterationBehavior: iterationBehavior) }

/// Creates a new body with the provided byte collection.
Expand Down Expand Up @@ -323,7 +319,7 @@ extension HTTPBody {
@inlinable public convenience init<Bytes: AsyncSequence>(
_ sequence: Bytes,
length: HTTPBody.Length,
iterationBehavior: IterationBehavior
iterationBehavior: OpenAPIRuntime.IterationBehavior
) where Bytes.Element == ByteChunk, Bytes: Sendable {
self.init(.init(sequence), length: length, iterationBehavior: iterationBehavior)
}
Expand All @@ -337,7 +333,7 @@ extension HTTPBody {
@inlinable public convenience init<Bytes: AsyncSequence>(
_ sequence: Bytes,
length: HTTPBody.Length,
iterationBehavior: IterationBehavior
iterationBehavior: OpenAPIRuntime.IterationBehavior
) where Bytes: Sendable, Bytes.Element: Sequence & Sendable, Bytes.Element.Element == UInt8 {
self.init(sequence.map { ArraySlice($0) }, length: length, iterationBehavior: iterationBehavior)
}
Expand All @@ -356,7 +352,7 @@ extension HTTPBody: AsyncSequence {
public func makeAsyncIterator() -> AsyncIterator {
// The crash on error is intentional here.
try! tryToMarkIteratorCreated()
return sequence.makeAsyncIterator()
return .init(sequence.makeAsyncIterator())
}
}

Expand Down Expand Up @@ -482,7 +478,7 @@ extension HTTPBody {
@inlinable public convenience init<Strings: AsyncSequence>(
_ sequence: Strings,
length: HTTPBody.Length,
iterationBehavior: IterationBehavior
iterationBehavior: OpenAPIRuntime.IterationBehavior
) where Strings.Element: StringProtocol & Sendable, Strings: Sendable {
self.init(.init(sequence.map { ByteChunk.init($0) }), length: length, iterationBehavior: iterationBehavior)
}
Expand Down Expand Up @@ -583,83 +579,3 @@ extension HTTPBody {
public mutating func next() async throws -> Element? { try await produceNext() }
}
}

extension HTTPBody {

/// A type-erased async sequence that wraps input sequences.
@usableFromInline struct BodySequence: AsyncSequence, Sendable {

/// The type of the type-erased iterator.
@usableFromInline typealias AsyncIterator = HTTPBody.Iterator

/// The byte chunk element type.
@usableFromInline typealias Element = ByteChunk

/// A closure that produces a new iterator.
@usableFromInline let produceIterator: @Sendable () -> AsyncIterator

/// Creates a new sequence.
/// - Parameter sequence: The input sequence to type-erase.
@inlinable init<Bytes: AsyncSequence>(_ sequence: Bytes) where Bytes.Element == Element, Bytes: Sendable {
self.produceIterator = { .init(sequence.makeAsyncIterator()) }
}

@usableFromInline func makeAsyncIterator() -> AsyncIterator { produceIterator() }
}

/// An async sequence wrapper for a sync sequence.
@usableFromInline struct WrappedSyncSequence<Bytes: Sequence>: AsyncSequence, Sendable
where Bytes.Element == ByteChunk, Bytes.Iterator.Element == ByteChunk, Bytes: Sendable {

/// The type of the iterator.
@usableFromInline typealias AsyncIterator = Iterator

/// The byte chunk element type.
@usableFromInline typealias Element = ByteChunk

/// An iterator type that wraps a sync sequence iterator.
@usableFromInline struct Iterator: AsyncIteratorProtocol {

/// The byte chunk element type.
@usableFromInline typealias Element = ByteChunk

/// The underlying sync sequence iterator.
var iterator: any IteratorProtocol<Element>

@usableFromInline mutating func next() async throws -> HTTPBody.ByteChunk? { iterator.next() }
}

/// The underlying sync sequence.
@usableFromInline let sequence: Bytes

/// Creates a new async sequence with the provided sync sequence.
/// - Parameter sequence: The sync sequence to wrap.
@inlinable init(sequence: Bytes) { self.sequence = sequence }

@usableFromInline func makeAsyncIterator() -> Iterator { Iterator(iterator: sequence.makeIterator()) }
}

/// An empty async sequence.
@usableFromInline struct EmptySequence: AsyncSequence, Sendable {

/// The type of the empty iterator.
@usableFromInline typealias AsyncIterator = EmptyIterator

/// The byte chunk element type.
@usableFromInline typealias Element = ByteChunk

/// An async iterator of an empty sequence.
@usableFromInline struct EmptyIterator: AsyncIteratorProtocol {

/// The byte chunk element type.
@usableFromInline typealias Element = ByteChunk

@usableFromInline mutating func next() async throws -> HTTPBody.ByteChunk? { nil }
}

/// Creates a new empty async sequence.
@inlinable init() {}

@usableFromInline func makeAsyncIterator() -> EmptyIterator { EmptyIterator() }
}
}
Loading