diff --git a/core/src/main/kotlin/com/avsystem/justworks/core/parser/SpecParser.kt b/core/src/main/kotlin/com/avsystem/justworks/core/parser/SpecParser.kt index c45053f..446555a 100644 --- a/core/src/main/kotlin/com/avsystem/justworks/core/parser/SpecParser.kt +++ b/core/src/main/kotlin/com/avsystem/justworks/core/parser/SpecParser.kt @@ -16,6 +16,7 @@ import com.avsystem.justworks.core.Warnings import com.avsystem.justworks.core.accumulate import com.avsystem.justworks.core.accumulateAndReturnNull import com.avsystem.justworks.core.ensureNotNullOrAccumulate +import com.avsystem.justworks.core.ensureOrAccumulate import com.avsystem.justworks.core.model.ApiKeyLocation import com.avsystem.justworks.core.model.ApiSpec import com.avsystem.justworks.core.model.ContentType @@ -186,11 +187,15 @@ object SpecParser { } val syntheticModels = collectModels(emptySet(), emptyList()) + val schemas = schemaModels + syntheticModels + + warnOnUnknownTypes(endpoints, schemas) + return ApiSpec( title = title, version = info?.version ?: "0.0.0", endpoints = endpoints, - schemas = schemaModels + syntheticModels, + schemas = schemas, enums = enumModels, securitySchemes = securitySchemes, ) @@ -506,6 +511,50 @@ object SpecParser { ) } + context(_: Warnings) + private fun warnOnUnknownTypes(endpoints: List, schemas: List) { + for (schema in schemas) { + for (prop in schema.properties) { + ensureOrAccumulate(!containsUnknown(prop.type)) { + Issue.Warning( + "Schema '${schema.name}', property '${prop.name}': unresolvable type mapped to JsonElement", + ) + } + } + if (schema.underlyingType != null) { + ensureOrAccumulate(!containsUnknown(schema.underlyingType)) { + Issue.Warning( + "Schema '${schema.name}': underlying type contains unresolvable type mapped to JsonElement", + ) + } + } + } + for (endpoint in endpoints) { + val op = endpoint.operationId + for ((code, response) in endpoint.responses) { + ensureOrAccumulate(response.schema == null || !containsUnknown(response.schema)) { + Issue.Warning("Endpoint '$op', response '$code': unresolvable type mapped to JsonElement") + } + } + ensureOrAccumulate(endpoint.requestBody == null || !containsUnknown(endpoint.requestBody.schema)) { + Issue.Warning("Endpoint '$op', request body: unresolvable type mapped to JsonElement") + } + for (param in endpoint.parameters) { + ensureOrAccumulate(!containsUnknown(param.schema)) { + Issue.Warning("Endpoint '$op', parameter '${param.name}': unresolvable type mapped to JsonElement") + } + } + } + } + + private fun containsUnknown(type: TypeRef): Boolean = when (type) { + TypeRef.Unknown -> true + is TypeRef.Array -> containsUnknown(type.items) + is TypeRef.Map -> containsUnknown(type.valueType) + is TypeRef.Inline -> type.properties.any { containsUnknown(it.type) } + is TypeRef.Primitive, is TypeRef.Reference -> false + } + private fun generateOperationId(method: HttpMethod, path: String): String { val segments = path .split("/") diff --git a/core/src/test/kotlin/com/avsystem/justworks/core/parser/SpecParserTest.kt b/core/src/test/kotlin/com/avsystem/justworks/core/parser/SpecParserTest.kt index ad055d5..d651e3d 100644 --- a/core/src/test/kotlin/com/avsystem/justworks/core/parser/SpecParserTest.kt +++ b/core/src/test/kotlin/com/avsystem/justworks/core/parser/SpecParserTest.kt @@ -160,6 +160,49 @@ class SpecParserTest : SpecParserTestBase() { assertEquals("Pet", itemType.schemaName) } + // -- SPEC-01b: Warnings for unknown schemas -- + + @Test + fun `parse spec with unresolvable schema emits warning`() { + val result = SpecParser.parse( + """ + openapi: 3.0.0 + info: + title: Test + version: 1.0.0 + paths: {} + components: + schemas: + Container: + type: object + properties: + data: + type: object + """.trimIndent().toTempFile(), + ) + assertIs>(result) + val warningMessages = result.warnings.map { it.message } + assertTrue( + warningMessages.any { + it.contains("Container") && it.contains("data") && it.contains("JsonElement") + }, + "Expected warning about unresolvable type, got: $warningMessages", + ) + } + + @Test + fun `parse spec without unknown schemas has no unknown-type warnings`() { + val result = SpecParser.parse(loadResource("petstore.yaml")) + assertIs>(result) + val unknownWarnings = result.warnings.filter { + it.message.contains("JsonElement") + } + assertTrue( + unknownWarnings.isEmpty(), + "Petstore should have no unknown-type warnings, got: $unknownWarnings", + ) + } + // -- SPEC-02: $ref resolution -- @Test