Skip to content

Commit

Permalink
Generate deprecation annotations based on OpenAPI document (#92)
Browse files Browse the repository at this point in the history
### Motivation

The OpenAPI spec supports declaring various parts of the API as
deprecated, including operations, parameters, schemas, and properties.
These can be surfaced in the generated code by adding `@available(*,
deprecated)` annotations to the functions, types, and properties.

### Modifications

- Support for deprecated OpenAPI schemas.
- Support for deprecated OpenAPI properties.
- Support for deprecated OpenAPI operations.

For deprecated operations, these are annotated on the function
requirements in the generated `APIProtocol`.

### Result

Deprecated annotations are generated for the parts of the API marked as
deprecated in the OpenAPI document.

### Test Plan

- Added `#/components/schemas/DeprecatedObject` to reference test.
- Added `#/components/schemas/ObjectWithDeprecatedProperty` to reference
test.
- Marked `#/paths//probe//post` operation as deprecated in reference
test.
- Marked `#/components/parameters/My-Request-UUID` parameter as
deprecated in reference test.
- Unit tests for rendering `DeprecatedDescription` already existed.

### Resolves

Resolves #26.

---------

Signed-off-by: Si Beaumont <[email protected]>
  • Loading branch information
simonjbeaumont authored Jun 27, 2023
1 parent 6b11135 commit 9d9469f
Show file tree
Hide file tree
Showing 13 changed files with 102 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1451,3 +1451,13 @@ extension KeywordKind {
.try(hasPostfixQuestionMark: false)
}
}

