Skip to content
Merged
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
@@ -0,0 +1,74 @@
package com.backbase.oss.boat.quay.ruleset

import org.zalando.zally.core.toJsonPointer
import org.zalando.zally.rule.api.*
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.time.format.DateTimeParseException

@Rule(
ruleSet = BoatRuleSet::class,
id = "B015",
severity = Severity.MUST,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

if this will break builds for the services which already have a deprecated specs, do we need to add an explanation in README.md?

title = "Check x-sunset-date is set and valid when x-deprecated is true"
)
class InfoBlockSunsetDateChecker {

@Check(Severity.MUST)
fun validate(context: Context): List<Violation> {
val violations = mutableListOf<Violation>()
val extensions = context.api.info.extensions ?: return emptyList()

val isDeprecated = isTrue(extensions["x-deprecated"])
val sunsetDateValue = extensions["x-sunset-date"]
val hasSunsetDate = sunsetDateValue != null && sunsetDateValue.toString().isNotBlank()

when {
isDeprecated && !hasSunsetDate -> {
violations.add(
context.violation(
"x-sunset-date must be set (as YYYY-MM-DD) when x-deprecated is true",
"/openapi/info/x-sunset-date".toJsonPointer()

Check failure on line 31 in boat-quay/boat-quay-rules/src/main/kotlin/com/backbase/oss/boat/quay/ruleset/InfoBlockSunsetDateChecker.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "/openapi/info/x-sunset-date" 3 times.

See more on https://sonarcloud.io/project/issues?id=com.backbase.oss%3Abackbase-openapi-tools&issues=AaBniRGFmQ3p7RClxVyu&open=AaBniRGFmQ3p7RClxVyu&pullRequest=1282
)
)
}
isDeprecated && hasSunsetDate -> {
if (!isValidDate(sunsetDateValue.toString())) {
violations.add(
context.violation(
"x-sunset-date must be a valid YYYY-MM-DD date, got: ${sunsetDateValue}",
"/openapi/info/x-sunset-date".toJsonPointer()
)
)
}
}
!isDeprecated && hasSunsetDate -> {
violations.add(
context.violation(
"x-sunset-date is set but x-deprecated is not true — remove the stale sunset date or mark the API deprecated",
"/openapi/info/x-sunset-date".toJsonPointer()
)
)
}
}

return violations
}

private fun isTrue(value: Any?): Boolean {
return when (value) {
is Boolean -> value
is String -> value.equals("true", ignoreCase = true)
else -> false
}
}

private fun isValidDate(value: String): Boolean {
return try {
LocalDate.parse(value, DateTimeFormatter.ISO_LOCAL_DATE)
true
} catch (e: DateTimeParseException) {
false
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package com.backbase.oss.boat.quay.ruleset

import com.backbase.oss.boat.quay.ruleset.test.ZallyAssertions
import org.intellij.lang.annotations.Language
import org.junit.jupiter.api.Test
import org.zalando.zally.core.DefaultContextFactory

class InfoBlockSunsetDateCheckerTest {

private val cut = InfoBlockSunsetDateChecker()

@Test
fun `x-deprecated true with valid x-sunset-date passes`() {
@Language("YAML")
val context = DefaultContextFactory().getOpenApiContext(
"""
openapi: 3.0.3
info:
title: API
version: 1.0.0
x-deprecated: true
x-sunset-date: "2027-04-15"
""".trimIndent()
)

val violations = cut.validate(context)

ZallyAssertions
.assertThat(violations)
.isEmpty()
}

@Test
fun `x-deprecated true without x-sunset-date fails`() {
@Language("YAML")
val context = DefaultContextFactory().getOpenApiContext(
"""
openapi: 3.0.3
info:
title: API
version: 1.0.0
x-deprecated: true
""".trimIndent()
)

val violations = cut.validate(context)

ZallyAssertions
.assertThat(violations)
.isNotEmpty
}

@Test
fun `x-deprecated true with empty x-sunset-date fails`() {
@Language("YAML")
val context = DefaultContextFactory().getOpenApiContext(
"""
openapi: 3.0.3
info:
title: API
version: 1.0.0
x-deprecated: true
x-sunset-date: ""
""".trimIndent()
)

val violations = cut.validate(context)

ZallyAssertions
.assertThat(violations)
.isNotEmpty
}

@Test
fun `x-deprecated true with malformed x-sunset-date fails`() {
@Language("YAML")
val context = DefaultContextFactory().getOpenApiContext(
"""
openapi: 3.0.3
info:
title: API
version: 1.0.0
x-deprecated: true
x-sunset-date: "2027.04"
""".trimIndent()
)

val violations = cut.validate(context)

ZallyAssertions
.assertThat(violations)
.isNotEmpty
}

@Test
fun `x-deprecated false without x-sunset-date passes`() {
@Language("YAML")
val context = DefaultContextFactory().getOpenApiContext(
"""
openapi: 3.0.3
info:
title: API
version: 1.0.0
x-deprecated: false
""".trimIndent()
)

val violations = cut.validate(context)

ZallyAssertions
.assertThat(violations)
.isEmpty()
}

@Test
fun `no x-deprecated without x-sunset-date passes`() {
@Language("YAML")
val context = DefaultContextFactory().getOpenApiContext(
"""
openapi: 3.0.3
info:
title: API
version: 1.0.0
""".trimIndent()
)

val violations = cut.validate(context)

ZallyAssertions
.assertThat(violations)
.isEmpty()
}

@Test
fun `x-deprecated false with x-sunset-date fails`() {
@Language("YAML")
val context = DefaultContextFactory().getOpenApiContext(
"""
openapi: 3.0.3
info:
title: API
version: 1.0.0
x-deprecated: false
x-sunset-date: "2027-04-15"
""".trimIndent()
)

val violations = cut.validate(context)

ZallyAssertions
.assertThat(violations)
.isNotEmpty
}

@Test
fun `no x-deprecated with x-sunset-date fails`() {
@Language("YAML")
val context = DefaultContextFactory().getOpenApiContext(
"""
openapi: 3.0.3
info:
title: API
version: 1.0.0
x-sunset-date: "2027-04-15"
""".trimIndent()
)

val violations = cut.validate(context)

ZallyAssertions
.assertThat(violations)
.isNotEmpty
}

@Test
fun `no extensions passes`() {
@Language("YAML")
val context = DefaultContextFactory().getOpenApiContext(
"""
openapi: 3.0.3
info:
title: API
version: 1.0.0
""".trimIndent()
)

val violations = cut.validate(context)

ZallyAssertions
.assertThat(violations)
.isEmpty()
}
}
Loading