Skip to content
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
1 change: 1 addition & 0 deletions bin/configs/swift6-validation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ templateDir: modules/openapi-generator/src/main/resources/swift6
generateAliasAsModel: true
additionalProperties:
useSPMFileStructure: false
nonisolatedModels: true
podAuthors: ""
podSummary: PetstoreClient
projectName: PetstoreClient
Expand Down
1 change: 1 addition & 0 deletions docs/generators/swift6.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|library|Library template (sub-template) to use|<dl><dt>**urlsession**</dt><dd>[DEFAULT] HTTP client: URLSession</dd><dt>**alamofire**</dt><dd>HTTP client: Alamofire</dd><dt>**vapor**</dt><dd>HTTP client: Vapor</dd></dl>|urlsession|
|mapFileBinaryToData|Map File and Binary to Data (default: false)| |false|
|nonPublicApi|Generates code with reduced access modifiers; allows embedding elsewhere without exposing non-public API calls to consumers.(default: false)| |null|
|nonisolatedModels|Mark generated models, and the supporting protocols, extensions and helper types they rely on, as nonisolated so they stay usable off the main actor in modules built with default MainActor isolation (SE-0466). Requires a Swift 6.1+ toolchain (default: false)| |false|
|objcCompatible|Add additional properties and methods for Objective-C compatibility (default: false)| |null|
|oneOfUnknownDefaultCase|Add unknownDefault case to oneOf enum (default: false)| |false|
|podAuthors|Authors used for Podspec| |null|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public class Swift6ClientCodegen extends DefaultCodegen implements CodegenConfig
public static final String GENERATE_MODEL_ADDITIONAL_PROPERTIES = "generateModelAdditionalProperties";
public static final String HASHABLE_MODELS = "hashableModels";
public static final String IDENTIFIABLE_MODELS = "identifiableModels";
public static final String NONISOLATED_MODELS = "nonisolatedModels";
public static final String USE_PARAMETER_CONVERTIBLE = "useParameterConvertible";
public static final String MAP_FILE_BINARY_TO_DATA = "mapFileBinaryToData";
public static final String USE_CUSTOM_DATE_WITHOUT_TIME = "useCustomDateWithoutTime";
Expand Down Expand Up @@ -125,6 +126,8 @@ public class Swift6ClientCodegen extends DefaultCodegen implements CodegenConfig
@Setter
protected boolean identifiableModels = true;
@Setter
protected boolean nonisolatedModels = false;
@Setter
protected boolean useParameterConvertible = true;
@Getter
@Setter
Expand Down Expand Up @@ -379,6 +382,12 @@ public Swift6ClientCodegen() {
"Make models conform to Identifiable when an id is present (default: true)")
.defaultValue(Boolean.TRUE.toString()));

cliOptions.add(new CliOption(NONISOLATED_MODELS,
"Mark generated models, and the supporting protocols, extensions and helper types they rely on, "
+ "as nonisolated so they stay usable off the main actor in modules built with default MainActor isolation (SE-0466). "
+ "Requires a Swift 6.1+ toolchain (default: false)")
.defaultValue(Boolean.FALSE.toString()));

cliOptions.add(new CliOption(USE_PARAMETER_CONVERTIBLE,
"Make models conform to ParameterConvertible protocol (default: true)")
.defaultValue(Boolean.TRUE.toString()));
Expand Down Expand Up @@ -619,6 +628,11 @@ public void processOpts() {
}
additionalProperties.put(IDENTIFIABLE_MODELS, identifiableModels);

if (additionalProperties.containsKey(NONISOLATED_MODELS)) {
setNonisolatedModels(convertPropertyToBooleanAndWriteBack(NONISOLATED_MODELS));
}
additionalProperties.put(NONISOLATED_MODELS, nonisolatedModels);

