Skip to content

Commit

Permalink
feat(server): migrate kebab case in path segments rule to use context…
Browse files Browse the repository at this point in the history
… object to support OpenAPI 3 (#714)
  • Loading branch information
maxim-tschumak committed Sep 7, 2018
1 parent 9643b42 commit 7a0e718
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 53 deletions.
2 changes: 1 addition & 1 deletion cli/zally/integration_test.go
Expand Up @@ -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")
Expand Down
@@ -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<Violation> =
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) }
}
3 changes: 0 additions & 3 deletions server/src/main/java/de/zalando/zally/util/PatternUtil.kt
Expand Up @@ -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()
Expand All @@ -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)
Expand Down
@@ -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()
}
}
8 changes: 0 additions & 8 deletions server/src/test/java/de/zalando/zally/util/PatternUtilTest.kt
Expand Up @@ -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
Expand All @@ -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}"))
Expand Down

0 comments on commit 7a0e718

Please sign in to comment.