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,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