Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ public static List<Class<?>> getFormatChecks() {
OAR051DescriptionDiffersSummaryCheck.class,
OAR110LicenseInformationCheck.class,
OAR111ContactInformationCheck.class,
OAR113CustomFieldCheck.class
OAR113CustomFieldCheck.class,
OAR115VerifyRequiredFields.class
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package apiaddicts.sonar.openapi.checks.format;

import apiaddicts.sonar.openapi.checks.BaseCheck;
import static apiaddicts.sonar.openapi.utils.JsonNodeUtils.isExternalRef;
import static apiaddicts.sonar.openapi.utils.JsonNodeUtils.resolve;
import com.google.common.collect.ImmutableSet;
import com.sonar.sslr.api.AstNodeType;
import java.util.HashSet;
import java.util.Set;
import org.apiaddicts.apitools.dosonarapi.api.v2.OpenApi2Grammar;
import org.apiaddicts.apitools.dosonarapi.api.v3.OpenApi3Grammar;
import org.apiaddicts.apitools.dosonarapi.api.v31.OpenApi31Grammar;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
import org.sonar.check.Rule;

@Rule(key = OAR115VerifyRequiredFields.KEY)
public class OAR115VerifyRequiredFields extends BaseCheck {
public static final String KEY = "OAR115";

protected JsonNode externalRefNode = null;


@Override
public Set<AstNodeType> subscribedKinds() {
return ImmutableSet.of(OpenApi2Grammar.SCHEMA,OpenApi2Grammar.RESPONSE, OpenApi3Grammar.SCHEMA,OpenApi31Grammar.SCHEMA,OpenApi3Grammar.RESPONSE, OpenApi31Grammar.RESPONSE);
}

@Override
public void visitNode(JsonNode node) {
if(node.getType() == OpenApi3Grammar.RESPONSE || node.getType() == OpenApi31Grammar.RESPONSE ){
JsonNode content = node.get("content");
JsonNode json = content.get("application/json");
JsonNode schema = json.get("schema");
resolveExteralRef(schema);
}else if(node.getType() == OpenApi2Grammar.RESPONSE){
JsonNode schema = node.get("schema");
resolveExteralRef(schema);
} else {
verifyTypeObject(node);
}
}


public void resolveExteralRef(JsonNode node) {
if(!"null".equals(node.getTokenValue())){
boolean externalRefManagement = false;
if (isExternalRef(node) && externalRefNode == null) {
externalRefNode = node;
externalRefManagement = true;
}

node = resolve(node);
validateRequiredFields(node);
if (externalRefManagement) externalRefNode = null;
}
}

public void verifyTypeObject(JsonNode node){
JsonNode typeNode = node.get("type");
if (typeNode != null && "object".equals(typeNode.getTokenValue())) {
validateRequiredFields(node);
}
}


private void validateRequiredFields(JsonNode objectNode) {
JsonNode requiredNode = objectNode.get("required");
JsonNode propertiesNode = objectNode.get("properties");

if (requiredNode == null || !requiredNode.isArray()) return;

Set<String> properties = new HashSet<>();
if (propertiesNode != null) {
for (JsonNode property : propertiesNode.getJsonChildren()) {
String propertyName = property.key().getTokenValue();
if(!"null".equals(propertyName)){
properties.add(propertyName);
}
}
}

for (JsonNode requiredField : requiredNode.elements()) {
String requiredName = requiredField.getTokenValue();
if (!properties.contains(requiredName)) {
addIssue(KEY,
translate("OAR115.error"),
requiredField);
}
}
}
}
1 change: 1 addition & 0 deletions src/main/resources/messages/errors.properties
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ OAR109.error=Use default instead of directly specifying 5XX codes
OAR110.error=License information cannot be empty
OAR111.error=Contact information cannot be empty
OAR113.error=Field or extension {0} must be at the assigned location
OAR115.error=This value does not exist, it must be defined in the schema properties
generic.section=Section {0} is mandatory
generic.consume=Should indicate the default request media type
generic.produce=Should indicate the default response media type
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/messages/errors_es.properties
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ OAR109.error=Utilice default en lugar de especificar códigos 5XX directamente
OAR110.error=La información de licencia no puede estar vacía
OAR111.error=La información de contacto no puede estar vacía
OAR113.error=El campo o extensión {0} debe estar en la ubicación asignada.
OAR115.error=Este valor no existe, debe estár definido en las propiedades del esquema
generic.section=La sección {0} es obligatoria
generic.consume=Debe indicar el tipo de medio de solicitud predeterminado
generic.produce=Debe indicar el tipo de medio de respuesta predeterminado
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<p>The data in the required field must exist in schema parameters</p>
<h2>Noncompliant Solution (OpenAPI 2)</h2>
<pre>
swagger: "2.0"
info:
title: API de ejemplo
version: "1.0.0"
paths: {}
definitions:
ErrorResponse:
type: object
properties:
code:
type: integer
message:
type: string
required:
- code
- message
- otherfield # Noncompliant {{OAR115: This value does not exist, it must be defined in the schema properties}}

</pre>
<h2>Compliant Solution (OpenAPI 2)</h2>
<pre>
swagger: "2.0"
info:
title: API de ejemplo
version: "1.0.0"
paths: {}
definitions:
ErrorResponse:
type: object
properties:
code:
type: integer
message:
type: string
required:
- code
- message
</pre>
<h2>Noncompliant Solution (OpenAPI 3)</h2>
<pre>
openapi: 3.0.0
info:
title: API de ejemplo
version: "1.0.0"
paths: {}
components:
schemas:
ErrorResponse:
type: object
properties:
code:
type: integer
message:
type: string
required:
- code
- message
- otherfield # Noncompliant {{OAR115: This value does not exist, it must be defined in the schema properties}}

</pre>
<h2>Compliant Solution (OpenAPI 3)</h2>
<pre>
openapi: 3.0.3
info:
title: API de ejemplo
version: "1.0.0"
paths: {}
components:
schemas:
ErrorResponse:
type: object
properties:
code:
type: integer
message:
type: string
required:
- code
- message
</pre>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"title": "OAR115 - VerifyRequiredFields - the data in the required field must exist in schema parameters",
"type": "BUG",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "30min"
},
"tags": [
"format"
],
"defaultSeverity": "MINOR"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.sonar.samples.openapi.checks.format;

