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 @@ -38,5 +38,19 @@ object SpecValidator {
Issue.Warning("Links are not supported in v1 and will be ignored")
}
}

val operations = openApi.paths.orEmpty().flatMap { (path, pathItem) ->
pathItem.readOperationsMap().mapNotNull { (method, op) ->
op.operationId?.let { Triple(it, method.name, path) }
}
}
val operationIds = operations.groupBy { it.first }

for ((opId, occurrences) in operationIds) {
ensureOrAccumulate(occurrences.size == 1) {
val locations = occurrences.joinToString { "${it.second} ${it.third}" }
Issue.Warning("Duplicate operationId '$opId' found at: $locations")
}
Comment on lines +42 to +53
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 duplicate-warning message order depends on the iteration order of paths and readOperationsMap() (and then groupBy preserving that order). To keep warnings deterministic (and easier to scan / test), consider sorting occurrences (e.g., by path then method) before joinToString.

Copilot uses AI. Check for mistakes.
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,60 @@ class SpecParserTest : SpecParserTestBase() {
}
}

// -- SPEC-03b: Duplicate operationId warning --

@Test
fun `parse spec with duplicate operationId emits warning`() {
val result = SpecParser.parse(
"""
openapi: 3.0.0
info:
title: Test
version: 1.0.0
paths:
/pets:
get:
operationId: listPets
tags: [Pets]
responses:
'200':
description: OK
/animals:
get:
operationId: listPets
tags: [Animals]
responses:
'200':
description: OK
""".trimIndent().toTempFile(),
)
assertIs<ParseResult.Success<ApiSpec>>(result)
val dupWarnings = result.warnings.filter {
it.message.contains("Duplicate operationId")
}
assertTrue(
dupWarnings.isNotEmpty(),
"Expected warning about duplicate operationId, got: ${result.warnings}",
)
assertTrue(
dupWarnings.first().message.contains("listPets"),
"Warning should mention the duplicate operationId",
)
}

@Test
fun `parse spec with unique operationIds has no duplicate warnings`() {
val result = SpecParser.parse(loadResource("petstore.yaml"))
assertIs<ParseResult.Success<ApiSpec>>(result)
val dupWarnings = result.warnings.filter {
it.message.contains("Duplicate operationId")
}
assertTrue(
dupWarnings.isEmpty(),
"Petstore should have no duplicate operationId warnings",
)
}

// -- SPEC-04: Swagger 2.0 auto-conversion --

@Test
Expand Down
Loading