Skip to content

Commit

Permalink
Emit JSON path reference in comments for all generated types (#196)
Browse files Browse the repository at this point in the history
### Motivation

Resolve #13

### Modifications

Applied commentable to the declaration variable in Traslator so that a
comment in Types.swift is added indicating that it was generated from
the Json path of the openapi document

### Result

Some comments in Types.swift is generated, like
`Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift`.

### Test Plan

Modify Type.swift in the reference source

---------

Co-authored-by: Honza Dvorsky <[email protected]>
  • Loading branch information
takeshi-1000 and czechboy0 authored Aug 17, 2023
1 parent 198880f commit b59b8a3
Show file tree
Hide file tree
Showing 13 changed files with 320 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,23 @@ extension TypeName {
suffix: generatedFromCommentText
)
}

/// Returns a documentation comment by appending the "generated from"
/// string to the specified user description.
///
/// The "generated from" string also includes a subpath.
/// - Parameter userDescription: The description specified by the user.
/// - Parameter subPath: A subpath appended to the JSON path of this
/// type name.
func docCommentWithUserDescription(_ userDescription: String?, subPath: String) -> Comment? {
guard let fullyQualifiedJSONPath else {
return Comment.doc(prefix: userDescription, suffix: nil)
}
return Comment.doc(
prefix: userDescription,
suffix: "- Remark: Generated from `\(fullyQualifiedJSONPath)/\(subPath)`."
)
}
}

extension ResponseKind {
Expand Down Expand Up @@ -131,6 +148,30 @@ extension ResponseKind {
}
}

extension TypedParameter {
/// Returns a documentation comment for the parameter.
/// - Parameters:
/// - parent: The parent type of the parameter.
func docComment(parent: TypeName) -> Comment? {
parent.docCommentWithUserDescription(
nil,
subPath: "\(parameter.location.rawValue)/\(parameter.name)"
)
}
}

extension ContentType {
/// Returns a documentation comment for the content type.
/// - Parameters:
/// - typeName: The type name of the content.
func docComment(typeName: TypeName) -> Comment? {
typeName.docCommentWithUserDescription(
nil,
subPath: lowercasedTypeAndSubtypeWithEscape
)
}
}

extension Comment {

/// Returns a reference documentation string to attach to the generated function for an operation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,14 @@ struct ContentType: Hashable {
"\(lowercasedType)/\(lowercasedSubtype)"
}

/// Returns the type and subtype as a "<type>\/<subtype>" string.
///
/// Lowercased to ease case-insensitive comparisons, and escaped to show
/// that the slash between type and subtype is not a path separator.
var lowercasedTypeAndSubtypeWithEscape: String {
"\(lowercasedType)\\/\(lowercasedSubtype)"
}

