Skip to content

Commit 1eeccab

Browse files
committed
add meta information rule
1 parent 2b3e5cc commit 1eeccab

7 files changed

Lines changed: 272 additions & 0 deletions

File tree

docs/rules/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ Documentation about the rules can be found in the [folder](./implemented-rules).
5353
* [Camel case should be used (optional)](./implemented-rules/Camel-case-should-be-used.md)
5454
* [Must use official HTTP status codes](./implemented-rules/Must-use-official-HTTP-status-codes.md)
5555
* [Must use normalized paths without empty path segments](./implemented-rules/Must-use-normalized-paths.md)
56+
* [Must contain API meta information](./implemented-rules/Must-contain-API-meta-information.md)
5657

5758
### Rule implementation and extension
5859
For each rule, a single Java class is created, which can be found in in this [dir](../../src/main/java/cli/rule/rules). It is just as easy to implement a new rule. For implementing a new rule, it is merely necessary to create a Java class in the folder just mentioned, which implements the [`IRestRule`](../../src/main/java/cli/rule/IRestRule.java) interface. Then, a constructor with an `isActive` boolean is needed. Now the rule is automatically recognized and listed in the CLI. This is the minimum that needs to be done to implement a new rule.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Must contain API meta information
2+
3+
## Category
4+
5+
META
6+
7+
## Importance, severity, difficulty
8+
9+
* Importance: medium
10+
* Severity: warning
11+
* Difficulty to implement the rule: easy
12+
13+
## Quality Attribute
14+
15+
* Maintainability
16+
* Usability
17+
18+
## Rule Description
19+
20+
Description from Zalando [1].
21+
22+
"API specifications must contain the following OpenAPI meta information to allow for API management:
23+
24+
#/info/title as (unique) identifying, functional descriptive name of the API
25+
26+
#/info/version to distinguish API specifications versions following semantic rules
27+
28+
#/info/description containing a proper description of the API
29+
30+
#/info/contact/{name,url,email} containing the responsible team"
31+
32+
## Implemented
33+
34+
* Y
35+
36+
## Implementation Details
37+
38+
### What is checked
39+
40+
* Checks the info field of an OpenAPI spec
41+
* Checks whether the `title`, `version`, `description`, and `contact/{name, url, email}` fields are populated
42+
* Provides clear error messages if any of the fields are missing/empty
43+
44+
### What is not checked
45+
46+
* Other information components (such as `x-api-id` or `x-audience` etc.)
47+
48+
### Future work
49+
50+
* More fields that are required for clear documentation may be identified and checked
51+
52+
## Source
53+
54+
[1] https://opensource.zalando.com/restful-api-guidelines/#218