import apiaddicts.sonar.openapi.checks.format.OAR115VerifyRequiredFields;
import org.junit.Before;
import org.junit.Test;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.RuleType;
import org.sonar.samples.openapi.BaseCheckTest;

public class OAR115VerifyRequiredFieldsTest extends BaseCheckTest {
@Before
public void init() {
ruleName = "OAR115";
check = new OAR115VerifyRequiredFields();
v2Path = getV2Path("format");
v3Path = getV3Path("format");
}

@Test
public void verifyValidRequiredFieldV2() {
verifyV2("valid");
}
@Test
public void verifyInvalidRequiredFieldV2() {
verifyV2("invalid");
}

@Test
public void verifyValidRequiredFieldV3() {
verifyV3("valid");
}
@Test
public void verifyInvalidRequiredFieldV3() {
verifyV3("invalid");
}


@Override
public void verifyRule() {
assertRuleProperties("OAR115 - VerifyRequiredFields - the data in the required field must exist in schema parameters", RuleType.BUG, Severity.MINOR, tags("format"));
}
}
40 changes: 40 additions & 0 deletions src/test/resources/checks/v2/format/OAR115/invalid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"swagger": "2.0",
"info": {
"title": "API de ejemplo",
"version": "1.0.0"
},
"paths": {
"/users": {
"get": {
"summary": "Obtener lista de usuarios",
"responses": {
"200": {
"description": "OK"
},
"400": {
"description": "Error de validación",
"schema": {
"$ref": "#/definitions/ErrorResponse"
}
}
}
}
}
},
"definitions": {
"ErrorResponse": {
"type": "object",
"properties": {
"code": { "type": "integer" },
"message": { "type": "string" }
},
"required":
[
"code",
"message",
"otherfield" # Noncompliant {{OAR115: This value does not exist, it must be defined in the schema properties}}
]
}
}
}
27 changes: 27 additions & 0 deletions src/test/resources/checks/v2/format/OAR115/invalid.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
swagger: "2.0"
info:
title: API de ejemplo
version: "1.0.0"
paths:
/users:
get:
summary: Obtener lista de usuarios
responses:
200:
description: OK
400:
description: Error de validación
schema:
$ref: "#/definitions/ErrorResponse"
definitions:
ErrorResponse:
type: object
properties:
code:
type: integer
message:
type: string
required:
- code
- message
- otherfield # Noncompliant {{OAR115: This value does not exist, it must be defined in the schema properties}}
35 changes: 35 additions & 0 deletions src/test/resources/checks/v2/format/OAR115/valid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"swagger": "2.0",
"info": {
"title": "API de ejemplo",
"version": "1.0.0"
},
"paths": {
"/users": {
"get": {
"summary": "Obtener lista de usuarios",
"responses": {
"200": {
"description": "OK"
},
"400": {
"description": "Error de validación",
"schema": {
"$ref": "#/definitions/ErrorResponse"
}
}
}
}
}
},
"definitions": {
"ErrorResponse": {
"type": "object",
"properties": {
"code": { "type": "integer" },
"message": { "type": "string" }
},
"required": ["code", "message"]
}
}
}
26 changes: 26 additions & 0 deletions src/test/resources/checks/v2/format/OAR115/valid.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
swagger: "2.0"
info:
title: API de ejemplo
version: "1.0.0"
paths:
/users:
get:
summary: Obtener lista de usuarios
responses:
200:
description: OK
400:
description: Error de validación
schema:
$ref: "#/definitions/ErrorResponse"
definitions:
ErrorResponse:
type: object
properties:
code:
type: integer
message:
type: string
required:
- code
- message
Loading
Loading