Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions core/src/main/kotlin/com/avsystem/justworks/core/gen/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import com.avsystem.justworks.core.model.PrimitiveType
import com.avsystem.justworks.core.model.PropertyModel
import com.avsystem.justworks.core.model.SchemaModel
import com.avsystem.justworks.core.model.TypeRef
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.BOOLEAN
import com.squareup.kotlinpoet.BYTE_ARRAY
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.DOUBLE
import com.squareup.kotlinpoet.FLOAT
import com.squareup.kotlinpoet.INT
Expand All @@ -18,6 +20,12 @@ import com.squareup.kotlinpoet.SET
import com.squareup.kotlinpoet.STRING
import com.squareup.kotlinpoet.TypeName

internal val DEPRECATED = ClassName("kotlin", "Deprecated")

/** Builds a `@Deprecated("<message>")` annotation. */
internal fun deprecatedAnnotation(message: String): AnnotationSpec =
AnnotationSpec.builder(DEPRECATED).addMember("%S", message).build()

internal val TypeRef.properties: List<PropertyModel>
get() = when (this) {
is TypeRef.Inline -> properties
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.avsystem.justworks.core.gen.TOKEN
import com.avsystem.justworks.core.gen.client.BodyGenerator.buildFunctionBody
import com.avsystem.justworks.core.gen.client.ParametersGenerator.buildBodyParams
import com.avsystem.justworks.core.gen.client.ParametersGenerator.buildNullableParameter
import com.avsystem.justworks.core.gen.deprecatedAnnotation
import com.avsystem.justworks.core.gen.invoke
import com.avsystem.justworks.core.gen.shared.toAuthParam
import com.avsystem.justworks.core.gen.toCamelCase
Expand Down Expand Up @@ -231,6 +232,10 @@ internal object ClientGenerator {
.addModifiers(KModifier.SUSPEND)
.returns(returnType)

if (endpoint.deprecated) {
funBuilder.addAnnotation(deprecatedAnnotation("This operation is deprecated."))
}

val params = endpoint.parameters.groupBy { it.location }

val pathParams = params[ParameterLocation.PATH].orEmpty().map { param ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.avsystem.justworks.core.gen.UUID_SERIALIZER
import com.avsystem.justworks.core.gen.UUID_TYPE
import com.avsystem.justworks.core.gen.collectInlineEnums
import com.avsystem.justworks.core.gen.collectInlineSchemas
import com.avsystem.justworks.core.gen.deprecatedAnnotation
import com.avsystem.justworks.core.gen.invoke
import com.avsystem.justworks.core.gen.model.ModelGenerator.buildNestedVariant
import com.avsystem.justworks.core.gen.model.ModelGenerator.generateDataClass
Expand Down Expand Up @@ -165,6 +166,10 @@ internal object ModelGenerator {
val parentBuilder = TypeSpec.interfaceBuilder(className).addModifiers(KModifier.SEALED)
parentBuilder.addAnnotation(SERIALIZABLE)

if (schema.deprecated) {
parentBuilder.addAnnotation(deprecatedAnnotation("This schema is deprecated."))
}

if (schema.discriminator != null) {
parentBuilder.addAnnotation(
AnnotationSpec
Expand Down Expand Up @@ -264,8 +269,10 @@ internal object ModelGenerator {
.builder(kotlinName, type)
.initializer(kotlinName)
.addAnnotation(AnnotationSpec.builder(SERIAL_NAME).addMember("%S", prop.name).build())
.apply { prop.description?.let { addKdoc("%L", it) } }
.build()
.apply {
if (prop.deprecated) addAnnotation(deprecatedAnnotation("This property is deprecated."))
prop.description?.let { addKdoc("%L", it) }
}.build()
}

builder.primaryConstructor(constructorBuilder.build())
Expand Down Expand Up @@ -294,6 +301,10 @@ internal object ModelGenerator {
.build(),
)

if (schema.deprecated) {
typeSpec.addAnnotation(deprecatedAnnotation("This schema is deprecated."))
}

if (schema.description != null) {
typeSpec.addKdoc("%L", schema.description)
}
Expand Down Expand Up @@ -423,6 +434,10 @@ internal object ModelGenerator {
.addAnnotation(SERIALIZABLE)
.addSuperinterfaces(superinterfaces)

if (schema.deprecated) {
typeSpec.addAnnotation(deprecatedAnnotation("This schema is deprecated."))
}

if (serialName != null) {
typeSpec.addAnnotation(
AnnotationSpec
Expand Down Expand Up @@ -664,6 +679,10 @@ internal object ModelGenerator {

val typeAlias = TypeAliasSpec.builder(schema.name, primitiveType)

if (schema.deprecated) {
typeAlias.addAnnotation(deprecatedAnnotation("This schema is deprecated."))
}

if (schema.description != null) {
typeAlias.addKdoc("%L", schema.description)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ data class Endpoint(
val parameters: List<Parameter>,
val requestBody: RequestBody?,
val responses: Map<String, Response>,
val deprecated: Boolean = false,
)

enum class HttpMethod {
Expand All @@ -61,6 +62,7 @@ data class Parameter(
val required: Boolean,
val schema: TypeRef,
val description: String?,
val deprecated: Boolean = false,
)

// todo: add cookie
Expand Down Expand Up @@ -99,6 +101,7 @@ data class SchemaModel(
val anyOf: List<TypeRef>?,
val discriminator: Discriminator?,
val underlyingType: TypeRef? = null,
val deprecated: Boolean = false,
)

data class PropertyModel(
Expand All @@ -107,6 +110,7 @@ data class PropertyModel(
val description: String?,
val nullable: Boolean,
val defaultValue: Any? = null,
val deprecated: Boolean = false,
)

data class EnumModel(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ object SpecParser {
parameters = mergedParams,
requestBody = requestBody,
responses = responses,
deprecated = operation.deprecated ?: false,
)
}
}.toList()
Expand All @@ -310,6 +311,7 @@ object SpecParser {
required = required ?: false,
schema = schema?.toTypeRef() ?: TypeRef.Primitive(PrimitiveType.STRING),
description = description,
deprecated = deprecated ?: false,
)

// --- Schema extraction ---
Expand Down Expand Up @@ -363,6 +365,7 @@ object SpecParser {
anyOf = anyOf?.let { it.map(TypeRef::Reference).ifEmpty { null } },
discriminator = discriminator,
underlyingType = underlyingType,
deprecated = schema.deprecated ?: false,
)
}

Expand Down Expand Up @@ -592,6 +595,7 @@ object SpecParser {
description = propSchema.description,
nullable = propName !in required && !type.honorsDefault(propSchema.default),
defaultValue = normalizeDefault(propSchema.default),
deprecated = propSchema.deprecated == true,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,28 @@ class ClientGeneratorTest {
assertTrue(KModifier.SUSPEND in funSpec.modifiers, "Expected SUSPEND modifier")
}

// -- Deprecated operations --

@Test
fun `deprecated operation gets Deprecated annotation`() {
val cls = clientClass(endpoint().copy(deprecated = true))
val funSpec = cls.funSpecs.first { it.name == "listPets" }
assertTrue(
funSpec.annotations.any { it.typeName.toString() == "kotlin.Deprecated" },
"Expected @Deprecated on deprecated operation",
)
}

@Test
fun `non-deprecated operation has no Deprecated annotation`() {
val cls = clientClass(endpoint())
val funSpec = cls.funSpecs.first { it.name == "listPets" }
assertFalse(
funSpec.annotations.any { it.typeName.toString() == "kotlin.Deprecated" },
"Did not expect @Deprecated on non-deprecated operation",
)
}

// -- CLNT-03: All HTTP methods --

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,51 @@ class ModelGeneratorTest {
assertTrue(KModifier.DATA in typeSpec.modifiers, "Expected DATA modifier")
}

@Test
fun `deprecated schema gets Deprecated annotation on data class`() {
val files = generate(spec(schemas = listOf(petSchema.copy(deprecated = true))))
val typeSpec = files[0].members.filterIsInstance<TypeSpec>()[0]
val annotations = typeSpec.annotations.map { it.typeName.toString() }
assertTrue("kotlin.Deprecated" in annotations, "Expected @Deprecated on deprecated schema")
}

@Test
fun `non-deprecated schema has no Deprecated annotation`() {
val files = generate(spec(schemas = listOf(petSchema)))
val typeSpec = files[0].members.filterIsInstance<TypeSpec>()[0]
val annotations = typeSpec.annotations.map { it.typeName.toString() }
assertTrue("kotlin.Deprecated" !in annotations, "Did not expect @Deprecated")
}

@Test
fun `deprecated property gets Deprecated annotation`() {
val schema = SchemaModel(
name = "Thing",
description = null,
properties = listOf(
PropertyModel("legacy", TypeRef.Primitive(PrimitiveType.STRING), null, false, deprecated = true),
PropertyModel("current", TypeRef.Primitive(PrimitiveType.STRING), null, false),
),
requiredProperties = setOf("legacy", "current"),
allOf = null,
oneOf = null,
anyOf = null,
discriminator = null,
)
val files = generate(spec(schemas = listOf(schema)))
val typeSpec = files[0].members.filterIsInstance<TypeSpec>()[0]
val legacy = typeSpec.propertySpecs.first { it.name == "legacy" }
val current = typeSpec.propertySpecs.first { it.name == "current" }
assertTrue(
legacy.annotations.any { it.typeName.toString() == "kotlin.Deprecated" },
"Expected @Deprecated on deprecated property",
)
assertTrue(
current.annotations.none { it.typeName.toString() == "kotlin.Deprecated" },
"Did not expect @Deprecated on normal property",
)
}

@Test
fun `generates class with Serializable annotation`() {
val files = generate(spec(schemas = listOf(petSchema)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,55 @@ class SpecParserTest : SpecParserTestBase() {
assertEquals("NewPet", bodyType.schemaName)
}

@Test
fun `parses deprecated operations schemas and parameters`() {
val spec = parseSpec(
"""
openapi: 3.0.0
info:
title: Deprecated API
version: 1.0.0
paths:
/legacy:
get:
operationId: getLegacy
deprecated: true
parameters:
- name: oldParam
in: query
deprecated: true
schema:
type: string
responses:
'200':
description: ok
components:
schemas:
LegacyModel:
type: object
deprecated: true
properties:
id:
type: string
oldField:
type: string
deprecated: true
""".trimIndent().toTempFile(),
)

val op = spec.endpoints.find { it.operationId == "getLegacy" } ?: fail("getLegacy not found")
assertTrue(op.deprecated, "operation should be deprecated")
val param = op.parameters.find { it.name == "oldParam" } ?: fail("oldParam not found")
assertTrue(param.deprecated, "parameter should be deprecated")

val model = spec.schemas.find { it.name == "LegacyModel" } ?: fail("LegacyModel not found")
assertTrue(model.deprecated, "schema should be deprecated")
val oldField = model.properties.find { it.name == "oldField" } ?: fail("oldField not found")
assertTrue(oldField.deprecated, "property should be deprecated")
val idField = model.properties.find { it.name == "id" } ?: fail("id not found")
assertFalse(idField.deprecated, "normal property should not be deprecated")
}

@Test
fun `parsed endpoints have tags`() {
val listPets = petstore.endpoints.find { it.operationId == "listPets" }!!
Expand Down
Loading