Skip to content

Commit 34a8ccc

Browse files
author
Jay Anslow
committed
Merge branch 'feature/collection-rules-use-swagger-validate' into 'develop'
Feature/collection rules use swagger validate Closes #2 See merge request pdds/zally!39
2 parents 78936f2 + e2fb6eb commit 34a8ccc

15 files changed

+201
-65
lines changed

server/src/main/kotlin/com/corefiling/pdds/zally/extensions/SwaggerExtensions.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import de.zalando.zally.rule.api.Violation
55
import io.swagger.models.HttpMethod
66
import io.swagger.models.Operation
77
import io.swagger.models.Path
8+
import io.swagger.models.Response
89
import io.swagger.models.Swagger
910
import io.swagger.models.parameters.Parameter
1011

@@ -36,6 +37,14 @@ private fun validateParameter(description: String, swagger: Swagger, toMessages:
3637
}
3738
}
3839

40+
private fun validateResponse(description: String, swagger: Swagger, toMessages: (pattern: String, path: Path, method: HttpMethod, operation: Operation, status: String, response: Response) -> List<String>) =
41+
validateOperation(description, swagger) { pattern, path, method, operation ->
42+
operation.responses
43+
.flatMap { (status, response) ->
44+
toMessages(pattern, path, method, operation, status, response).map { "response $status $it" }
45+
}
46+
}
47+
3948
private fun String?.normalizeLocations() = this?.let { listOf(it) }.orEmpty()
4049

