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

Emit JSON path reference in comments for all generated types #196

Merged
merged 10 commits into from
Aug 17, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,21 @@ extension TypeName {
suffix: generatedFromCommentText
)
}

/// Returns a documentation comment by appending the "generated from" with some path
/// string to the specified user description.
/// - Parameter userDescription: The description specified by the user
/// - Parameter subPath: Sub path following `fullyQualifiedJSONPath`
/// in the OpenAPI document.
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
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.
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 @@ -43,7 +43,13 @@ extension TypesFileTranslator {
} else {
associatedDeclarations = []
}

let comment: Comment? = parent.docCommentWithUserDescription(
nil,
subPath: "\(parameter.location.rawValue)/\(parameter.name)"
)
return .init(
comment: comment,
isDeprecated: parameter.parameter.deprecated,
originalName: parameter.name,
typeUsage: parameter.typeUsage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,18 @@ extension TypesFileTranslator {
}
let identifier = contentSwiftName(content.content.contentType)
takeshi-1000 marked this conversation as resolved.
Show resolved Hide resolved
let associatedType = content.resolvedTypeUsage
let contentCase: Declaration = .enumCase(
.init(

let subPath: String = {
let contentPath = requestBody.typeUsage.typeName.isComponent ? "content" : "requestBody/content"
return "\(contentPath)/\(content.content.contentType.lowercasedTypeAndSubtypeWithEscape)"
}()

let contentCase: Declaration = .commentable(
requestBody
.typeUsage
.typeName
takeshi-1000 marked this conversation as resolved.
Show resolved Hide resolved
.docCommentWithUserDescription(nil, subPath: subPath),
.enumCase(
name: identifier,
kind: .nameWithAssociatedValues([
.init(type: associatedType.fullyQualifiedNonOptionalSwiftName)
Expand Down Expand Up @@ -154,7 +164,14 @@ extension TypesFileTranslator {
conformances: Constants.Operation.Output.conformances,
members: members
)
return bodyEnumDecl
let comment: Comment? = {
if typeName.isComponent {
return typeName.docCommentWithUserDescription(nil)
} else {
return typeName.docCommentWithUserDescription(nil, subPath: "requestBody")
}
}()
return .commentable(comment, bodyEnumDecl)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ extension TypesFileTranslator {
/// - typedResponse: The typed response to declare.
/// - Returns: A structure declaration.
func translateResponseInTypes(
responseKind: ResponseKind?,
czechboy0 marked this conversation as resolved.
Show resolved Hide resolved
typeName: TypeName,
response: TypedResponse
) throws -> Declaration {
Expand All @@ -34,10 +35,21 @@ extension TypesFileTranslator {
inParent: headersTypeName
)
let headerProperties: [PropertyBlueprint] = try headers.map { header in
try parseResponseHeaderAsProperty(for: header)
try parseResponseHeaderAsProperty(
responseKind: responseKind,
typeName: typeName,
for: header
)
}
let headerStructComment: Comment? = {
if let responseKind = responseKind?.jsonPathComponent, !typeName.isComponent {
return typeName.docCommentWithUserDescription(nil, subPath: "responses/\(responseKind)/headers")
} else {
return typeName.docCommentWithUserDescription(nil, subPath: "headers")
}
}()
let headersStructBlueprint: StructBlueprint = .init(
comment: nil,
comment: headerStructComment,
access: config.access,
typeName: headersTypeName,
conformances: Constants.Operation.Output.Payload.Headers.conformances,
Expand Down Expand Up @@ -76,22 +88,45 @@ extension TypesFileTranslator {
)
bodyCases.append(contentsOf: inlineTypeDecls)
}
let bodyCase: Declaration = .enumCase(
name: identifier,
kind: .nameWithAssociatedValues([
.init(type: associatedType.fullyQualifiedSwiftName)
])

let subPathForContentCase: String = {
if let responseKind = responseKind?.jsonPathComponent, !typeName.isComponent {
return
"responses/\(responseKind)/content/\(typedContent.content.contentType.lowercasedTypeAndSubtypeWithEscape)"
} else {
return "content/\(typedContent.content.contentType.lowercasedTypeAndSubtypeWithEscape)"
}
}()
let bodyCase: Declaration = .commentable(
typeName.docCommentWithUserDescription(nil, subPath: subPathForContentCase),
.enumCase(
name: identifier,
kind: .nameWithAssociatedValues([
.init(type: associatedType.fullyQualifiedSwiftName)
])
)
)
bodyCases.append(bodyCase)
}
let subPathForContent: String = {
if let responseKind = responseKind?.jsonPathComponent, !typeName.isComponent {
return "responses/\(responseKind)/content"
} else {
return "content"
}
czechboy0 marked this conversation as resolved.
Show resolved Hide resolved
}()
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(
typeName.docCommentWithUserDescription(nil, subPath: subPathForContent),
.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 Expand Up @@ -136,6 +171,7 @@ extension TypesFileTranslator {
of: OpenAPI.Response.self
)
return try translateResponseInTypes(
responseKind: nil,
czechboy0 marked this conversation as resolved.
Show resolved Hide resolved
typeName: typeName,
response: response
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,22 @@ extension TypesFileTranslator {
/// - header: A response parameter.
/// - Returns: A property blueprint.
func parseResponseHeaderAsProperty(
responseKind: ResponseKind?,
typeName: TypeName,
for header: TypedResponseHeader
) throws -> PropertyBlueprint {
let comment: Comment? = {
let subPath: String = {
if let responseKind = responseKind?.jsonPathComponent, !typeName.isComponent {
return "responses/\(responseKind)/headers/\(header.name)"
} else {
return "headers/\(header.name)"
}
}()
return typeName.docCommentWithUserDescription(nil, subPath: subPath)
}()
czechboy0 marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -42,6 +42,7 @@ extension TypesFileTranslator {
let responseStructDecl: Declaration?
if typedResponse.isInlined {
responseStructDecl = try translateResponseInTypes(
responseKind: responseKind,
typeName: typedResponse.typeUsage.typeName,
response: typedResponse
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,14 @@ struct TypeName: Equatable {
precondition(components.count >= 1, "Cannot get the parent of a root type")
return .init(components: components.dropLast())
}

/// Returns a bool value indicating whether the list of JSON path components contains "#" first and "components" second.
var isComponent: Bool {
takeshi-1000 marked this conversation as resolved.
Show resolved Hide resolved
guard let jsonKeyPathComponents else {
return false
}
return jsonKeyPathComponents[0] == "#" && jsonKeyPathComponents[1] == "components"
}
}

extension TypeName: CustomStringConvertible {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ extension TypesFileTranslator {
try typedResponses
.map { value in
try translateResponseInTypes(
responseKind: nil,
typeName: value.typeUsage.typeName,
response: value
)
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(
inputTypeName.docCommentWithUserDescription(nil, subPath: "\(location.rawValue)"),
translateStructBlueprint(
.init(
comment: nil,
access: config.access,
typeName: structTypeName,
conformances: Constants.Operation.Input.conformances,
properties: structProperties
)
)
)

Expand Down
Loading