diff --git a/cli/zally/integration_test.go b/cli/zally/integration_test.go index 5c7f74507..c39a49775 100644 --- a/cli/zally/integration_test.go +++ b/cli/zally/integration_test.go @@ -67,7 +67,7 @@ func TestIntegrationWithSomeOtherLocalYamlFile(t *testing.T) { out, e := RunAppAndCaptureOutput([]string{"", "lint", "../../server/src/test/resources/fixtures/api_tinbox.yaml"}) assert.Contains(t, out, "Provide API Specification using OpenAPI") - assert.Contains(t, out, "MUST violations: 48") + assert.Contains(t, out, "MUST violations: 50") assert.Contains(t, out, "SHOULD violations: 23") assert.Contains(t, out, "MAY violations: 11") assert.Contains(t, out, "HINT violations: 0") diff --git a/server/src/main/java/de/zalando/zally/rule/zalando/KebabCaseInPathSegmentsRule.kt b/server/src/main/java/de/zalando/zally/rule/zalando/KebabCaseInPathSegmentsRule.kt index 08a12b909..04888c7e0 100644 --- a/server/src/main/java/de/zalando/zally/rule/zalando/KebabCaseInPathSegmentsRule.kt +++ b/server/src/main/java/de/zalando/zally/rule/zalando/KebabCaseInPathSegmentsRule.kt @@ -1,28 +1,29 @@ package de.zalando.zally.rule.zalando import de.zalando.zally.rule.api.Check +import de.zalando.zally.rule.api.Context import de.zalando.zally.rule.api.Rule import de.zalando.zally.rule.api.Severity import de.zalando.zally.rule.api.Violation import de.zalando.zally.util.PatternUtil -import io.swagger.models.Swagger @Rule( - ruleSet = ZalandoRuleSet::class, - id = "129", - severity = Severity.MUST, - title = "Lowercase words with hyphens" + ruleSet = ZalandoRuleSet::class, + id = "129", + severity = Severity.MUST, + title = "Lowercase words with hyphens" ) class KebabCaseInPathSegmentsRule { private val description = "Use lowercase separate words with hyphens for path segments" + internal val lowerCaseHyphenSeparatedRegex = """^[a-z-]+$""".toRegex() @Check(severity = Severity.MUST) - fun validate(swagger: Swagger): Violation? { - val paths = swagger.paths.orEmpty().keys.filterNot { - val pathSegments = it.split("/").filter { it.isNotEmpty() } - pathSegments.filter { !PatternUtil.isPathVariable(it) && !PatternUtil.isLowerCaseAndHyphens(it) }.isEmpty() - } - return if (paths.isNotEmpty()) Violation(description, paths) else null - } + fun checkKebabCaseInPathSegments(context: Context): List = + context.api.paths.orEmpty().entries + .filter { (path, _) -> + path.split("/").filterNot { it.isEmpty() } + .any { segment -> !PatternUtil.isPathVariable(segment) && !segment.matches(lowerCaseHyphenSeparatedRegex) } + } + .map { (_, pathEntry) -> context.violation(description, pathEntry) } } diff --git a/server/src/main/java/de/zalando/zally/util/PatternUtil.kt b/server/src/main/java/de/zalando/zally/util/PatternUtil.kt index 48d8bd6c8..18f4d2990 100644 --- a/server/src/main/java/de/zalando/zally/util/PatternUtil.kt +++ b/server/src/main/java/de/zalando/zally/util/PatternUtil.kt @@ -5,7 +5,6 @@ package de.zalando.zally.util */ object PatternUtil { - private val LOWER_CASE_HYPHENS_PATTERN = "^[a-z-]*$".toRegex() private val CAMEL_CASE_PATTERN = "^[a-z]+(?:[A-Z][a-z]+)*$".toRegex() private val PASCAL_CASE_PATTERN = "^[A-Z][a-z]+(?:[A-Z][a-z]+)*$".toRegex() private val HYPHENATED_CAMEL_CASE_PATTERN = "^[a-z]+(?:-[A-Z][a-z]+)*$".toRegex() @@ -21,8 +20,6 @@ object PatternUtil { fun hasTrailingSlash(input: String): Boolean = input.trim { it <= ' ' }.endsWith("/") - fun isLowerCaseAndHyphens(input: String): Boolean = input.matches(LOWER_CASE_HYPHENS_PATTERN) - fun isPathVariable(input: String): Boolean = input.matches(PATH_VARIABLE_PATTERN) fun isCamelCase(input: String): Boolean = input.matches(CAMEL_CASE_PATTERN) diff --git a/server/src/test/java/de/zalando/zally/rule/zalando/KebabCaseInPathSegmentsRuleTest.kt b/server/src/test/java/de/zalando/zally/rule/zalando/KebabCaseInPathSegmentsRuleTest.kt index d47cb7e00..6493afcb3 100644 --- a/server/src/test/java/de/zalando/zally/rule/zalando/KebabCaseInPathSegmentsRuleTest.kt +++ b/server/src/test/java/de/zalando/zally/rule/zalando/KebabCaseInPathSegmentsRuleTest.kt @@ -1,48 +1,52 @@ package de.zalando.zally.rule.zalando -import de.zalando.zally.swaggerWithPaths -import io.swagger.models.Swagger +import de.zalando.zally.getOpenApiContextFromContent import org.assertj.core.api.Assertions.assertThat +import org.intellij.lang.annotations.Language import org.junit.Test class KebabCaseInPathSegmentsRuleTest { - private val testPath1 = "/shipment-order/{shipment_order_id}" - private val testPath2 = "/partner-order/{partner_order_id}" - private val testPath3 = "/partner-order/{partner_order_id}/partner-order/{partner_order_id}" - private val wrongTestPath1 = "/shipment_order/{shipment_order_id}" - private val wrongTestPath2 = "/partner-order/{partner_order_id}/partner-order1/{partner_order_id}" - private val rule = KebabCaseInPathSegmentsRule() @Test - fun emptySwagger() { - assertThat(rule.validate(Swagger())).isNull() - } - - @Test - fun validateNormalPath() { - val swagger = swaggerWithPaths(testPath1) - assertThat(rule.validate(swagger)).isNull() + fun `checkKebabCaseInPathSegments should return violation for path segments which are not lowercase separate words with hyphens`() { + @Language("YAML") + val spec = """ + openapi: 3.0.1 + paths: + /partnerOrders: {} + """.trimIndent() + val context = getOpenApiContextFromContent(spec) + + val violations = rule.checkKebabCaseInPathSegments(context) + + assertThat(violations).isNotEmpty + assertThat(violations[0].description).contains("Use lowercase separate words with hyphens") + assertThat(violations[0].pointer.toString()).isEqualTo("/paths/~1partnerOrders") } @Test - fun validateMultipleNormalPaths() { - val swagger = swaggerWithPaths(testPath1, testPath2, testPath3) - assertThat(rule.validate(swagger)).isNull() + fun `checkKebabCaseInPathSegments should return no violation if all segments are lowercase separated words with hyphens`() { + @Language("YAML") + val spec = """ + openapi: 3.0.1 + paths: + /partner-orders: {} + """.trimIndent() + val context = getOpenApiContextFromContent(spec) + + val violations = rule.checkKebabCaseInPathSegments(context) + + assertThat(violations).isEmpty() } @Test - fun validateFalsePath() { - val swagger = swaggerWithPaths(wrongTestPath1) - val result = rule.validate(swagger)!! - assertThat(result.paths).hasSameElementsAs(listOf(wrongTestPath1)) - } + fun `lowerCaseHyphenSeparatedRegex should match lowercase, with hyphen separated words`() { + assertThat("articles".matches(rule.lowerCaseHyphenSeparatedRegex)).isTrue() + assertThat("partner-articles".matches(rule.lowerCaseHyphenSeparatedRegex)).isTrue() - @Test - fun validateMultipleFalsePaths() { - val swagger = swaggerWithPaths(wrongTestPath1, testPath2, wrongTestPath2) - val result = rule.validate(swagger)!! - assertThat(result.paths).hasSameElementsAs(listOf(wrongTestPath1, wrongTestPath2)) + assertThat("COOL-ARTICLES".matches(rule.lowerCaseHyphenSeparatedRegex)).isFalse() + assertThat("wEirDARtiCles".matches(rule.lowerCaseHyphenSeparatedRegex)).isFalse() } } diff --git a/server/src/test/java/de/zalando/zally/util/PatternUtilTest.kt b/server/src/test/java/de/zalando/zally/util/PatternUtilTest.kt index 1233c8378..b048332bb 100644 --- a/server/src/test/java/de/zalando/zally/util/PatternUtilTest.kt +++ b/server/src/test/java/de/zalando/zally/util/PatternUtilTest.kt @@ -7,7 +7,6 @@ import de.zalando.zally.util.PatternUtil.isHyphenated import de.zalando.zally.util.PatternUtil.isHyphenatedCamelCase import de.zalando.zally.util.PatternUtil.isHyphenatedPascalCase import de.zalando.zally.util.PatternUtil.isKebabCase -import de.zalando.zally.util.PatternUtil.isLowerCaseAndHyphens import de.zalando.zally.util.PatternUtil.isPascalCase import de.zalando.zally.util.PatternUtil.isPathVariable import de.zalando.zally.util.PatternUtil.isSnakeCase @@ -27,13 +26,6 @@ class PatternUtilTest { assertFalse(hasTrailingSlash("blah")) } - @Test - fun checkIsLowerCaseAndHyphens() { - assertTrue(isLowerCaseAndHyphens("a-b-c")) - assertTrue(isLowerCaseAndHyphens("abc")) - assertFalse(isLowerCaseAndHyphens("A-B-C")) - } - @Test fun checkIsPathVariable() { assertTrue(isPathVariable("{test}"))