extension Declaration {
/// Returns a new deprecated variant of the declaration if `shouldDeprecate` is true.
func deprecate(if shouldDeprecate: Bool) -> Self {
if shouldDeprecate {
return .deprecated(.init(), self)
}
return self
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ extension FileTranslator {
func translateObjectStruct(
typeName: TypeName,
openAPIDescription: String?,
objectContext: JSONSchema.ObjectContext
objectContext: JSONSchema.ObjectContext,
isDeprecated: Bool
) throws -> Declaration {

let documentedProperties: [PropertyBlueprint] =
Expand Down Expand Up @@ -61,6 +62,7 @@ extension FileTranslator {
}
return PropertyBlueprint(
comment: comment,
isDeprecated: value.deprecated,
originalName: key,
typeUsage: propertyType,
associatedDeclarations: associatedDeclarations
Expand All @@ -86,6 +88,7 @@ extension FileTranslator {
return translateStructBlueprint(
StructBlueprint(
comment: comment,
isDeprecated: isDeprecated,
access: config.access,
typeName: typeName,
conformances: Constants.ObjectStruct.conformances,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ extension FileTranslator {
let objectDecl = try translateObjectStruct(
typeName: typeName,
openAPIDescription: overrides.userDescription ?? coreContext.description,
objectContext: objectContext
objectContext: objectContext,
isDeprecated: coreContext.deprecated
)
return [objectDecl]
case let .string(coreContext, _):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,8 @@ extension FileTranslator {
conformances: blueprint.conformances,
members: members
)
let structDecl: Declaration = .struct(structDesc)

guard let comment = blueprint.comment else {
return structDecl
}
return .commentable(comment, structDecl)
return .commentable(blueprint.comment, .struct(structDesc).deprecate(if: blueprint.isDeprecated))
}

/// Returns a declaration of an initializer declared in a structure.
Expand Down Expand Up @@ -144,6 +140,7 @@ extension FileTranslator {
type: propertyTypeName
)
)
.deprecate(if: property.isDeprecated)
)
return property.associatedDeclarations + [propertyDecl]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ struct StructBlueprint {
/// A documentation comment for the structure.
var comment: Comment?

/// Whether the type should be annotated as deprecated.
var isDeprecated: Bool = false

/// An access modifier.
var access: AccessModifier?

Expand Down Expand Up @@ -103,6 +106,9 @@ struct PropertyBlueprint {
/// A documentation comment for the property.
var comment: Comment? = nil

/// Whether the property should be annotated as deprecated.
var isDeprecated: Bool = false

/// The original name of the property specified in the OpenAPI document.
var originalName: String

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ extension TypesFileTranslator {
associatedDeclarations = []
}
return .init(
isDeprecated: parameter.parameter.deprecated,
originalName: parameter.name,
typeUsage: parameter.typeUsage,
associatedDeclarations: associatedDeclarations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ extension TypesFileTranslator {
let function = FunctionDescription(signature: signature)
return .commentable(
operationComment,
.function(function)
.function(function).deprecate(if: description.operation.deprecated)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,16 @@ final class Test_translateStructBlueprint: Test_Core {
)
}

func testDeprecatedStruct() throws {
let blueprint = StructBlueprint(isDeprecated: true, typeName: Self.testTypeName, properties: [])
let decl = makeTypesTranslator().translateStructBlueprint(blueprint)
XCTAssertEqual(decl.strippingTopComment.info.kind, .deprecated)
}

func _testStruct(_ blueprint: StructBlueprint) throws -> [DeclInfo] {
let translator = makeTypesTranslator()
let decl = translator.translateStructBlueprint(blueprint)
guard case .struct(let structDecl) = decl else {
guard case .struct(let structDecl) = decl.strippingTopComment else {
throw UnexpectedDeclError(actual: decl.info.kind, expected: .struct)
}
XCTAssertEqual(structDecl.name, "Foo")
Expand Down
19 changes: 19 additions & 0 deletions Tests/OpenAPIGeneratorReferenceTests/Resources/Docs/petstore.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ paths:
/probe/:
post:
operationId: probe
deprecated: true
responses:
'204':
description: Ack
Expand Down Expand Up @@ -202,6 +203,13 @@ components:
schema:
type: integer
format: int64
header.deprecatedHeader:
name: deprecatedHeader
in: header
deprecated: true
description: A deprecated header parameter
schema:
type: string
schemas:
Pet:
type: object
Expand Down Expand Up @@ -370,6 +378,17 @@ components:
- $ref: '#/components/schemas/MessagedExercise'
discriminator:
propertyName: kind
DeprecatedObject:
deprecated: true
type: object
properties: {}
additionalProperties: false
ObjectWithDeprecatedProperty:
type: object
properties:
message:
type: string
deprecated: true
responses:
ErrorBadRequest:
description: Bad request
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ public protocol APIProtocol: Sendable {
func createPet(_ input: Operations.createPet.Input) async throws -> Operations.createPet.Output
/// - Remark: HTTP `POST /probe/`.
/// - Remark: Generated from `#/paths//probe//post(probe)`.
func probe(_ input: Operations.probe.Input) async throws -> Operations.probe.Output
@available(*, deprecated) func probe(_ input: Operations.probe.Input) async throws
-> Operations.probe.Output
/// Update just a specific property of an existing pet. Nothing is updated if no request body is provided.
///
/// - Remark: HTTP `PATCH /pets/{petId}`.
Expand Down Expand Up @@ -577,6 +578,26 @@ public enum Components {
}
}
}
/// - Remark: Generated from `#/components/schemas/DeprecatedObject`.
@available(*, deprecated)
public struct DeprecatedObject: Codable, Equatable, Hashable, Sendable {
/// Creates a new `DeprecatedObject`.
public init() {}
public init(from decoder: Decoder) throws {
try decoder.ensureNoAdditionalProperties(knownKeys: [])
}
}
/// - Remark: Generated from `#/components/schemas/ObjectWithDeprecatedProperty`.
public struct ObjectWithDeprecatedProperty: Codable, Equatable, Hashable, Sendable {
/// - Remark: Generated from `#/components/schemas/ObjectWithDeprecatedProperty/message`.
@available(*, deprecated) public var message: Swift.String?
/// Creates a new `ObjectWithDeprecatedProperty`.
///
/// - Parameters:
/// - message:
public init(message: Swift.String? = nil) { self.message = message }
public enum CodingKeys: String, CodingKey { case message }
}
}
/// Types generated from the `#/components/parameters` section of the OpenAPI document.
public enum Parameters {
Expand All @@ -588,6 +609,10 @@ public enum Components {
///
/// - Remark: Generated from `#/components/parameters/path.petId`.
public typealias path_petId = Swift.Int64
/// A deprecated header parameter
///
/// - Remark: Generated from `#/components/parameters/header.deprecatedHeader`.
public typealias header_deprecatedHeader = Swift.String
}
/// Types generated from the `#/components/requestBodies` section of the OpenAPI document.
public enum RequestBodies {
Expand Down
9 changes: 8 additions & 1 deletion docker/docker-compose.2204.58.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@ services:
test:
image: *image
environment:
- WARN_AS_ERROR_ARG=-Xswiftc -warnings-as-errors
# Because OpenAPI supports deprecation, the generated code could include
# deprecated symbols, which will produce warnings.
#
# We'll desable -warnings-as-errors for now and revisit this when we
# refactor the compilation of the generated code into a separate
# pipeline.
#
# - WARN_AS_ERROR_ARG=-Xswiftc -warnings-as-errors
- IMPORT_CHECK_ARG=--explicit-target-dependency-import-check error

shell:
Expand Down
9 changes: 8 additions & 1 deletion docker/docker-compose.2204.59.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ services:
test:
image: *image
environment:
- WARN_AS_ERROR_ARG=-Xswiftc -warnings-as-errors
# Because OpenAPI supports deprecation, the generated code could include
# deprecated symbols, which will produce warnings.
#
# We'll desable -warnings-as-errors for now and revisit this when we
# refactor the compilation of the generated code into a separate
# pipeline.
#
# - WARN_AS_ERROR_ARG=-Xswiftc -warnings-as-errors
- IMPORT_CHECK_ARG=--explicit-target-dependency-import-check error

shell:
Expand Down
9 changes: 8 additions & 1 deletion docker/docker-compose.2204.main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@ services:
test:
image: *image
environment:
- WARN_AS_ERROR_ARG=-Xswiftc -warnings-as-errors
# Because OpenAPI supports deprecation, the generated code could include
# deprecated symbols, which will produce warnings.
#
# We'll desable -warnings-as-errors for now and revisit this when we
# refactor the compilation of the generated code into a separate
# pipeline.
#
# - WARN_AS_ERROR_ARG=-Xswiftc -warnings-as-errors
- IMPORT_CHECK_ARG=--explicit-target-dependency-import-check error

shell:
Expand Down

0 comments on commit 9d9469f

Please sign in to comment.