4150
fun Swagger.validatePath(description: String, getViolationMessage: (pattern: String, path: Path) -> String?) =
@@ -52,3 +61,8 @@ fun Swagger.validateParameter(description: String, getViolationMessage: (pattern
5261
validateParameter(description, this) { pattern, path, method, operation, parameter ->
5362
getViolationMessage(pattern, path, method, operation, parameter).normalizeLocations()
5463
}
64+
65+
fun Swagger.validateResponse(description: String, getViolationMessage: (pattern: String, path: Path, method: HttpMethod, operation: Operation, status: String, response: Response) -> String?) =
66+
validateResponse(description, this) { pattern, path, method, operation, status, response ->
67+
getViolationMessage(pattern, path, method, operation, status, response).normalizeLocations()
68+
}

server/src/main/kotlin/com/corefiling/pdds/zally/rule/collections/CollectionsReturnArrays.kt

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package com.corefiling.pdds.zally.rule.collections
22

3+
import com.corefiling.pdds.zally.extensions.validateResponse
34
import com.corefiling.pdds.zally.rule.CoreFilingRuleSet
45
import de.zalando.zally.rule.api.Check
56
import de.zalando.zally.rule.api.Rule
67
import de.zalando.zally.rule.api.Severity
78
import de.zalando.zally.rule.api.Violation
89
import io.swagger.models.ArrayModel
10+
import io.swagger.models.HttpMethod
911
import io.swagger.models.Model
1012
import io.swagger.models.Response
1113
import io.swagger.models.Swagger
@@ -24,16 +26,13 @@ class CollectionsReturnArrays {
2426

2527
@Check(Severity.MUST)
2628
fun validate(swagger: Swagger): Violation? =
27-
swagger.collections()
28-
.flatMap { (pattern, path) ->
29-
path.get?.responses.orEmpty()
30-
.filterKeys { Integer.parseInt(it) in 200..299 }
31-
.filterValues { !isArrayResponse(it, swagger) }
32-
.map { (code, response) ->
33-
"paths $pattern GET responses $code schema type: expected array but found ${response?.schema?.type}"
34-
}
35-
}
36-
.ifNotEmptyLet { Violation(description, it) }
29+
swagger.validateResponse(description) { pattern, path, method, _, status, response ->
30+
"schema type: expected array but found ${response?.schema?.type}"
31+
.onlyIf(method == HttpMethod.GET
32+
&& swagger.isCollection(pattern, path)
33+
&& Integer.parseInt(status) in 200..299
34+
&& !isArrayResponse(response, swagger))
35+
}
3736

3837
private fun isArrayResponse(response: Response, swagger: Swagger): Boolean {
3938
val schema = response.schema ?: return false

server/src/main/kotlin/com/corefiling/pdds/zally/rule/collections/CollectionsReturnTotalItemsHeader.kt

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package com.corefiling.pdds.zally.rule.collections
22

3+
import com.corefiling.pdds.zally.extensions.validateResponse
34
import com.corefiling.pdds.zally.rule.CoreFilingRuleSet
45
import de.zalando.zally.rule.api.Check
56
import de.zalando.zally.rule.api.Rule
67
import de.zalando.zally.rule.api.Severity
78
import de.zalando.zally.rule.api.Violation
9+
import io.swagger.models.HttpMethod
810
import io.swagger.models.Response
911
import io.swagger.models.Swagger
1012

@@ -20,16 +22,13 @@ class CollectionsReturnTotalItemsHeader {
2022

2123
@Check(Severity.SHOULD)
2224
fun validate(swagger: Swagger): Violation? =
23-
swagger.collections()
24-
.flatMap { (pattern, path) ->
25-
path.get?.responses.orEmpty()
26-
.filterKeys { Integer.parseInt(it) in 200..299 }
27-
.filterValues { !hasTotalItemsHeader(it) }
28-
.map { (code, _) ->
29-
"paths $pattern GET responses $code headers: does not include an int32 format integer Total-Items header"
30-
}
31-
}
32-
.ifNotEmptyLet { Violation(description, it) }
25+
swagger.validateResponse(description) { pattern, path, method, _, status, response ->
26+
"headers: does not include an int32 format integer Total-Items header"
27+
.onlyIf(method == HttpMethod.GET
28+
&& swagger.isCollection(pattern, path)
29+
&& Integer.parseInt(status) in 200..299
30+
&& !hasTotalItemsHeader(response))
31+
}
3332

3433
private fun hasTotalItemsHeader(response: Response?): Boolean {
3534
val header = response?.headers?.get("Total-Items") ?: return false

server/src/main/kotlin/com/corefiling/pdds/zally/rule/collections/PaginatedCollectionsReturnTotalPagesHeader.kt

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package com.corefiling.pdds.zally.rule.collections
22

3+
import com.corefiling.pdds.zally.extensions.validateResponse
34
import com.corefiling.pdds.zally.rule.CoreFilingRuleSet
45
import de.zalando.zally.rule.api.Check
56
import de.zalando.zally.rule.api.Rule
67
import de.zalando.zally.rule.api.Severity
78
import de.zalando.zally.rule.api.Violation
9+
import io.swagger.models.HttpMethod
810
import io.swagger.models.Response
911
import io.swagger.models.Swagger
1012

@@ -20,16 +22,13 @@ class PaginatedCollectionsReturnTotalPagesHeader {
2022

2123
@Check(Severity.SHOULD)
2224
fun validate(swagger: Swagger): Violation? =
23-
swagger.collections()
24-
.flatMap { (pattern, path) ->
25-
path.get?.responses.orEmpty()
26-
.filterKeys { Integer.parseInt(it) in 200..299 }
27-
.filterValues { !hasTotalPagesHeader(it) }
28-
.map { (code, _) ->
29-
"paths $pattern GET responses $code headers: does not include an int32 format integer Total-Pages header"
30-
}
31-
}
32-
.ifNotEmptyLet { Violation(description, it) }
25+
swagger.validateResponse(description) { pattern, path, method, _, status, response ->
26+
"headers: does not include an int32 format integer Total-Pages header"
27+
.onlyIf(method == HttpMethod.GET
28+
&& swagger.isCollection(pattern, path)
29+
&& Integer.parseInt(status) in 200..299
30+
&& !hasTotalPagesHeader(response))
31+
}
3332

3433
private fun hasTotalPagesHeader(response: Response?): Boolean {
3534
val header = response?.headers?.get("Total-Pages") ?: return false

server/src/main/kotlin/com/corefiling/pdds/zally/rule/collections/PaginatedCollectionsSupportPageNumberQueryParameter.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package com.corefiling.pdds.zally.rule.collections
22

3+
import com.corefiling.pdds.zally.extensions.validateOperation
34
import com.corefiling.pdds.zally.rule.CoreFilingRuleSet
45
import de.zalando.zally.rule.api.Check
56
import de.zalando.zally.rule.api.Rule
67
import de.zalando.zally.rule.api.Severity
78
import de.zalando.zally.rule.api.Violation
9+
import io.swagger.models.HttpMethod
810
import io.swagger.models.Operation
911
import io.swagger.models.Swagger
1012
import io.swagger.models.parameters.Parameter
@@ -23,14 +25,12 @@ class PaginatedCollectionsSupportPageNumberQueryParameter {
2325

2426
@Check(Severity.SHOULD)
2527
fun validate(swagger: Swagger): Violation? =
26-
swagger.collections()
27-
.map { (pattern, path) ->
28-
when {
29-
(hasPageNumberQueryParam(path.get)) -> null
30-
else -> "paths $pattern GET parameters: does not include a valid pageNumber query parameter"
31-
}
32-
}
33-
.ifNotEmptyLet { Violation(description, it) }
28+
swagger.validateOperation(description) { pattern, path, method, _ ->
29+
"parameters: does not include a valid pageNumber query parameter"
30+
.onlyIf(method == HttpMethod.GET
31+
&& swagger.isCollection(pattern, path)
32+
&& !hasPageNumberQueryParam(path.get))
33+
}
3434

3535
private fun hasPageNumberQueryParam(op: Operation?): Boolean =
3636
op?.parameters?.find { isPageNumberQueryParam(it) } != null
@@ -48,4 +48,4 @@ class PaginatedCollectionsSupportPageNumberQueryParameter {
4848
else -> true
4949
}
5050
}
51-
}
51+
}

server/src/main/kotlin/com/corefiling/pdds/zally/rule/collections/PaginatedCollectionsSupportPageSizeQueryParameter.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package com.corefiling.pdds.zally.rule.collections
22

3+
import com.corefiling.pdds.zally.extensions.validateOperation
34
import com.corefiling.pdds.zally.rule.CoreFilingRuleSet
45
import de.zalando.zally.rule.api.Check
56
import de.zalando.zally.rule.api.Rule
67
import de.zalando.zally.rule.api.Severity
78
import de.zalando.zally.rule.api.Violation
9+
import io.swagger.models.HttpMethod
810
import io.swagger.models.Operation
911
import io.swagger.models.Swagger
1012
import io.swagger.models.parameters.Parameter
@@ -23,14 +25,12 @@ class PaginatedCollectionsSupportPageSizeQueryParameter {
2325

2426
@Check(Severity.SHOULD)
2527
fun validate(swagger: Swagger): Violation? =
26-
swagger.collections()
27-
.map { (pattern, path) ->
28-
when {
29-
hasPageSizeQueryParam(path.get) -> null
30-
else -> "paths $pattern GET parameters: does not include a valid pageSize query parameter"
31-
}
32-
}
33-
.ifNotEmptyLet { Violation(description, it) }
28+
swagger.validateOperation(description) { pattern, path, method, _ ->
29+
"parameters: does not include a valid pageSize query parameter"
30+
.onlyIf(method == HttpMethod.GET
31+
&& swagger.isCollection(pattern, path)
32+
&& !hasPageSizeQueryParam(path.get))
33+
}
3434

3535
private fun hasPageSizeQueryParam(op: Operation?): Boolean =
3636
op?.parameters?.find { isPageSizeQueryParam(it) } != null
@@ -47,4 +47,4 @@ class PaginatedCollectionsSupportPageSizeQueryParameter {
4747
else -> true
4848
}
4949
}
50-
}
50+
}

server/src/main/kotlin/com/corefiling/pdds/zally/rule/collections/Utils.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ fun Swagger.collections(): Map<String, Path> = collectionPaths(this)
1818

1919
fun collectionPaths(swagger: Swagger?): Map<String, Path> {
2020
return swagger?.paths.orEmpty().filter {
21-
entry: Entry<String, Path> -> detectCollection(swagger!!, entry.key, entry.value)
21+
entry: Entry<String, Path> -> swagger!!.isCollection(entry.key, entry.value)
2222
}
2323
}
2424

25-
fun detectCollection(swagger: Swagger, pattern: String, path: Path): Boolean {
26-
return detectCollectionByGetResponseReturningArray(swagger, path) ||
27-
detectCollectionByParameterizedSubresources(swagger, pattern) ||
28-
detectCollectionByPaginationQueryParameters(swagger, path)
25+
fun Swagger.isCollection(pattern: String, path: Path): Boolean {
26+
return detectCollectionByGetResponseReturningArray(this, path) ||
27+
detectCollectionByParameterizedSubresources(this, pattern) ||
28+
detectCollectionByPaginationQueryParameters(this, path)
2929
}
3030

3131
fun detectCollectionByGetResponseReturningArray(swagger: Swagger, path: Path): Boolean {
@@ -78,3 +78,7 @@ inline fun <I : Any, O> Iterable<I?>.ifNotEmptyLet(block: (List<I>) -> O): O? {
7878
val nonNull = this.filterNotNull()
7979
return if (nonNull.isEmpty()) null else block(nonNull)
8080
}
81+
82+
fun String.onlyIf(condition: Boolean): String? {
83+
return if (condition) this else null
84+
}

server/src/main/kotlin/com/corefiling/pdds/zally/rule/operations/PostResponding200ConsideredSuspicious.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package com.corefiling.pdds.zally.rule.operations
22

33
import com.corefiling.pdds.zally.extensions.validateOperation
44
import com.corefiling.pdds.zally.rule.CoreFilingRuleSet
5-
import com.corefiling.pdds.zally.rule.collections.detectCollection
5+
import com.corefiling.pdds.zally.rule.collections.isCollection
66
import de.zalando.zally.rule.api.Check
77
import de.zalando.zally.rule.api.Rule
88
import de.zalando.zally.rule.api.Severity
@@ -25,8 +25,8 @@ class PostResponding200ConsideredSuspicious {
2525
when {
2626
method != HttpMethod.POST -> null
2727
op.responses["200"] == null -> null
28-
detectCollection(swagger, pattern, path) -> "response 200 OK probably should be a 201 Created"
28+
swagger.isCollection(pattern, path) -> "response 200 OK probably should be a 201 Created"
2929
else -> "response 200 OK probably should be a 202 Accepted"
3030
}
3131
}
32-
}
32+
}

server/src/test/java/de/zalando/zally/rule/CoreFilingAPITest.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ class CoreFilingAPITest {
114114
assertEmptyResults(results)
115115
}
116116

117+
@Test
118+
fun `organisation-service`() {
119+
val results = validate("platform", "organisation-service", policy)
120+
121+
assertEmptyResults(results)
122+
}
123+
117124
@Test
118125
fun `table-rendering-service`() {
119126
val results = validate("platform", "table-rendering-service",
@@ -198,7 +205,11 @@ class CoreFilingAPITest {
198205
val results = validate("tms", "taxonomy-modelling-service",
199206
// ignoring rules that historically failed for this service
200207
policy.withMoreIgnores(listOf(
208+
"CollectionsReturnTotalItemsHeader",
201209
"MatchingSummaryAndOperationIdNames",
210+
"PaginatedCollectionsReturnTotalPagesHeader",
211+
"PaginatedCollectionsSupportPageNumberQueryParameter",
212+
"PaginatedCollectionsSupportPageSizeQueryParameter",
202213
"101", // UseOpenApiRule
203214
"146", // LimitNumberOfResourcesRule
204215
"150", // UseSpecificHttpStatusCodes

server/src/test/kotlin/com/corefiling/pdds/zally/rule/collections/CollectionsArePluralTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,4 @@ paths:
9999
Assertions.assertThat(cut.validate(SwaggerParser().parse(yaml))!!.paths)
100100
.hasSameElementsAs(listOf("paths /path/to/taxonomy-package/: 'package' appears to be singular"))
101101
}
102-
}
102+
}

0 commit comments

Comments
 (0)