Skip to content

Commit

Permalink
Litteral YAML in tests + Less class inheritance for rules
Browse files Browse the repository at this point in the history
  • Loading branch information
David Dufour-Boivin committed Jul 24, 2018
1 parent 5018e15 commit 1a144a9
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 83 deletions.
44 changes: 30 additions & 14 deletions server/src/main/java/de/zalando/zally/rule/Context.kt
Original file line number Diff line number Diff line change
Expand Up @@ -122,30 +122,46 @@ class Context(openApi: OpenAPI, swagger: Swagger? = null) {
private val log = LoggerFactory.getLogger(Context::class.java)
val extensionNames = arrayOf("getVendorExtensions", "getExtensions")

fun createOpenApiContext(content: String): Context? {
fun createOpenApiContext(content: String, failOnParseErrors: Boolean = false): Context? {
val parseOptions = ParseOptions()
parseOptions.isResolve = true
// parseOptions.isResolveFully = true // https://github.com/swagger-api/swagger-parser/issues/682

return OpenAPIV3Parser().readContents(content, null, parseOptions)?.openAPI?.let {
val parseResult = OpenAPIV3Parser().readContents(content, null, parseOptions)
if (failOnParseErrors && parseResult.messages.orEmpty().isNotEmpty()) {
val sep = "\n - "
val messageBulletList = parseResult.messages.joinToString(sep)
throw RuntimeException("Swagger parsing failed with those errors:$sep$messageBulletList")
}
return parseResult?.openAPI?.let {
ResolverFully(true).resolveFully(it) // workaround for NPE bug in swagger-parser
Context(it)
}
}

fun createSwaggerContext(content: String): Context? =
SwaggerParser().readWithInfo(content, true)?.let {
val swagger = it.swagger ?: return null
val openApi = SwaggerConverter().convert(it)?.openAPI

openApi?.let {
try {
ResolverFully(true).resolveFully(it)
} catch (e: NullPointerException) {
log.warn("Failed to fully resolve Swagger schema.", e)
}
Context(openApi, swagger)
fun createSwaggerContext(content: String, failOnParseErrors: Boolean = false): Context? =
SwaggerParser().readWithInfo(content, true)?.let { parseResult ->
if (failOnParseErrors && parseResult.messages.orEmpty().isNotEmpty()) {
val sep = "\n - "
val messageBulletList = parseResult.messages.joinToString(sep)
throw RuntimeException("Swagger parsing failed with those errors:$sep$messageBulletList")
}
val swagger = parseResult.swagger ?: return null
val convertResult = SwaggerConverter().convert(parseResult)
if (failOnParseErrors && convertResult.messages.orEmpty().isNotEmpty()) {
val sep = "\n - "
val messageBulletList = parseResult.messages.joinToString(sep)
throw RuntimeException("Swagger conversion to OpenAPI 3 failed with those errors:$sep$messageBulletList")
}
convertResult?.openAPI?.let {
try {
ResolverFully(true).resolveFully(it)
} catch (e: NullPointerException) {
log.warn("Failed to fully resolve Swagger schema.", e)
if (failOnParseErrors) throw e
}
Context(it, swagger)
}
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ package de.zalando.zally.rule.zalando

import com.typesafe.config.Config
import de.zalando.zally.rule.Context
import de.zalando.zally.rule.HttpHeadersRuleWithContext
import de.zalando.zally.rule.HttpHeadersRule
import de.zalando.zally.rule.api.Check
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.getAllHeaders
import org.springframework.beans.factory.annotation.Autowired

@Rule(
Expand All @@ -15,16 +16,17 @@ import org.springframework.beans.factory.annotation.Autowired
severity = Severity.MUST,
title = "Avoid Link in Header Rule"
)
class AvoidLinkHeadersRule(@Autowired rulesConfig: Config) : HttpHeadersRuleWithContext(rulesConfig) {
class AvoidLinkHeadersRule(@Autowired rulesConfig: Config) {

private val headersWhitelist = rulesConfig.getStringList(HttpHeadersRule::class.simpleName + ".whitelist").toSet()

private val description = "Do Not Use Link Headers with JSON entities"

@Check(severity = Severity.MUST)
override fun validate(context: Context): List<Violation> {
return super.validate(context)
fun validate(context: Context): List<Violation> {
val allHeaders = context.api.getAllHeaders()
return allHeaders
.filter { it.name !in headersWhitelist && it.name == "Link" }
.map { context.violation(description, it.element) } // createViolation(context, it) }
}

override fun isViolation(header: HeaderElement) = header.name == "Link"

override fun createViolation(context: Context, header: HeaderElement): Violation =
context.violation(description, header.element)
}
37 changes: 37 additions & 0 deletions server/src/main/java/de/zalando/zally/util/OpenApiUtil.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package de.zalando.zally.util

import io.swagger.v3.oas.models.OpenAPI
import io.swagger.v3.oas.models.parameters.Parameter
import io.swagger.v3.oas.models.responses.ApiResponse

data class HeaderElement(
val name: String,
val element: Any
)

fun OpenAPI.getAllHeaders(): Set<HeaderElement> {

fun Collection<Parameter>?.extractHeaders() = orEmpty()
.filter { it.`in` == "header" }
.map { HeaderElement(it.name, it) }
.toSet()

fun Collection<ApiResponse>?.extractHeaders() = orEmpty()
.flatMap { it.headers.orEmpty().entries }
.map { HeaderElement(it.key, it.value) }
.toSet()

val fromParams = components.parameters.orEmpty().values.extractHeaders()

val fromPaths = paths.orEmpty().flatMap { (_, path) ->
val fromPathParameters = path.parameters.extractHeaders()
val fromOperations = path.readOperations().flatMap { operation ->
val fromOpParams = operation.parameters.extractHeaders()
val fromOpResponses = operation.responses.orEmpty().values.extractHeaders()
fromOpParams + fromOpResponses
}
fromPathParameters + fromOperations
}

return fromParams + fromPaths
}
Original file line number Diff line number Diff line change
@@ -1,37 +1,138 @@
package de.zalando.zally.rule.zalando

import com.fasterxml.jackson.core.JsonPointer
import de.zalando.zally.getContextFromFixture
import de.zalando.zally.rule.Context
import de.zalando.zally.rule.api.Violation
import de.zalando.zally.testConfig
import org.assertj.core.api.Assertions.assertThat
import org.intellij.lang.annotations.Language
import org.junit.Test

class AvoidLinkHeadersRuleTest {

private val rule = AvoidLinkHeadersRule(testConfig)

@Test
fun positiveCaseSpp() {
val context = getContextFromFixture("api_spp.json")
fun `a Swagger API with no header called Link produces no violation`() {
@Language("YAML")
val context = Context.createSwaggerContext("""
swagger: 2.0
info:
title: Clean Swagger API
paths:
/foo:
get:
description: Lorem Ipsum
responses:
202:
description: Lorem Ipsum
headers:
Location:
type: string
format: url
parameters:
FlowId:
name: X-Flow-Id
in: header
type: string
required: false
Authorization:
name: Authorization
in: header
type: string
required: true
ProductId:
name: product_id
in: path
type: string
required: true
""".trimIndent(), failOnParseErrors = true)!!
val violations = rule.validate(context)
assertThat(violations).isEmpty()
}

@Test
fun positiveCaseSpa() {
val context = getContextFromFixture("api_spa.yaml")
fun `an OpenAPI 3 API with no header called Link produces no violation`() {
@Language("YAML")
val context = Context.createOpenApiContext("""
openapi: 3.0.0
info:
title: Clean Swagger API
version: 1.0.0
paths:
/foo:
get:
description: Lorem Ipsum
responses:
202:
description: Lorem Ipsum
headers:
Location:
schema:
type: string
format: url
components:
parameters:
FlowId:
name: X-Flow-Id
in: header
required: false
schema:
type: string
Authorization:
name: Authorization
in: header
required: true
schema:
type: string
ProductId:
name: product_id
in: path
required: true
schema:
type: string
""".trimIndent(), failOnParseErrors = true)!!
val violations = rule.validate(context)
assertThat(violations).isEmpty()
}

@Test
fun negativeCase() {
val context = getContextFromFixture("avoidLinkHeaderRuleInvalid.json")
fun `an API with Link headers causes violations`() {
@Language("YAML")
val context = Context.createSwaggerContext("""
swagger: 2.0
info:
title: Clean Swagger API
paths:
/foo:
get:
parameters:
- name: Authorization
in: header
type: string
- name: Link
in: header
type: string
responses:
202:
description: Lorem Ipsum
headers:
Location:
type: string
format: url
post:
responses:
202:
description: Lorem Ipsum
headers:
Link:
type: string
format: url
""".trimIndent(), failOnParseErrors = true)!!
val violations = rule.validate(context)
assertThat(violations).hasSameElementsAs(listOf(
v("/paths/~1products/get/parameters/4"),
v("/paths/~1product-put-requests~1{product_path}/post/responses/202/headers/Link")
v("/paths/~1foo/get/parameters/1"),
v("/paths/~1foo/post/responses/202/headers/Link")
))
}

Expand Down

0 comments on commit 1a144a9

Please sign in to comment.