Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
96ecc2b
WIP on recursive type support
czechboy0 Oct 13, 2023
1d3ca43
WIP
czechboy0 Oct 13, 2023
4a77a62
Formatting
czechboy0 Oct 13, 2023
9d05a58
WIP
czechboy0 Oct 16, 2023
e8685d9
WIP
czechboy0 Oct 16, 2023
374f3c0
Make response type rendering also more type-safe.
czechboy0 Oct 17, 2023
458bddc
More formatting
czechboy0 Oct 17, 2023
3365f3b
Tests passing
czechboy0 Oct 18, 2023
d85e4dd
Also test nested references
czechboy0 Oct 18, 2023
efbdd1b
wip
czechboy0 Oct 18, 2023
3366536
Tests passing for all types that need recursion.
czechboy0 Oct 18, 2023
158dbf4
Add more conceptual documentation for maintainers about how recursion…
czechboy0 Oct 18, 2023
7526219
Add a utility factory method for .member
czechboy0 Oct 18, 2023
fd1f7a6
Use the new utility in more places
czechboy0 Oct 18, 2023
93809ac
Remove a deprecated computed property
czechboy0 Oct 18, 2023
fff0323
Added more doc comments
czechboy0 Oct 18, 2023
d96a037
Add more documentation
czechboy0 Oct 18, 2023
5ff0e68
More comments
czechboy0 Oct 18, 2023
ad12ea1
Remove an obsolete file.
czechboy0 Oct 18, 2023
71f74aa
Add more snippet tests
czechboy0 Oct 18, 2023
e48c013
Merge branch 'main' into hd-recursive-types-support
czechboy0 Oct 18, 2023
624427f
Improve structured Swift representation for recursive type support
czechboy0 Oct 18, 2023
c748597
Rename variable to pattern
czechboy0 Oct 18, 2023
3656b23
PR feedback, use the _modify accessor instead of set
czechboy0 Oct 19, 2023
14b9ec9
Add _modify and yield support
czechboy0 Oct 19, 2023
166e048
Merge branch 'hd-recursive-prep' into hd-recursive-types-support
czechboy0 Oct 19, 2023
f028ac0
Update docs
czechboy0 Oct 19, 2023
da75322
Merge branch 'main' into hd-recursive-types-support
czechboy0 Oct 19, 2023
ceb51fa
Move the reference stack into its own file
czechboy0 Oct 19, 2023
57782e2
Bump the runtime dependency to 0.3.3
czechboy0 Oct 19, 2023
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
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ let package = Package(
// Tests-only: Runtime library linked by generated code, and also
// helps keep the runtime library new enough to work with the generated
// code.
.package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.3.2")),
.package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.3.3")),

// Build and preview docs
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
Expand Down
24 changes: 0 additions & 24 deletions Sources/_OpenAPIGeneratorCore/Parser/validateDoc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,29 +44,5 @@ func validateDoc(_ doc: ParsedOpenAPIRepresentation, config: Config) throws -> [
]
)
}

// Validate that the document is dereferenceable, which
// catches reference cycles, which we don't yet support.
_ = try doc.locallyDereferenced()

// Also explicitly dereference the parts of components
// that the generator uses. `locallyDereferenced()` above
// only dereferences paths/operations, but not components.
let components = doc.components
try components.schemas.forEach { schema in
_ = try schema.value.dereferenced(in: components)
}
try components.parameters.forEach { schema in
_ = try schema.value.dereferenced(in: components)
}
try components.headers.forEach { schema in
_ = try schema.value.dereferenced(in: components)
}
try components.requestBodies.forEach { schema in
_ = try schema.value.dereferenced(in: components)
}
try components.responses.forEach { schema in
_ = try schema.value.dereferenced(in: components)
}
return diagnostics
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,10 @@ extension FileTranslator {
associatedDeclarations: associatedDeclarations,
asSwiftSafeName: swiftSafeName
)
var referenceStack = ReferenceStack.empty
let isKeyValuePairSchema = try TypeMatcher.isKeyValuePair(
schema,
referenceStack: &referenceStack,
components: components
)
return (blueprint, isKeyValuePairSchema)
Expand Down Expand Up @@ -196,8 +198,10 @@ extension FileTranslator {
} else {
associatedDeclarations = []
}
var referenceStack = ReferenceStack.empty
let isKeyValuePair = try TypeMatcher.isKeyValuePair(
schema,
referenceStack: &referenceStack,
components: components
)
return (caseName, nil, isKeyValuePair, comment, childType, associatedDeclarations)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,12 @@ enum Constants {

/// The name of the namespace.
static let namespace: String = "Schemas"

/// The full namespace components.
static let components: [String] = [
Constants.Components.namespace,
Constants.Components.Schemas.namespace,
]
}

