diff --git a/server/zally-ruleset-sbb/src/main/kotlin/org/zalando/zally/ruleset/sbb/PluralizeResourceNamesRule.kt b/server/zally-ruleset-sbb/src/main/kotlin/org/zalando/zally/ruleset/sbb/PluralizeResourceNamesRule.kt index dbd864c07..b7877883f 100644 --- a/server/zally-ruleset-sbb/src/main/kotlin/org/zalando/zally/ruleset/sbb/PluralizeResourceNamesRule.kt +++ b/server/zally-ruleset-sbb/src/main/kotlin/org/zalando/zally/ruleset/sbb/PluralizeResourceNamesRule.kt @@ -6,11 +6,7 @@ import org.zalando.zally.core.plus import org.zalando.zally.core.toEscapedJsonPointer import org.zalando.zally.core.toJsonPointer import org.zalando.zally.core.util.PatternUtil -import org.zalando.zally.rule.api.Check -import org.zalando.zally.rule.api.Context -import org.zalando.zally.rule.api.Rule -import org.zalando.zally.rule.api.Severity -import org.zalando.zally.rule.api.Violation +import org.zalando.zally.rule.api.* import org.zalando.zally.ruleset.zalando.util.WordUtil.isPlural @Rule( @@ -24,6 +20,7 @@ class PluralizeResourceNamesRule(rulesConfig: Config) { private val slash = "/" private val slashes = "/+".toRegex() + private val semVerVersionFormat = "^[0-9]+([.][0-9]+){1,2}$".toRegex() @Suppress("SpreadOperator") internal val whitelist = mutableListOf( @@ -49,12 +46,22 @@ class PluralizeResourceNamesRule(rulesConfig: Config) { } private fun pathSegments(path: String): List { - return path.split(slashes).filter { !it.isEmpty() } + return path.split(slashes).filter { it.isNotEmpty() } } private fun isNonViolating(it: String) = - !PatternUtil.isPathVariable(it) && !isPlural(it) + (!PatternUtil.isPathVariable(it) && (!isPlural(it) || it.contains("."))) private fun violation(context: Context, term: String, pointer: JsonPointer) = - context.violation("Resource '$term' appears to be singular", pointer) + when { + semVerVersionFormat.matches(term) -> { + context.violation("Resource '$term' is a version, instead of a resource", pointer) + } + term.contains(".") -> { + context.violation("Resource '$term' has a dot as delimiter", pointer) + } + else -> { + context.violation("Resource '$term' appears to be singular", pointer) + } + } } diff --git a/server/zally-ruleset-sbb/src/main/kotlin/org/zalando/zally/ruleset/sbb/VersionInUriRule.kt b/server/zally-ruleset-sbb/src/main/kotlin/org/zalando/zally/ruleset/sbb/VersionInUriRule.kt index 06bbeb448..65bad8886 100644 --- a/server/zally-ruleset-sbb/src/main/kotlin/org/zalando/zally/ruleset/sbb/VersionInUriRule.kt +++ b/server/zally-ruleset-sbb/src/main/kotlin/org/zalando/zally/ruleset/sbb/VersionInUriRule.kt @@ -33,7 +33,7 @@ class VersionInUriRule { @Check(severity = Severity.MUST) fun checkResourceNames(context: Context): List = (violatingResourceNames(context.api)) - .map { context.violation("Version found in resource name. Use versions only at the beginning of the path as an own resource, e.g. like .../v1/... .", it) } + .map { context.violation("Version found in resource name. Use versions only at the beginning of the path as an own resource and in format v\${major}, e.g. like .../v1/... .", it) } private fun violatingServers(api: OpenAPI): Collection = api.servers.orEmpty() diff --git a/server/zally-ruleset-sbb/src/main/resources/reference.conf b/server/zally-ruleset-sbb/src/main/resources/reference.conf index 799db0cbd..e57d7ab00 100644 --- a/server/zally-ruleset-sbb/src/main/resources/reference.conf +++ b/server/zally-ruleset-sbb/src/main/resources/reference.conf @@ -74,6 +74,7 @@ PluralizeResourceNamesRule { whitelist: [ /api/ /kpis/ + /"v[0-9]"/ ] } diff --git a/server/zally-ruleset-sbb/src/test/kotlin/org/zalando/zally/ruleset/sbb/PluralizeResourceNamesRuleTest.kt b/server/zally-ruleset-sbb/src/test/kotlin/org/zalando/zally/ruleset/sbb/PluralizeResourceNamesRuleTest.kt index 623bcd0a4..e1f9a26a7 100644 --- a/server/zally-ruleset-sbb/src/test/kotlin/org/zalando/zally/ruleset/sbb/PluralizeResourceNamesRuleTest.kt +++ b/server/zally-ruleset-sbb/src/test/kotlin/org/zalando/zally/ruleset/sbb/PluralizeResourceNamesRuleTest.kt @@ -150,6 +150,33 @@ class PluralizeResourceNamesRuleTest { .isEmpty() } + @Test + fun `validate with simple version in path returns no violations`() { + val context = openApiContextWithPath("/v1/things") + + val violations = rule.validate(context) + + assertThat(violations) + .isEmpty() + } + + @Test + fun `validate with semver version or dot in path segment returns violations`() { + val context = openApiContextWithPath("/api/1.2.3/some.things") + + val violations = rule.validate(context) + + assertThat(violations) + .descriptionsEqualTo( + "Resource '1.2.3' is a version, instead of a resource", + "Resource 'some.things' has a dot as delimiter" + ) + .pointersEqualTo( + "/paths/~1api~11.2.3~1some.things", + "/paths/~1api~11.2.3~1some.things" + ) + } + @Test fun `validate with whitelisted component returns other violations`() { val context = openApiContextWithPath("/prefix/whitelisted/suffix")