Skip to content

Commit a3d87d8

Browse files
Merge pull request #37 from apiaddicts/feat/29/validate_properties_in_object
fix: new rule oar115 analize value in the required fields on schemas
2 parents 2c3f493 + 887e09d commit a3d87d8

File tree

15 files changed

+509
-1
lines changed

15 files changed

+509
-1
lines changed

src/main/java/apiaddicts/sonar/openapi/checks/RulesLists.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ public static List<Class<?>> getFormatChecks() {
5151
OAR051DescriptionDiffersSummaryCheck.class,
5252
OAR110LicenseInformationCheck.class,
5353
OAR111ContactInformationCheck.class,
54-
OAR113CustomFieldCheck.class
54+
OAR113CustomFieldCheck.class,
55+
OAR115VerifyRequiredFields.class
5556
);
5657
}
5758

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package apiaddicts.sonar.openapi.checks.format;
2+
3+
import apiaddicts.sonar.openapi.checks.BaseCheck;
4+
import static apiaddicts.sonar.openapi.utils.JsonNodeUtils.isExternalRef;
5+
import static apiaddicts.sonar.openapi.utils.JsonNodeUtils.resolve;
6+
import com.google.common.collect.ImmutableSet;
7+
import com.sonar.sslr.api.AstNodeType;
8+
import java.util.HashSet;
9+
import java.util.Set;
10+
import org.apiaddicts.apitools.dosonarapi.api.v2.OpenApi2Grammar;
11+
import org.apiaddicts.apitools.dosonarapi.api.v3.OpenApi3Grammar;
12+
import org.apiaddicts.apitools.dosonarapi.api.v31.OpenApi31Grammar;
13+
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
14+
import org.sonar.check.Rule;
15+
16+
@Rule(key = OAR115VerifyRequiredFields.KEY)
17+
public class OAR115VerifyRequiredFields extends BaseCheck {
18+
public static final String KEY = "OAR115";
19+
20+
protected JsonNode externalRefNode = null;
21+
22+
23+
@Override
24+
public Set<AstNodeType> subscribedKinds() {
25+
return ImmutableSet.of(OpenApi2Grammar.SCHEMA,OpenApi2Grammar.RESPONSE, OpenApi3Grammar.SCHEMA,OpenApi31Grammar.SCHEMA,OpenApi3Grammar.RESPONSE, OpenApi31Grammar.RESPONSE);
26+
}
27+
28+
@Override
29+
public void visitNode(JsonNode node) {
30+
if(node.getType() == OpenApi3Grammar.RESPONSE || node.getType() == OpenApi31Grammar.RESPONSE ){
31+
JsonNode content = node.get("content");
32+
JsonNode json = content.get("application/json");
33+
JsonNode schema = json.get("schema");
34+
resolveExteralRef(schema);
35+
}else if(node.getType() == OpenApi2Grammar.RESPONSE){
36+
JsonNode schema = node.get("schema");
37+
resolveExteralRef(schema);
38+
} else {
39+
verifyTypeObject(node);
40+
}
41+
}
42+
43+
44+
public void resolveExteralRef(JsonNode node) {
45+
if(!"null".equals(node.getTokenValue())){
46+
boolean externalRefManagement = false;
47+
if (isExternalRef(node) && externalRefNode == null) {
48+
externalRefNode = node;
49+
externalRefManagement = true;
50+
}
51+
52+
node = resolve(node);
53+
validateRequiredFields(node);
54+
if (externalRefManagement) externalRefNode = null;
55+
}
56+
}
57+
58+
public void verifyTypeObject(JsonNode node){
59+
JsonNode typeNode = node.get("type");
60+
if (typeNode != null && "object".equals(typeNode.getTokenValue())) {
61+
validateRequiredFields(node);
62+
}
63+
}
64+
65+
66+
private void validateRequiredFields(JsonNode objectNode) {
67+
JsonNode requiredNode = objectNode.get("required");
68+
JsonNode propertiesNode = objectNode.get("properties");
69+
70+
if (requiredNode == null || !requiredNode.isArray()) return;
71+
72+
Set<String> properties = new HashSet<>();
73+
if (propertiesNode != null) {
74+
for (JsonNode property : propertiesNode.getJsonChildren()) {
75+
String propertyName = property.key().getTokenValue();
76+
if(!"null".equals(propertyName)){
77+
properties.add(propertyName);
78+
}
79+
}
80+
}
81+
82+
for (JsonNode requiredField : requiredNode.elements()) {
83+
String requiredName = requiredField.getTokenValue();
84+
if (!properties.contains(requiredName)) {
85+
addIssue(KEY,
86+
translate("OAR115.error"),
87+
requiredField);
88+
}
89+
}
90+
}
91+
}