if (additionalProperties.containsKey(USE_PARAMETER_CONVERTIBLE)) {
setUseParameterConvertible(convertPropertyToBooleanAndWriteBack(USE_PARAMETER_CONVERTIBLE));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class CodableHelper: @unchecked Sendable {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} {{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}class CodableHelper: @unchecked Sendable {

// MARK: - Private state

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,73 +11,73 @@ import FoundationNetworking
@preconcurrency import PromiseKit{{/usePromiseKit}}{{#useVapor}}
import Vapor{{/useVapor}}{{^useVapor}}

extension Bool: ParameterConvertible {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension Bool: ParameterConvertible {
func asParameter(codableHelper: CodableHelper) -> any Sendable { self }
}

extension Float: ParameterConvertible {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension Float: ParameterConvertible {
func asParameter(codableHelper: CodableHelper) -> any Sendable { self }
}

extension Int: ParameterConvertible {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension Int: ParameterConvertible {
func asParameter(codableHelper: CodableHelper) -> any Sendable { self }
}

extension Int32: ParameterConvertible {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension Int32: ParameterConvertible {
func asParameter(codableHelper: CodableHelper) -> any Sendable { self }
}

extension Int64: ParameterConvertible {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension Int64: ParameterConvertible {
func asParameter(codableHelper: CodableHelper) -> any Sendable { self }
}

extension Double: ParameterConvertible {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension Double: ParameterConvertible {
func asParameter(codableHelper: CodableHelper) -> any Sendable { self }
}

extension Decimal: ParameterConvertible {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension Decimal: ParameterConvertible {
func asParameter(codableHelper: CodableHelper) -> any Sendable { self }
}

extension String: ParameterConvertible {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension String: ParameterConvertible {
func asParameter(codableHelper: CodableHelper) -> any Sendable { self }
}

extension URL: ParameterConvertible {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension URL: ParameterConvertible {
func asParameter(codableHelper: CodableHelper) -> any Sendable { self }
}

extension UUID: ParameterConvertible {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension UUID: ParameterConvertible {
func asParameter(codableHelper: CodableHelper) -> any Sendable { self }
}

extension RawRepresentable where RawValue: ParameterConvertible {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension RawRepresentable where RawValue: ParameterConvertible {
func asParameter(codableHelper: CodableHelper) -> any Sendable {
rawValue.asParameter(codableHelper: codableHelper)
}
}

private func encodeIfPossible<T: Sendable>(_ object: T, codableHelper: CodableHelper) -> any Sendable {
private {{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}func encodeIfPossible<T: Sendable>(_ object: T, codableHelper: CodableHelper) -> any Sendable {
if let encodableObject = object as? ParameterConvertible {
return encodableObject.asParameter(codableHelper: codableHelper)
} else {
return object
}
}

extension Array where Element: Sendable {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension Array where Element: Sendable {
func asParameter(codableHelper: CodableHelper) -> any Sendable {
return self.map { encodeIfPossible($0, codableHelper: codableHelper) }
}
}

extension Set where Element: Sendable {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension Set where Element: Sendable {
func asParameter(codableHelper: CodableHelper) -> any Sendable {
return Array(self).asParameter(codableHelper: codableHelper)
}
}

extension Dictionary where Key: Sendable, Value: Sendable {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension Dictionary where Key: Sendable, Value: Sendable {
func asParameter(codableHelper: CodableHelper) -> any Sendable {
var dictionary = [Key: any Sendable]()
for (key, value) in self {
Expand All @@ -87,19 +87,19 @@ extension Dictionary where Key: Sendable, Value: Sendable {
}
}

extension Data: ParameterConvertible {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension Data: ParameterConvertible {
func asParameter(codableHelper: CodableHelper) -> any Sendable {
return self.base64EncodedString(options: Data.Base64EncodingOptions())
}
}

extension Date: ParameterConvertible {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension Date: ParameterConvertible {
func asParameter(codableHelper: CodableHelper) -> any Sendable {
return codableHelper.dateFormatter.string(from: self)
}
}{{/useVapor}}{{#generateModelAdditionalProperties}}

extension String: @retroactive CodingKey {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension String: @retroactive CodingKey {

public var stringValue: String {
return self
Expand All @@ -119,7 +119,7 @@ extension String: @retroactive CodingKey {

}

extension KeyedEncodingContainerProtocol {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension KeyedEncodingContainerProtocol {

{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeArray<T>(_ values: [T], forKey key: Self.Key) throws where T: Encodable {
var arrayContainer = nestedUnkeyedContainer(forKey: key)
Expand Down Expand Up @@ -161,7 +161,7 @@ extension KeyedEncodingContainerProtocol {
}
}

extension KeyedDecodingContainerProtocol {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension KeyedDecodingContainerProtocol {

{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func decodeArray<T>(_ type: T.Type, forKey key: Self.Key) throws -> [T] where T: Decodable {
var tmpArray = [T]()
Expand Down Expand Up @@ -281,4 +281,4 @@ extension Set: @retroactive AsyncRequestDecodable where Element: Content {

extension Set: @retroactive Content where Element: Content { }

extension JSONValue: Content {}{{/useVapor}}
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension JSONValue: Content {}{{/useVapor}}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum JSONValue: Sendable, Codable, Hashable {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} {{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}enum JSONValue: Sendable, Codable, Hashable {
case string(String)
case int(Int)
case double(Double)
Expand Down Expand Up @@ -62,7 +62,7 @@ import Foundation
}
}

extension JSONValue {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension JSONValue {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(_ value: String) {
self = .string(value)
}
Expand Down Expand Up @@ -97,7 +97,7 @@ extension JSONValue {
}
}

extension JSONValue {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension JSONValue {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var isString: Bool {
if case .string = self { return true }
return false
Expand Down Expand Up @@ -134,7 +134,7 @@ extension JSONValue {

}

extension JSONValue {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension JSONValue {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var stringValue: String? {
switch self {
case .string(let value):
Expand Down Expand Up @@ -186,7 +186,7 @@ extension JSONValue {
}
}

extension JSONValue {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension JSONValue {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} subscript(key: String) -> JSONValue? {
return dictionaryValue?[key]
}
Expand All @@ -199,38 +199,38 @@ extension JSONValue {
}
}

extension JSONValue: ExpressibleByStringLiteral, ExpressibleByStringInterpolation {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension JSONValue: ExpressibleByStringLiteral, ExpressibleByStringInterpolation {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(stringLiteral value: StringLiteralType) {
self = .string(value)
}
}

extension JSONValue: ExpressibleByIntegerLiteral {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension JSONValue: ExpressibleByIntegerLiteral {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(integerLiteral value: IntegerLiteralType) {
self = .int(value)
}
}

extension JSONValue: ExpressibleByFloatLiteral {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension JSONValue: ExpressibleByFloatLiteral {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(floatLiteral value: FloatLiteralType) {
self = .double(value)
}
}


extension JSONValue: ExpressibleByBooleanLiteral {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension JSONValue: ExpressibleByBooleanLiteral {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(booleanLiteral value: BooleanLiteralType) {
self = .bool(value)
}
}

extension JSONValue: ExpressibleByArrayLiteral {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension JSONValue: ExpressibleByArrayLiteral {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(arrayLiteral elements: JSONValue...) {
self = .array(elements)
}
}

extension JSONValue: ExpressibleByDictionaryLiteral {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension JSONValue: ExpressibleByDictionaryLiteral {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(dictionaryLiteral elements: (String, JSONValue)...) {
var dict: [String: JSONValue] = [:]
for (key, value) in elements {
Expand All @@ -240,7 +240,7 @@ extension JSONValue: ExpressibleByDictionaryLiteral {
}
}

extension JSONValue: ExpressibleByNilLiteral {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension JSONValue: ExpressibleByNilLiteral {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(nilLiteral: ()) {
self = .null
}
Expand Down
20 changes: 10 additions & 10 deletions modules/openapi-generator/src/main/resources/swift6/Models.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import FoundationNetworking
#endif{{#useAlamofire}}
import Alamofire{{/useAlamofire}}

protocol ParameterConvertible {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}protocol ParameterConvertible {
func asParameter(codableHelper: CodableHelper) -> any Sendable
}

/// An enum where the last case value can be used as a default catch-all.
protocol CaseIterableDefaultsLast: Decodable & CaseIterable & RawRepresentable
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}protocol CaseIterableDefaultsLast: Decodable & CaseIterable & RawRepresentable
where RawValue: Decodable, AllCases: BidirectionalCollection {}

extension CaseIterableDefaultsLast {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension CaseIterableDefaultsLast {
/// Initializes an enum such that if a known raw value is found, then it is decoded.
/// Otherwise the last case is used.
/// - Parameter decoder: A decoder.
Expand All @@ -40,28 +40,28 @@ extension CaseIterableDefaultsLast {
/// Protocol for types used as oneOf variants, allowing the oneOf decoder to reject
/// a variant that only decoded successfully because CaseIterableDefaultsLast
/// silently accepted an unknown enum value.
protocol UnknownCaseCheckable {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}protocol UnknownCaseCheckable {
var containsUnknownDefaultOpenApiCase: Bool { get }
}

extension UnknownCaseCheckable {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension UnknownCaseCheckable {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var containsUnknownDefaultOpenApiCase: Bool { false }
}

{{/enumUnknownDefaultCase}}
/// A flexible type that can be encoded (`.encodeNull` or `.encodeValue`)
/// or not encoded (`.encodeNothing`). Intended for request payloads.
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum NullEncodable<Wrapped> {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} {{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}enum NullEncodable<Wrapped> {
case encodeNothing
case encodeNull
case encodeValue(Wrapped)
}

extension NullEncodable: Equatable where Wrapped: Equatable {}
extension NullEncodable: Hashable where Wrapped: Hashable {}
extension NullEncodable: Sendable where Wrapped: Sendable {}
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension NullEncodable: Equatable where Wrapped: Equatable {}
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension NullEncodable: Hashable where Wrapped: Hashable {}
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension NullEncodable: Sendable where Wrapped: Sendable {}

extension NullEncodable: Codable where Wrapped: Codable {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension NullEncodable: Codable where Wrapped: Codable {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let value = try? container.decode(Wrapped.self) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import Foundation
/// an appropriate padding in order to transform to GMT+0 which is the assumed timezone in ISO 8601.
/// When decoding, GMT+0 can be assumed (again: ISO8601) so there is no padding necessary and wrappedDate
/// can be used safely.
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct OpenAPIDateWithoutTime: Sendable, Codable, Hashable, Equatable {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} {{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}struct OpenAPIDateWithoutTime: Sendable, Codable, Hashable, Equatable {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let wrappedDate: Date
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let timezone: TimeZone

Expand Down Expand Up @@ -76,13 +76,13 @@ import Foundation
}
}

extension OpenAPIDateWithoutTime: ParameterConvertible {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension OpenAPIDateWithoutTime: ParameterConvertible {
func asParameter(codableHelper: CodableHelper) -> any Sendable {
return OpenISO8601DateFormatter.withoutTime.string(from: self.normalizedWrappedDate())
}
}

extension OpenAPIDateWithoutTime: RawRepresentable {
{{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}extension OpenAPIDateWithoutTime: RawRepresentable {
public typealias RawValue = String
public init?(rawValue: String) {
if let date = OpenISO8601DateFormatter.withoutTime.date(from: rawValue) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import Foundation

internal final class OpenAPIMutex<Value>: @unchecked Sendable {
internal {{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}final class OpenAPIMutex<Value>: @unchecked Sendable {

private var _value: Value
private let lock = NSRecursiveLock()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation

// https://stackoverflow.com/a/50281094/976628
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} class OpenISO8601DateFormatter: DateFormatter, @unchecked Sendable {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} {{#nonisolatedModels}}nonisolated {{/nonisolatedModels}}class OpenISO8601DateFormatter: DateFormatter, @unchecked Sendable {
static let withoutSeconds: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
Expand Down
Loading
Loading