/// The header value used when sending a content-type header.
var headerValueForSending: String {
guard case .json = category else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,7 @@ extension OperationDescription {
var outputTypeName: TypeName {
operationNamespace.appending(
swiftComponent: Constants.Operation.Output.typeName,

// intentionally nil, we'll append the specific params etc
// with their valid JSON key path when nested inside Output
jsonComponent: nil
jsonComponent: "responses"
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ extension TypesFileTranslator {
associatedDeclarations = []
}
return .init(
comment: parameter.docComment(parent: parent),
isDeprecated: parameter.parameter.deprecated,
originalName: parameter.name,
typeUsage: parameter.typeUsage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ extension FileTranslator {
type = try typeAssigner.typeName(for: reference)
case .b:
type = parent.appending(
swiftComponent: Constants.Operation.Body.typeName
swiftComponent: Constants.Operation.Body.typeName,
jsonComponent: "requestBody"
)
}
return try typedRequestBody(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ extension TypesFileTranslator {
for requestBody: TypedRequestBody
) throws -> [Declaration] {
var bodyMembers: [Declaration] = []
let typeName = requestBody.typeUsage.typeName
let contentTypeName = typeName.appending(jsonComponent: "content")
let contents = requestBody.contents
for content in contents {
if TypeMatcher.isInlinable(content.content.schema) {
Expand All @@ -52,10 +54,12 @@ extension TypesFileTranslator {
)
bodyMembers.append(contentsOf: inlineTypeDecls)
}
let identifier = contentSwiftName(content.content.contentType)
let contentType = content.content.contentType
let identifier = contentSwiftName(contentType)
let associatedType = content.resolvedTypeUsage
let contentCase: Declaration = .enumCase(
.init(
let contentCase: Declaration = .commentable(
contentType.docComment(typeName: contentTypeName),
.enumCase(
name: identifier,
kind: .nameWithAssociatedValues([
.init(type: associatedType.fullyQualifiedNonOptionalSwiftName)
Expand Down Expand Up @@ -99,7 +103,8 @@ extension TypesFileTranslator {
} else {
isRequestBodyOptional = true
bodyEnumTypeName = parent.appending(
swiftComponent: Constants.Operation.Body.typeName
swiftComponent: Constants.Operation.Body.typeName,
jsonComponent: "requestBody"
)
extraDecls = [
translateRequestBodyInTypes(
Expand Down Expand Up @@ -154,7 +159,11 @@ extension TypesFileTranslator {
conformances: Constants.Operation.Output.conformances,
members: members
)
return bodyEnumDecl
let comment: Comment? = typeName.docCommentWithUserDescription(nil)
return .commentable(
comment,
bodyEnumDecl
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,10 @@ enum ResponseKind {
/// Returns a new type name that appends the response's Swift name to
/// the specified parent type name.
func typeName(in parent: TypeName) -> TypeName {
parent.appending(swiftComponent: prettyName.uppercasingFirstLetter)
parent.appending(
swiftComponent: prettyName.uppercasingFirstLetter,
jsonComponent: jsonPathComponent
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,26 @@ extension TypesFileTranslator {
typeName: TypeName,
response: TypedResponse
) throws -> Declaration {

let response = response.response

let headersTypeName = typeName.appending(
swiftComponent: Constants.Operation.Output.Payload.Headers.typeName
swiftComponent: Constants.Operation.Output.Payload.Headers.typeName,
jsonComponent: "headers"
)
let headers = try typedResponseHeaders(
from: response,
inParent: headersTypeName
)
let headerProperties: [PropertyBlueprint] = try headers.map { header in
try parseResponseHeaderAsProperty(for: header)
try parseResponseHeaderAsProperty(
for: header,
parent: headersTypeName
)
}
let headerStructComment: Comment? =
headersTypeName
.docCommentWithUserDescription(nil)
let headersStructBlueprint: StructBlueprint = .init(
comment: nil,
comment: headerStructComment,
access: config.access,
typeName: headersTypeName,
conformances: Constants.Operation.Output.Payload.Headers.conformances,
Expand All @@ -58,15 +63,17 @@ extension TypesFileTranslator {
)

let bodyTypeName = typeName.appending(
swiftComponent: Constants.Operation.Body.typeName
swiftComponent: Constants.Operation.Body.typeName,
jsonComponent: "content"
)
let typedContents = try supportedTypedContents(
response.content,
inParent: bodyTypeName
)
var bodyCases: [Declaration] = []
for typedContent in typedContents {
let identifier = contentSwiftName(typedContent.content.contentType)
let contentType = typedContent.content.contentType
let identifier = contentSwiftName(contentType)
let associatedType = typedContent.resolvedTypeUsage
if TypeMatcher.isInlinable(typedContent.content.schema), let inlineType = typedContent.typeUsage {
let inlineTypeDecls = try translateSchema(
Expand All @@ -76,22 +83,30 @@ extension TypesFileTranslator {
)
bodyCases.append(contentsOf: inlineTypeDecls)
}
let bodyCase: Declaration = .enumCase(
name: identifier,
kind: .nameWithAssociatedValues([
.init(type: associatedType.fullyQualifiedSwiftName)
])

let bodyCase: Declaration = .commentable(
contentType.docComment(typeName: bodyTypeName),
.enumCase(
name: identifier,
kind: .nameWithAssociatedValues([
.init(type: associatedType.fullyQualifiedSwiftName)
])
)
)
bodyCases.append(bodyCase)
}
let hasNoContent: Bool = bodyCases.isEmpty
let contentEnumDecl: Declaration = .enum(
isFrozen: true,
accessModifier: config.access,
name: bodyTypeName.shortSwiftName,
conformances: Constants.Operation.Body.conformances,
members: bodyCases
let contentEnumDecl: Declaration = .commentable(
bodyTypeName.docCommentWithUserDescription(nil),
.enum(
isFrozen: true,
accessModifier: config.access,
name: bodyTypeName.shortSwiftName,
conformances: Constants.Operation.Body.conformances,
members: bodyCases
)
)

let contentTypeUsage = bodyTypeName.asUsage.withOptional(hasNoContent)
let contentProperty = PropertyBlueprint(
comment: .doc("Received HTTP response body"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,18 @@ extension TypesFileTranslator {
///
/// - Parameters:
/// - header: A response parameter.
/// - parent: The type name of the parent struct.
/// - Returns: A property blueprint.
func parseResponseHeaderAsProperty(
for header: TypedResponseHeader
for header: TypedResponseHeader,
parent: TypeName
) throws -> PropertyBlueprint {
let comment = parent.docCommentWithUserDescription(
nil,
subPath: header.name
)
return .init(
comment: nil,
comment: comment,
originalName: header.name,
typeUsage: header.typeUsage,
default: header.header.required ? nil : .nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,10 @@ struct TypeName: Equatable {
/// - Parameters:
/// - swiftComponent: The name of the Swift type component.
/// - jsonComponent: The name of the JSON path component.
/// - Precondition: At least one of the components must be non-nil.
/// - Returns: A new type name.
func appending(swiftComponent: String, jsonComponent: String? = nil) -> Self {
func appending(swiftComponent: String? = nil, jsonComponent: String? = nil) -> Self {
precondition(swiftComponent != nil || jsonComponent != nil, "At least the Swift or JSON name must be non-nil.")
let newComponent = Component(swift: swiftComponent, json: jsonComponent)
return .init(components: components + [newComponent])
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,16 @@ extension TypesFileTranslator {
inParent: inputTypeName
)
}
let structDecl: Declaration = translateStructBlueprint(
.init(
comment: nil,
access: config.access,
typeName: structTypeName,
conformances: Constants.Operation.Input.conformances,
properties: structProperties
let structDecl: Declaration = .commentable(
structTypeName.docCommentWithUserDescription(nil),
translateStructBlueprint(
.init(
comment: nil,
access: config.access,
typeName: structTypeName,
conformances: Constants.Operation.Input.conformances,
properties: structProperties
)
)
)

Expand Down
Loading

0 comments on commit b59b8a3

Please sign in to comment.