Skip to content

Commit

Permalink
[Refactor] Introduce TranslatorContext (1/2) (#638)
Browse files Browse the repository at this point in the history
### Motivation

To make upcoming PRs less noisy, we need a way to propagate
configuration all the way deep to types like TypeAssigner/TypeMatcher,
which historically didn't have that need.

### Modifications

Introduced a new wrapper type called TranslatorContext, which wraps the
existing closure that converts arbitrary strings into safe Swift names
(which might in the future also need to be further customized).

The idea is that this new struct is what we'd attach more config to
shortly.

There'll be a few more smaller PRs like this, I'm breaking them up for
easier review.

### Result

Generalized the way to propagate config into low level translator
utilities.

### Test Plan

All tests still pass.
  • Loading branch information
czechboy0 authored Oct 4, 2024
1 parent de51f3d commit 07ee940
Show file tree
Hide file tree
Showing 28 changed files with 92 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ struct ClientFileTranslator: FileTranslator {
let imports =
Constants.File.clientServerImports + config.additionalImports.map { ImportDescription(moduleName: $0) }

let clientMethodDecls =
try OperationDescription.all(from: doc.paths, in: components, asSwiftSafeName: swiftSafeName)
let clientMethodDecls = try OperationDescription.all(from: doc.paths, in: components, context: context)
.map(translateClientMethod(_:))

let clientStructPropertyDecl: Declaration = .commentable(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,7 @@
//===----------------------------------------------------------------------===//
import Foundation

extension FileTranslator {

/// Returns a copy of the string modified to be a valid Swift identifier.
///
/// - Parameter string: The string to convert to be safe for Swift.
/// - Returns: A Swift-safe version of the input string.
func swiftSafeName(for string: String) -> String { string.safeForSwiftCode }
}

fileprivate extension String {
extension String {

/// Returns a string sanitized to be usable as a Swift identifier.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ extension TypesFileTranslator {
originalName: key,
typeUsage: propertyType,
associatedDeclarations: associatedDeclarations,
asSwiftSafeName: swiftSafeName
context: context
)
var referenceStack = ReferenceStack.empty
let isKeyValuePairSchema = try TypeMatcher.isKeyValuePair(
Expand Down Expand Up @@ -209,7 +209,7 @@ extension TypesFileTranslator {
let decoder: Declaration
if let discriminator {
let originalName = discriminator.propertyName
let swiftName = swiftSafeName(for: originalName)
let swiftName = context.asSwiftSafeName(originalName)
codingKeysDecls = [
.enum(
accessModifier: config.access,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ extension TypesFileTranslator {
originalName: key,
typeUsage: propertyType,
associatedDeclarations: associatedDeclarations,
asSwiftSafeName: swiftSafeName
context: context
)
}

Expand Down Expand Up @@ -175,7 +175,7 @@ extension TypesFileTranslator {
default: .emptyInit,
isSerializedInTopLevelDictionary: false,
associatedDeclarations: associatedDeclarations,
asSwiftSafeName: swiftSafeName
context: context
)
return (.allowingAdditionalProperties, extraProperty)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ extension FileTranslator {
// In nullable enum schemas, empty strings are parsed as Void.
// This is unlikely to be fixed, so handling that case here.
// https://github.com/apple/swift-openapi-generator/issues/118
if isNullable && anyValue is Void { return (swiftSafeName(for: ""), .string("")) }
if isNullable && anyValue is Void { return (context.asSwiftSafeName(""), .string("")) }
guard let rawValue = anyValue as? String else {
throw GenericError(message: "Disallowed value for a string enum '\(typeName)': \(anyValue)")
}
let caseName = swiftSafeName(for: rawValue)
let caseName = context.asSwiftSafeName(rawValue)
return (caseName, .string(rawValue))
case .integer:
let rawValue: Int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ extension FileTranslator {
/// component.
/// - Parameter type: The `OneOfMappedType` for which to determine the case name.
/// - Returns: A string representing the safe Swift name for the specified `OneOfMappedType`.
func safeSwiftNameForOneOfMappedType(_ type: OneOfMappedType) -> String { swiftSafeName(for: type.rawNames[0]) }
func safeSwiftNameForOneOfMappedType(_ type: OneOfMappedType) -> String {
context.asSwiftSafeName(type.rawNames[0])
}
}

extension OpenAPI.Discriminator {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,14 @@ struct PropertyBlueprint {
/// referring to them in the property.
var associatedDeclarations: [Declaration] = []

/// A converted function from user-provided strings to strings
/// safe to be used as a Swift identifier.
var asSwiftSafeName: (String) -> String
/// A set of configuration values that inform translation.
var context: TranslatorContext
}

extension PropertyBlueprint {

/// A name that is verified to be a valid Swift identifier.
var swiftSafeName: String { asSwiftSafeName(originalName) }
var swiftSafeName: String { context.asSwiftSafeName(originalName) }

/// The JSON path to the property.
///
Expand Down
16 changes: 16 additions & 0 deletions Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,19 @@ protocol FileTranslator {
/// - Throws: An error if translation encounters issues or errors during the process.
func translateFile(parsedOpenAPI: ParsedOpenAPIRepresentation) throws -> StructuredSwiftRepresentation
}

extension FileTranslator {

/// A new context from the file translator.
var context: TranslatorContext { TranslatorContext(asSwiftSafeName: { $0.safeForSwiftCode }) }
}

/// A set of configuration values for concrete file translators.
struct TranslatorContext {

/// A closure that returns a copy of the string modified to be a valid Swift identifier.
///
/// - Parameter string: The string to convert to be safe for Swift.
/// - Returns: A Swift-safe version of the input string.
var asSwiftSafeName: (String) -> String
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ extension FileTranslator {
}
var parts: [MultipartSchemaTypedContent] = try topLevelObject.properties.compactMap {
(key, value) -> MultipartSchemaTypedContent? in
let swiftSafeName = swiftSafeName(for: key)
let swiftSafeName = context.asSwiftSafeName(key)
let typeName = typeName.appending(
swiftComponent: swiftSafeName + Constants.Global.inlineTypeSuffix,
jsonComponent: key
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ extension TypesFileTranslator {
typeUsage: headersTypeName.asUsage,
default: headersStructBlueprint.hasEmptyInit ? .emptyInit : nil,
associatedDeclarations: [headersStructDecl],
asSwiftSafeName: swiftSafeName
context: context
)
} else {
headersProperty = nil
Expand All @@ -90,7 +90,7 @@ extension TypesFileTranslator {
originalName: Constants.Operation.Body.variableName,
typeUsage: bodyTypeUsage,
associatedDeclarations: associatedDeclarations,
asSwiftSafeName: swiftSafeName
context: context
)
let structDecl = translateStructBlueprint(
.init(
Expand Down Expand Up @@ -137,7 +137,7 @@ extension TypesFileTranslator {
switch part {
case .documentedTyped(let documentedPart):
let caseDecl: Declaration = .enumCase(
name: swiftSafeName(for: documentedPart.originalName),
name: context.asSwiftSafeName(documentedPart.originalName),
kind: .nameWithAssociatedValues([.init(type: .init(part.wrapperTypeUsage))])
)
let decl = try translateMultipartPartContent(
Expand Down Expand Up @@ -404,7 +404,7 @@ extension FileTranslator {
switch part {
case .documentedTyped(let part):
let originalName = part.originalName
let identifier = swiftSafeName(for: originalName)
let identifier = context.asSwiftSafeName(originalName)
let contentType = part.partInfo.contentType
let partTypeName = part.typeName
let schema = part.schema
Expand Down Expand Up @@ -613,7 +613,7 @@ extension FileTranslator {
switch part {
case .documentedTyped(let part):
let originalName = part.originalName
let identifier = swiftSafeName(for: originalName)
let identifier = context.asSwiftSafeName(originalName)
let contentType = part.partInfo.contentType
let headersTypeName = part.typeName.appending(
swiftComponent: Constants.Operation.Output.Payload.Headers.typeName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,8 @@ struct OperationDescription {
/// The OpenAPI components, used to resolve JSON references.
var components: OpenAPI.Components

/// A converted function from user-provided strings to strings
/// safe to be used as a Swift identifier.
var asSwiftSafeName: (String) -> String
/// A set of configuration values that inform translation.
var context: TranslatorContext

/// The OpenAPI operation object.
var operation: OpenAPI.Operation { endpoint.operation }
Expand All @@ -52,7 +51,7 @@ extension OperationDescription {
/// - Parameters:
/// - map: The paths from the OpenAPI document.
/// - components: The components from the OpenAPI document.
/// - asSwiftSafeName: A converted function from user-provided strings
/// - context: A set of configuration values that inform translation.
/// to strings safe to be used as a Swift identifier.
/// - Returns: An array of `OperationDescription` instances, each representing
/// an operation discovered in the provided paths.
Expand All @@ -62,11 +61,9 @@ extension OperationDescription {
/// 1. OpenAPI 3.0.3 only supports external path references (cf. 3.1, which supports internal references too)
/// 2. Swift OpenAPI Generator currently only supports OpenAPI 3.0.x.
/// 3. Swift OpenAPI Generator currently doesn't support external references.
static func all(
from map: OpenAPI.PathItem.Map,
in components: OpenAPI.Components,
asSwiftSafeName: @escaping (String) -> String
) throws -> [OperationDescription] {
static func all(from map: OpenAPI.PathItem.Map, in components: OpenAPI.Components, context: TranslatorContext)
throws -> [OperationDescription]
{
try map.flatMap { path, value in
let value = try value.resolve(in: components)
return value.endpoints.map { endpoint in
Expand All @@ -75,7 +72,7 @@ extension OperationDescription {
endpoint: endpoint,
pathParameters: value.parameters,
components: components,
asSwiftSafeName: asSwiftSafeName
context: context
)
}
}
Expand All @@ -86,7 +83,7 @@ extension OperationDescription {
/// Uses the `operationID` value in the OpenAPI operation, if one was
/// specified. Otherwise, computes a unique name from the operation's
/// path and HTTP method.
var methodName: String { asSwiftSafeName(operationID) }
var methodName: String { context.asSwiftSafeName(operationID) }

/// Returns the identifier for the operation.
///
Expand Down Expand Up @@ -295,7 +292,7 @@ extension OperationDescription {
}
let newPath = OpenAPI.Path(newComponents, trailingSlash: path.trailingSlash)
let names: [Expression] = orderedPathParameters.map { param in
.identifierPattern("input").dot("path").dot(asSwiftSafeName(param))
.identifierPattern("input").dot("path").dot(context.asSwiftSafeName(param))
}
let arrayExpr: Expression = .literal(.array(names))
return (newPath.rawValue, arrayExpr)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,8 @@ struct TypedParameter {
/// The coding strategy appropriate for this parameter.
var codingStrategy: CodingStrategy

/// A converted function from user-provided strings to strings
/// safe to be used as a Swift identifier.
var asSwiftSafeName: (String) -> String
/// A set of configuration values that inform translation.
var context: TranslatorContext
}

extension TypedParameter: CustomStringConvertible {
Expand All @@ -49,7 +48,7 @@ extension TypedParameter {
var name: String { parameter.name }

/// The name of the parameter sanitized to be a valid Swift identifier.
var variableName: String { asSwiftSafeName(name) }
var variableName: String { context.asSwiftSafeName(name) }

/// A Boolean value that indicates whether the parameter must be specified
/// when performing the OpenAPI operation.
Expand Down Expand Up @@ -208,7 +207,7 @@ extension FileTranslator {
explode: explode,
typeUsage: usage,
codingStrategy: codingStrategy,
asSwiftSafeName: swiftSafeName
context: context
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ extension TypesFileTranslator {
originalName: parameter.name,
typeUsage: parameter.typeUsage,
associatedDeclarations: associatedDeclarations,
asSwiftSafeName: swiftSafeName
context: context
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ extension TypesFileTranslator {
typeUsage: bodyEnumTypeUsage,
default: nil,
associatedDeclarations: extraDecls,
asSwiftSafeName: swiftSafeName
context: context
)
return bodyProperty
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,14 @@ struct TypedResponseHeader {
/// The coding strategy appropriate for this parameter.
var codingStrategy: CodingStrategy

/// A converted function from user-provided strings to strings
/// safe to be used as a Swift identifier.
var asSwiftSafeName: (String) -> String
/// A set of configuration values that inform translation.
var context: TranslatorContext
}

extension TypedResponseHeader {

/// The name of the header sanitized to be a valid Swift identifier.
var variableName: String { asSwiftSafeName(name) }
var variableName: String { context.asSwiftSafeName(name) }

/// A Boolean value that indicates whether the response header can
/// be omitted in the HTTP response.
Expand Down Expand Up @@ -152,7 +151,7 @@ extension FileTranslator {
schema: schema,
typeUsage: usage,
codingStrategy: codingStrategy,
asSwiftSafeName: swiftSafeName
context: context
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ extension TypesFileTranslator {
typeUsage: headersTypeName.asUsage,
default: headersStructBlueprint.hasEmptyInit ? .emptyInit : nil,
associatedDeclarations: [headersStructDecl],
asSwiftSafeName: swiftSafeName
context: context
)
} else {
headersProperty = nil
Expand Down Expand Up @@ -92,7 +92,7 @@ extension TypesFileTranslator {
typeUsage: contentTypeUsage,
default: hasNoContent ? .nil : nil,
associatedDeclarations: [contentEnumDecl],
asSwiftSafeName: swiftSafeName
context: context
)
} else {
bodyProperty = nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ extension TypesFileTranslator {
typeUsage: typeUsage,
default: header.header.required ? nil : .nil,
associatedDeclarations: associatedDeclarations,
asSwiftSafeName: swiftSafeName
context: context
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,7 @@ struct ServerFileTranslator: FileTranslator {
let imports =
Constants.File.clientServerImports + config.additionalImports.map { ImportDescription(moduleName: $0) }

let allOperations = try OperationDescription.all(
from: doc.paths,
in: components,
asSwiftSafeName: swiftSafeName
)
let allOperations = try OperationDescription.all(from: doc.paths, in: components, context: context)

let (registerHandlersDecl, serverMethodDecls) = try translateRegisterHandlers(allOperations)

Expand Down
Loading

0 comments on commit 07ee940

Please sign in to comment.