/// Constants related to the Parameters namespace.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

/// A set of specialized types for using the recursion detector for
/// declarations.
struct DeclarationRecursionDetector {

/// A node for a pair of a Swift type name and a corresponding declaration.
struct Node: TypeNode, Equatable {

/// The type of the name is a string.
typealias NameType = String

/// The name of the node.
var name: NameType

/// Whether the type can be boxed.
var isBoxable: Bool

/// The names of nodes pointed to by this node.
var edges: [NameType]

/// The declaration represented by this node.
var decl: Declaration

/// Creates a new node.
/// - Parameters:
/// - name: The name of the node.
/// - isBoxable: Whether the type can be boxed.
/// - edges: The names of nodes pointed to by this node.
/// - decl: The declaration represented by this node.
private init(name: NameType, isBoxable: Bool, edges: [NameType], decl: Declaration) {
self.name = name
self.isBoxable = isBoxable
self.edges = edges
self.decl = decl
}

/// Creates a new node from the provided declaration.
///
/// Returns nil when the declaration is missing a name.
/// - Parameter decl: A declaration.
init?(_ decl: Declaration) {
guard let name = decl.name else {
return nil
}
let edges = decl.schemaComponentNamesOfUnbreakableReferences
self.init(
name: name,
isBoxable: decl.isBoxable,
edges: edges,
decl: decl
)
}
}

/// A container for declarations.
struct Container: TypeNodeContainer {

/// The type of the node.
typealias Node = DeclarationRecursionDetector.Node

/// An error thrown by the container.
enum ContainerError: Swift.Error {

/// The node for the provided name was not found.
case nodeNotFound(Node.NameType)
}

/// The lookup map from the name to the node.
var lookupMap: [String: Node]

func lookup(_ name: String) throws -> DeclarationRecursionDetector.Node {
guard let node = lookupMap[name] else {
throw ContainerError.nodeNotFound(name)
}
return node
}
}
}

extension Declaration {

/// A name of the declaration, if it has one.
var name: String? {
switch self {
case .struct(let desc):
return desc.name
case .enum(let desc):
return desc.name
case .typealias(let desc):
return desc.name
case .commentable(_, let decl), .deprecated(_, let decl):
return decl.name
case .variable, .extension, .protocol, .function, .enumCase:
return nil
}
}

/// A Boolean value representing whether this declaration can be boxed.
var isBoxable: Bool {
switch self {
case .struct, .enum:
return true
case .commentable(_, let decl), .deprecated(_, let decl):
return decl.isBoxable
case .typealias, .variable, .extension, .protocol, .function, .enumCase:
return false
}
}

/// An array of names that can be found in `#/components/schemas` in
/// the OpenAPI document that represent references that can cause
/// a reference cycle.
var schemaComponentNamesOfUnbreakableReferences: [String] {
switch self {
case .struct(let desc):
return desc
.members
.compactMap { (member) -> [String]? in
switch member.strippingTopComment {
case .variable, // A reference to a reusable type.
.struct, .enum: // An inline type.
return member.schemaComponentNamesOfUnbreakableReferences
default:
return nil
}
}
.flatMap { $0 }
case .enum(let desc):
return desc
.members
.compactMap { (member) -> [String]? in
guard case .enumCase = member.strippingTopComment else {
return nil
}
return member
.schemaComponentNamesOfUnbreakableReferences
}
.flatMap { $0 }
case .commentable(_, let decl), .deprecated(_, let decl):
return decl
.schemaComponentNamesOfUnbreakableReferences
case .typealias(let desc):
return desc
.existingType
.referencedSchemaComponentName
.map { [$0] } ?? []
case .variable(let desc):
return desc.type?.referencedSchemaComponentName.map { [$0] } ?? []
case .enumCase(let desc):
switch desc.kind {
case .nameWithAssociatedValues(let values):
return values.compactMap { $0.type.referencedSchemaComponentName }
default:
return []
}
case .extension, .protocol, .function:
return []
}
}
}

fileprivate extension Array where Element == String {

/// The name in the `Components.Schemas.` namespace.
var nameIfTopLevelSchemaComponent: String? {
let components = self
guard
components.count == 3,
components.starts(with: Constants.Components.Schemas.components)
else {
return nil
}
return components[2]
}
}

extension ExistingTypeDescription {

/// The name in the `Components.Schemas.` namespace, if the type can appear
/// there. Nil otherwise.
var referencedSchemaComponentName: String? {
switch self {
case .member(let components):
return components.nameIfTopLevelSchemaComponent
case .array(let desc), .dictionaryValue(let desc), .any(let desc), .optional(let desc):
return desc.referencedSchemaComponentName
case .generic:
return nil
}
}
}
Loading