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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
)
Expand Down Expand Up @@ -506,6 +511,50 @@ object SpecParser {
)
}

context(_: Warnings)
private fun warnOnUnknownTypes(endpoints: List<Endpoint>, schemas: List<SchemaModel>) {
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",
)
}
}
}
Comment on lines +516 to +531
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warnOnUnknownTypes only scans SchemaModel.properties, but TypeRef.Unknown can occur inside SchemaModel.underlyingType (e.g., type: object with additionalProperties: true -> Map(Unknown), or type: array with missing items -> Array(Unknown)). Those cases currently produce generated types containing JsonElement without any warning, and endpoints referencing such schemas won’t be flagged either since TypeRef.Reference is treated as non-unknown. Consider also checking schema.underlyingType (and warning at least with schema name) when containsUnknown(schema.underlyingType) is true.

Copilot uses AI. Check for mistakes.
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("/")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ParseResult.Success<*>>(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<ParseResult.Success<*>>(result)
val unknownWarnings = result.warnings.filter {
it.message.contains("JsonElement")
}
assertTrue(
unknownWarnings.isEmpty(),
"Petstore should have no unknown-type warnings, got: $unknownWarnings",
)
}
Comment on lines +165 to +204
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The added tests cover unknown types inside an object property, but they don’t cover unknowns that appear only via a schema’s underlyingType (e.g., additionalProperties: true or type: array without items). Adding a test for such a schema (and ideally for an endpoint referencing it via $ref) would prevent regressions for the missing-warning cases.

Copilot uses AI. Check for mistakes.

// -- SPEC-02: $ref resolution --

@Test
Expand Down
Loading