src/main/resources/messages/errors.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ OAR109.error=Use default instead of directly specifying 5XX codes
115115
OAR110.error=License information cannot be empty
116116
OAR111.error=Contact information cannot be empty
117117
OAR113.error=Field or extension {0} must be at the assigned location
118+
OAR115.error=This value does not exist, it must be defined in the schema properties
118119
generic.section=Section {0} is mandatory
119120
generic.consume=Should indicate the default request media type
120121
generic.produce=Should indicate the default response media type

src/main/resources/messages/errors_es.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ OAR109.error=Utilice default en lugar de especificar códigos 5XX directamente
115115
OAR110.error=La información de licencia no puede estar vacía
116116
OAR111.error=La información de contacto no puede estar vacía
117117
OAR113.error=El campo o extensión {0} debe estar en la ubicación asignada.
118+
OAR115.error=Este valor no existe, debe estár definido en las propiedades del esquema
118119
generic.section=La sección {0} es obligatoria
119120
generic.consume=Debe indicar el tipo de medio de solicitud predeterminado
120121
generic.produce=Debe indicar el tipo de medio de respuesta predeterminado
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<p>The data in the required field must exist in schema parameters</p>
2+
<h2>Noncompliant Solution (OpenAPI 2)</h2>
3+
<pre>
4+
swagger: "2.0"
5+
info:
6+
title: API de ejemplo
7+
version: "1.0.0"
8+
paths: {}
9+
definitions:
10+
ErrorResponse:
11+
type: object
12+
properties:
13+
code:
14+
type: integer
15+
message:
16+
type: string
17+
required:
18+
- code
19+
- message
20+
- otherfield # Noncompliant {{OAR115: This value does not exist, it must be defined in the schema properties}}
21+
22+
</pre>
23+
<h2>Compliant Solution (OpenAPI 2)</h2>
24+
<pre>
25+
swagger: "2.0"
26+
info:
27+
title: API de ejemplo
28+
version: "1.0.0"
29+
paths: {}
30+
definitions:
31+
ErrorResponse:
32+
type: object
33+
properties:
34+
code:
35+
type: integer
36+
message:
37+
type: string
38+
required:
39+
- code
40+
- message
41+
</pre>
42+
<h2>Noncompliant Solution (OpenAPI 3)</h2>
43+
<pre>
44+
openapi: 3.0.0
45+
info:
46+
title: API de ejemplo
47+
version: "1.0.0"
48+
paths: {}
49+
components:
50+
schemas:
51+
ErrorResponse:
52+
type: object
53+
properties:
54+
code:
55+
type: integer
56+
message:
57+
type: string
58+
required:
59+
- code
60+
- message
61+
- otherfield # Noncompliant {{OAR115: This value does not exist, it must be defined in the schema properties}}
62+
63+
</pre>
64+
<h2>Compliant Solution (OpenAPI 3)</h2>
65+
<pre>
66+
openapi: 3.0.3
67+
info:
68+
title: API de ejemplo
69+
version: "1.0.0"
70+
paths: {}
71+
components:
72+
schemas:
73+
ErrorResponse:
74+
type: object
75+
properties:
76+
code:
77+
type: integer
78+
message:
79+
type: string
80+
required:
81+
- code
82+
- message
83+
</pre>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"title": "OAR115 - VerifyRequiredFields - the data in the required field must exist in schema parameters",
3+
"type": "BUG",
4+
"status": "ready",
5+
"remediation": {
6+
"func": "Constant\/Issue",
7+
"constantCost": "30min"
8+
},
9+
"tags": [
10+
"format"
11+
],
12+
"defaultSeverity": "MINOR"
13+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.sonar.samples.openapi.checks.format;
2+
3+
import apiaddicts.sonar.openapi.checks.format.OAR115VerifyRequiredFields;
4+
import org.junit.Before;
5+
import org.junit.Test;
6+
import org.sonar.api.rule.Severity;
7+
import org.sonar.api.rules.RuleType;
8+
import org.sonar.samples.openapi.BaseCheckTest;
9+
10+
public class OAR115VerifyRequiredFieldsTest extends BaseCheckTest {
11+
@Before
12+
public void init() {
13+
ruleName = "OAR115";
14+
check = new OAR115VerifyRequiredFields();
15+
v2Path = getV2Path("format");
16+
v3Path = getV3Path("format");
17+
}
18+
19+
@Test
20+
public void verifyValidRequiredFieldV2() {
21+
verifyV2("valid");
22+
}
23+
@Test
24+
public void verifyInvalidRequiredFieldV2() {
25+
verifyV2("invalid");
26+
}
27+
28+
@Test
29+
public void verifyValidRequiredFieldV3() {
30+
verifyV3("valid");
31+
}
32+
@Test
33+
public void verifyInvalidRequiredFieldV3() {
34+
verifyV3("invalid");
35+
}
36+
37+
38+
@Override
39+
public void verifyRule() {
40+
assertRuleProperties("OAR115 - VerifyRequiredFields - the data in the required field must exist in schema parameters", RuleType.BUG, Severity.MINOR, tags("format"));
41+
}
42+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"swagger": "2.0",
3+
"info": {
4+
"title": "API de ejemplo",
5+
"version": "1.0.0"
6+
},
7+
"paths": {
8+
"/users": {
9+
"get": {
10+
"summary": "Obtener lista de usuarios",
11+
"responses": {
12+
"200": {
13+
"description": "OK"
14+
},
15+
"400": {
16+
"description": "Error de validación",
17+
"schema": {
18+
"$ref": "#/definitions/ErrorResponse"
19+
}
20+
}
21+
}
22+
}
23+
}
24+
},
25+
"definitions": {
26+
"ErrorResponse": {
27+
"type": "object",
28+
"properties": {
29+
"code": { "type": "integer" },
30+
"message": { "type": "string" }
31+
},
32+
"required":
33+
[
34+
"code",
35+
"message",
36+
"otherfield" # Noncompliant {{OAR115: This value does not exist, it must be defined in the schema properties}}
37+
]
38+
}
39+
}
40+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
swagger: "2.0"
2+
info:
3+
title: API de ejemplo
4+
version: "1.0.0"
5+
paths:
6+
/users:
7+
get:
8+
summary: Obtener lista de usuarios
9+
responses:
10+
200:
11+
description: OK
12+
400:
13+
description: Error de validación
14+
schema:
15+
$ref: "#/definitions/ErrorResponse"
16+
definitions:
17+
ErrorResponse:
18+
type: object
19+
properties:
20+
code:
21+
type: integer
22+
message:
23+
type: string
24+
required:
25+
- code
26+
- message
27+
- otherfield # Noncompliant {{OAR115: This value does not exist, it must be defined in the schema properties}}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"swagger": "2.0",
3+
"info": {
4+
"title": "API de ejemplo",
5+
"version": "1.0.0"
6+
},
7+
"paths": {
8+
"/users": {
9+
"get": {
10+
"summary": "Obtener lista de usuarios",
11+
"responses": {
12+
"200": {
13+
"description": "OK"
14+
},
15+
"400": {
16+
"description": "Error de validación",
17+
"schema": {
18+
"$ref": "#/definitions/ErrorResponse"
19+
}
20+
}
21+
}
22+
}
23+
}
24+
},
25+
"definitions": {
26+
"ErrorResponse": {
27+
"type": "object",
28+
"properties": {
29+
"code": { "type": "integer" },
30+
"message": { "type": "string" }
31+
},
32+
"required": ["code", "message"]
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)