src/main/java/cli/rule/constants/ErrorMessage.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public class ErrorMessage {
2626
public static final String HTTP_STATUS_CODE_NOT_OFFICIAL = "HTTP status code %d is not an official HTTP status code";
2727
public static final String HTTP_STATUS_CODE_NOT_NUMERIC = "HTTP status code '%s' is not a numeric value";
2828
public static final String EMPTYPATHSEGMENT = "Path contains empty segments (//) which violates the path normalization rule.";
29+
public static final String METAINFO = "Make sure to include title, version, description, name, URL, and email in the information field of the API";
2930

3031

3132
private ErrorMessage() {

src/main/java/cli/rule/constants/RuleIdentifier.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ public enum RuleIdentifier {
1919
VERB_PHRASE,
2020
OFFICIAL_HTTP_STATUS_CODES,
2121
NORMALIZED_PATHS,
22+
META_INFO,
2223
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package cli.rule.rules;
2+
3+
import cli.rule.IRestRule;
4+
import cli.rule.Violation;
5+
import cli.rule.constants.*;
6+
import io.swagger.v3.oas.models.OpenAPI;
7+
import io.swagger.v3.oas.models.info.Contact;
8+
import io.swagger.v3.oas.models.info.Info;
9+
10+
import java.util.ArrayList;
11+
import java.util.List;
12+
13+
/**
14+
* This class implements the Rule "MUST contain API meta information"
15+
*/
16+
public class MetaInfoRule implements IRestRule {
17+
private static final String TITLE = "OpenAPI specification must contain required meta information";
18+
private static final RuleIdentifier RULE_IDENTIFIER = RuleIdentifier.META_INFO;
19+
private static final RuleCategory RULE_CATEGORY = RuleCategory.META;
20+
private static final RuleSeverity RULE_SEVERITY = RuleSeverity.WARNING;
21+
private static final List<RuleSoftwareQualityAttribute> SOFTWARE_QUALITY_ATTRIBUTES = List.of(
22+
RuleSoftwareQualityAttribute.MAINTAINABILITY,
23+
RuleSoftwareQualityAttribute.USABILITY
24+
);
25+
private boolean isActive;
26+
27+
public MetaInfoRule(boolean isActive) {
28+
this.isActive = isActive;
29+
}
30+
31+
@Override
32+
public String getTitle() {
33+
return TITLE;
34+
}
35+
36+
@Override
37+
public RuleIdentifier getIdentifier() {
38+
return RULE_IDENTIFIER;
39+
}
40+
41+
@Override
42+
public RuleCategory getCategory() {
43+
return RULE_CATEGORY;
44+
}
45+
46+
@Override
47+
public RuleSeverity getSeverityType() {
48+
return RULE_SEVERITY;
49+
}
50+
51+
@Override
52+
public List<RuleSoftwareQualityAttribute> getRuleSoftwareQualityAttribute() {
53+
return SOFTWARE_QUALITY_ATTRIBUTES;
54+
}
55+
56+
@Override
57+
public boolean getIsActive() {
58+
return this.isActive;
59+
}
60+
61+
@Override
62+
public void setIsActive(boolean isActive) {
63+
this.isActive = isActive;
64+
}
65+
66+
/**
67+
* Checks if there is a violation against the "MUST contain API meta information" rule
68+
* To conform to the rule, multiple fields within the "info" field of the API must be provided
69+
* The rule ensures an API's meta information is adequately documented
70+
*
71+
* @param openAPI the definition that will be checked against the rule.
72+
* @return the list of violations.
73+
*/
74+
@Override
75+
public List<Violation> checkViolation(OpenAPI openAPI) {
76+
List<Violation> violations = new ArrayList<>();
77+
78+
Info info = openAPI.getInfo();
79+
if (info == null) {
80+
violations.add(new Violation(this, 0, "Add an 'info' object to the OpenAPI specification",
81+
"info", ErrorMessage.METAINFO));
82+
return violations;
83+
}
84+
85+
if (info.getTitle() == null || info.getTitle().trim().isEmpty()) {
86+
violations.add(new Violation(this, 0, "Add a title to the info object",
87+
"info.title", ErrorMessage.METAINFO));
88+
}
89+
90+
if (info.getVersion() == null || info.getVersion().trim().isEmpty()) {
91+
violations.add(new Violation(this, 0, "Add a version to the info object",
92+
"info.version", ErrorMessage.METAINFO));
93+
}
94+
95+
if (info.getDescription() == null || info.getDescription().trim().isEmpty()) {
96+
violations.add(new Violation(this, 0, "Add a description to the info object",
97+
"info.description", ErrorMessage.METAINFO));
98+
}
99+
100+
Contact contact = info.getContact();
101+
if (contact == null) {
102+
violations.add(new Violation(this, 0, "Add a contact object to the info object",
103+
"info.contact", ErrorMessage.METAINFO));
104+
} else {
105+
if (contact.getName() == null || contact.getName().trim().isEmpty()) {
106+
violations.add(new Violation(this, 0, "Add a name to the contact object",
107+
"info.contact.name", ErrorMessage.METAINFO));
108+
}
109+
if (contact.getUrl() == null || contact.getUrl().trim().isEmpty()) {
110+
violations.add(new Violation(this, 0, "Add a URL to the contact object",
111+
"info.contact.url", ErrorMessage.METAINFO));
112+
}
113+
if (contact.getEmail() == null || contact.getEmail().trim().isEmpty()) {
114+
violations.add(new Violation(this, 0, "Add an email to the contact object",
115+
"info.contact.email", ErrorMessage.METAINFO));
116+
}
117+
}
118+
119+
return violations;
120+
}
121+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"swagger": "2.0",
3+
"schemes": ["https", "http"],
4+
"host": "1forge.com",
5+
"basePath": "/forex-quotes",
6+
"info": {
7+
"contact": {
8+
"name": "",
9+
"url": ""
10+
},
11+
"description": "",
12+
"title": "",
13+
"version": "1.0"
14+
},
15+
"produces": ["application/json"],
16+
"paths": {
17+
"/quotes": {
18+
"get": {
19+
"description": "Get quotes",
20+
"externalDocs": {
21+
"description": "Find out more",
22+
"url": "http://1forge.com/forex-data-api"
23+
},
24+
"security": [
25+
{
26+
"OAuth2": ["read"]
27+
}
28+
],
29+
"responses": {
30+
"200": {
31+
"schema": {
32+
"example": ["EURUSD", "GBPJPY", "AUDUSD"],
33+
"items": {
34+
"type": "string"
35+
},
36+
"type": "array"
37+
},
38+
"description": "A list of quotes"
39+
},
40+
"401": {
41+
"schema": {
42+
"example": ["EURUSD", "GBPJPY", "AUDUSD"],
43+
"items": {
44+
"type": "string"
45+
},
46+
"type": "array"
47+
},
48+
"description": "Unauthorized"
49+
}
50+
},
51+
"summary": "Get quotes for all symbols",
52+
"tags": ["forex", "finance", "quotes"]
53+
}
54+
}
55+
}
56+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package cli.rule.metaInfoTest;
2+
3+
import cli.analyzer.RestAnalyzer;
4+
import cli.rule.Violation;
5+
import cli.rule.rules.MetaInfoRule;
6+
import org.junit.jupiter.api.DisplayName;
7+
import org.junit.jupiter.api.Test;
8+
9+
import java.util.List;
10+
11+
import static org.junit.jupiter.api.Assertions.assertEquals;
12+
13+
class MetaInfoRuleTest {
14+
private static final String PATH_INVALID_OPENAPI = "src/test/java/cli/rule/metaInfoTest/InvalidOpenAPIMetaInfoRule.json";
15+
private static final String VALID_OPENAPI = "src/test/java/cli/validopenapi/validOpenAPI.json";
16+
RestAnalyzer restAnalyzer;
17+
MetaInfoRule metaInfoRule;
18+
19+
@Test
20+
@DisplayName("Test that checks if no meta info rule violation is detected when there is a correct OpenAPI definition.")
21+
void validFile() {
22+
List<Violation> violations = runMethodUnderTest(VALID_OPENAPI);
23+
assertEquals(0, violations.size(), "There should be no rule violation for the valid openAPI definition.");
24+
}
25+
26+
@Test
27+
@DisplayName("Test that checks if meta info rule violations are detected when required meta information is missing.")
28+
void invalidFile() {
29+
List<Violation> violations = runMethodUnderTest(PATH_INVALID_OPENAPI);
30+
assertEquals(5, violations.size(), "There should be 5 rule violations for missing meta information.");
31+
}
32+
33+
private List<Violation> runMethodUnderTest(String url) {
34+
this.restAnalyzer = new RestAnalyzer(url);
35+
this.metaInfoRule = new MetaInfoRule(true);
36+
return this.restAnalyzer.runAnalyse(List.of(this.metaInfoRule), false);
37+
}
38+
}

0 commit comments

Comments
 (0)