Skip to content
Open
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
8 changes: 7 additions & 1 deletion .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,16 @@ jobs:
cache: maven
- name: Build, Test and Coverage
run: mvn -B verify --file pom.xml
- name: Set up JDK 21 for Sonar
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: maven
- name: SonarCloud Analysis
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: mvn sonar:sonar -Dsonar.token=$SONAR_TOKEN
run: mvn org.sonarsource.scanner.maven:sonar-maven-plugin:3.11.0.3922:sonar -Dsonar.token=$SONAR_TOKEN

- name: Update dependency graph
uses: advanced-security/maven-dependency-submission-action@v4
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.2.2-beta-1] - 2026-08-18

### Added

- Accept OpenAPI `3.0.4`, `3.1.1` and `3.1.2` in version dispatch and the v3/v3.1/v3.2 grammars.

## [1.2.1] - 2026-06-04

### Added
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Feel free to drop by and greet us on our GitHub discussion or Discord chat. You

## ⚙️ Features

* Full compatibility with OpenAPI v2.0, v3.0.0, v3.0.1, v3.0.2, v3.0.3, v3.1.0 and v3.2.0
* Full compatibility with OpenAPI v2.0, v3.0.0, v3.0.1, v3.0.2, v3.0.3, v3.0.4, v3.1.0, v3.1.1, v3.1.2 and v3.2.0

![SonarOpenApi in action](sonarqube.jpg)

Expand Down
2 changes: 1 addition & 1 deletion its/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.apiaddicts.apitools.dosonarapi</groupId>
<artifactId>dosonarapi</artifactId>
<version>1.2.1</version>
<version>1.2.2-beta-1</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
Expand Down
2 changes: 1 addition & 1 deletion openapi-checks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.apiaddicts.apitools.dosonarapi</groupId>
<artifactId>dosonarapi</artifactId>
<version>1.2.1</version>
<version>1.2.2-beta-1</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion openapi-front-end/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.apiaddicts.apitools.dosonarapi</groupId>
<artifactId>dosonarapi</artifactId>
<version>1.2.1</version>
<version>1.2.2-beta-1</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public static YamlGrammarBuilder create() {
b.setRootRule(ROOT);

b.rule(ROOT).is(b.object(
b.mandatoryProperty("openapi", b.firstOf("3.0.0", "3.0.1", "3.0.2", "3.0.3")),
b.mandatoryProperty("openapi", b.firstOf("3.0.0", "3.0.1", "3.0.2", "3.0.3", "3.0.4")),
b.mandatoryProperty("info", INFO),
b.property("servers", b.array(SERVER)),
b.mandatoryProperty("paths", PATHS),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public enum OpenApi31Grammar implements GrammarRuleKey {
SECURITY_SCHEMES,
LINKS_COMPONENT,
CALLBACKS_COMPONENT,
PATH_ITEMS_COMPONENT,
SCHEMA_PROPERTIES,
DESCRIPTION;

Expand All @@ -88,10 +89,11 @@ public static YamlGrammarBuilder create() {
b.setRootRule(ROOT);

b.rule(ROOT).is(b.object(
b.mandatoryProperty("openapi", "3.1.0"),
b.mandatoryProperty("openapi", b.firstOf("3.1.0", "3.1.1", "3.1.2")),
b.mandatoryProperty("info", INFO),
b.property("jsonSchemaDialect", b.string()),
b.property("servers", b.array(SERVER)),
b.mandatoryProperty("paths", PATHS),
b.property("paths", PATHS),
b.property("webhooks", WEBHOOKS),
b.property("components", COMPONENTS),
b.property("security", b.array(SECURITY_REQUIREMENT)),
Expand Down Expand Up @@ -151,12 +153,14 @@ private static void buildComponents(YamlGrammarBuilder b) {
b.property("securitySchemes", SECURITY_SCHEMES),
b.property("links", LINKS_COMPONENT),
b.property("callbacks", CALLBACKS_COMPONENT),
b.property("pathItems", PATH_ITEMS_COMPONENT),
b.patternProperty(EXTENSION_PATTERN, b.anything())));
OpenApiGrammar.buildBaseComponentRules(b,
SCHEMAS_COMPONENT, RESPONSES_COMPONENT, PARAMETERS_COMPONENT, EXAMPLES_COMPONENT,
BODIES_COMPONENT, HEADERS_COMPONENT, SECURITY_SCHEMES, LINKS_COMPONENT, CALLBACKS_COMPONENT,
REF, SCHEMA, RESPONSE, PARAMETER, EXAMPLE, REQUEST_BODY, HEADER, SECURITY_SCHEME, LINK, CALLBACK);
b.rule(WEBHOOKS_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, WEBHOOK))));
b.rule(PATH_ITEMS_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, PATH))));

buildParameters(b);
buildResponses(b);
Expand Down Expand Up @@ -185,16 +189,35 @@ private static void buildSchema(YamlGrammarBuilder b) {
b.property("type", b.firstOf(b.string(), b.array(b.string()))),
b.property("contentMediaType", b.string()),
b.property("contentEncoding", b.string()),
b.property("contentSchema", b.firstOf(REF, SCHEMA)),
b.property("allOf", b.array(b.firstOf(REF, SCHEMA))),
b.property("oneOf", b.array(b.firstOf(REF, SCHEMA))),
b.property("anyOf", b.array(b.firstOf(REF, SCHEMA))),
b.property("not", b.firstOf(REF, SCHEMA)),
b.property("if", b.firstOf(REF, SCHEMA)),
b.property("then", b.firstOf(REF, SCHEMA)),
b.property("else", b.firstOf(REF, SCHEMA)),
b.property("prefixItems", b.array(b.firstOf(REF, SCHEMA))),
b.property("items", b.firstOf(REF, SCHEMA)),
b.property("contains", b.firstOf(REF, SCHEMA)),
b.property("minContains", b.integer()),
b.property("maxContains", b.integer()),
b.property("unevaluatedItems", b.firstOf(b.bool(), REF, SCHEMA)),
b.property("properties", SCHEMA_PROPERTIES),
b.property("patternProperties", SCHEMA_PROPERTIES),
b.property("propertyNames", b.firstOf(REF, SCHEMA)),
b.property("dependentSchemas", b.object(b.patternProperty(".*", b.firstOf(REF, SCHEMA)))),
b.property("dependentRequired", b.object(b.patternProperty(".*", b.array(b.string())))),
b.property("$schema", b.string()),
b.property("$id", b.string()),
b.property("$anchor", b.string()),
b.property("$defs", b.object(b.patternProperty(".*", b.firstOf(REF, SCHEMA)))),
b.property("$dynamicRef", b.string()),
b.property("$dynamicAnchor", b.string()),
b.property("$comment", b.string()),
b.property("additionalProperties", b.firstOf(b.bool(), REF, SCHEMA)),
b.property("description", DESCRIPTION),
b.property("unevaluatedProperties", b.bool()),
b.property("unevaluatedProperties", b.firstOf(b.bool(), REF, SCHEMA)),
b.property("format", b.string()),
b.property("default", b.anything()),
b.property("nullable", b.bool()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ private static void buildSchema(YamlGrammarBuilder b) {
b.property("type", b.firstOf(b.string(), b.array(b.string()))),
b.property("contentMediaType", b.string()),
b.property("contentEncoding", b.string()),
b.property("contentSchema", b.firstOf(REF, SCHEMA)),
b.property("allOf", b.array(b.firstOf(REF, SCHEMA))),
b.property("oneOf", b.array(b.firstOf(REF, SCHEMA))),
b.property("anyOf", b.array(b.firstOf(REF, SCHEMA))),
Expand All @@ -255,6 +256,7 @@ private static void buildSchema(YamlGrammarBuilder b) {
b.property("dependentSchemas", b.object(b.patternProperty(".*", b.firstOf(REF, SCHEMA)))),
b.property("dependentRequired", b.object(b.patternProperty(".*", b.array(b.string())))),
b.property("$schema", b.string()),
b.property("$id", b.string()),
b.property("$anchor", b.string()),
b.property("$defs", b.object(b.patternProperty(".*", b.firstOf(REF, SCHEMA)))),
b.property("$dynamicRef", b.string()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import org.junit.Test;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;

import static org.assertj.core.api.Assertions.assertThat;

public class JsonSchemaTest extends BaseNodeTest<OpenApi31Grammar> {

@Test
Expand All @@ -48,4 +50,23 @@ public class JsonSchemaTest extends BaseNodeTest<OpenApi31Grammar> {
JsonNode param = node.at("/value");
assertEquals("http://json-schema.org/draft-07/schema#", param, "/$schema");
}

@Test
public void json_schema_keywords() {
JsonNode node = parseResource(OpenApi31Grammar.SCHEMA, "/models/v31/jsonschemakeywords.yaml");

assertThat(issues).isEmpty();
assertEquals("https://example.com/schemas/person", node, "/$id");
assertEquals("base64", node, "/properties/id/contentEncoding");
assertEquals("1", node, "/properties/tags/minContains");
}

@Test
public void root_without_paths_with_dialect_and_path_items() {
JsonNode node = parseResource(OpenApi31Grammar.ROOT, "/models/v31/webhooksonly.yaml");

assertThat(issues).isEmpty();
assertEquals("https://spec.openapis.org/oas/3.1/dialect/base", node, "/jsonSchemaDialect");
assertMissing(node.at("/paths"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
public class OpenApiParserTest {

private static final OpenApiConfiguration CONFIG = new OpenApiConfiguration(StandardCharsets.UTF_8, false);
private static final OpenApiConfiguration STRICT = new OpenApiConfiguration(StandardCharsets.UTF_8, true);

private static final String MINIMAL_V2 =
"swagger: \"2.0\"\n" +
Expand All @@ -45,6 +46,14 @@ public class OpenApiParserTest {
" version: 1.0.0\n" +
"paths: {}";

private static String minimalOpenApi(String version) {
return "openapi: \"" + version + "\"\n" +
"info:\n" +
" title: Test\n" +
" version: 1.0.0\n" +
"paths: {}";
}

@Test
public void create_v2_parser_parses_swagger_doc() {
YamlParser parser = OpenApiParser.createV2(CONFIG);
Expand All @@ -63,6 +72,30 @@ public void create_v3_parser_parses_openapi_doc() {
assertThat(root.at("/openapi").getTokenValue()).isEqualTo("3.0.0");
}

@Test
public void create_v3_parser_accepts_openapi_304() {
YamlParser parser = OpenApiParser.createV3(STRICT);
JsonNode root = parser.parse(minimalOpenApi("3.0.4"));
assertThat(root).isNotNull();
assertThat(root.at("/openapi").getTokenValue()).isEqualTo("3.0.4");
}

@Test
public void create_v31_parser_accepts_openapi_311() {
YamlParser parser = OpenApiParser.createV31(STRICT);
JsonNode root = parser.parse(minimalOpenApi("3.1.1"));
assertThat(root).isNotNull();
assertThat(root.at("/openapi").getTokenValue()).isEqualTo("3.1.1");
}

@Test
public void create_v31_parser_accepts_openapi_312() {
YamlParser parser = OpenApiParser.createV31(STRICT);
JsonNode root = parser.parse(minimalOpenApi("3.1.2"));
assertThat(root).isNotNull();
assertThat(root.at("/openapi").getTokenValue()).isEqualTo("3.1.2");
}

@Test
public void create_v31_parser_returns_non_null() {
YamlParser parser = OpenApiParser.createV31(CONFIG);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
$schema: "https://json-schema.org/draft/2020-12/schema"
$id: "https://example.com/schemas/person"
$anchor: "person"
$comment: "Exercises JSON Schema keywords supported by OpenAPI 3.1"
$defs:
name:
type: string
type: [object, "null"]
properties:
id:
type: [string, "null"]
contentEncoding: base64
contentMediaType: application/json
contentSchema:
type: object
tags:
type: array
prefixItems:
- type: string
- type: integer
items:
type: string
contains:
type: string
minContains: 1
maxContains: 5
unevaluatedItems: false
patternProperties:
"^x-meta-":
type: string
propertyNames:
pattern: "^[a-z]+$"
dependentSchemas:
creditCard:
required: [billingAddress]
dependentRequired:
creditCard: [billingAddress]
if:
properties:
kind:
const: admin
then:
required: [permissions]
else:
required: [role]
unevaluatedProperties: false
18 changes: 18 additions & 0 deletions openapi-front-end/src/test/resources/models/v31/webhooksonly.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
openapi: "3.1.1"
info:
title: Webhooks only
version: 1.0.0
jsonSchemaDialect: "https://spec.openapis.org/oas/3.1/dialect/base"
webhooks:
newPet:
post:
responses:
'200':
description: ok
components:
pathItems:
petItem:
get:
responses:
'200':
description: ok
2 changes: 1 addition & 1 deletion openapi-test-tools/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.apiaddicts.apitools.dosonarapi</groupId>
<artifactId>dosonarapi</artifactId>
<version>1.2.1</version>
<version>1.2.2-beta-1</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

<groupId>org.apiaddicts.apitools.dosonarapi</groupId>
<artifactId>dosonarapi</artifactId>
<version>1.2.1</version>
<version>1.2.2-beta-1</version>
<packaging>pom</packaging>

<name>SonarOpenAPI</name>
Expand Down
2 changes: 1 addition & 1 deletion sonar-openapi-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.apiaddicts.apitools.dosonarapi</groupId>
<artifactId>dosonarapi</artifactId>
<version>1.2.1</version>
<version>1.2.2-beta-1</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,13 @@ public void scanFiles() {
private static boolean isV3Version(JsonNode openapiNode) {
if (openapiNode.isMissing()) return false;
String v = openapiNode.getTokenValue();
return "3.0.0".equals(v) || "3.0.1".equals(v) || "3.0.2".equals(v) || "3.0.3".equals(v);
return "3.0.0".equals(v) || "3.0.1".equals(v) || "3.0.2".equals(v) || "3.0.3".equals(v) || "3.0.4".equals(v);
}

private static boolean isV31Version(JsonNode openapiNode) {
if (openapiNode.isMissing()) return false;
String v = openapiNode.getTokenValue();
return "3.1.0".equals(v) || "3.1.1".equals(v) || "3.1.2".equals(v);
}

private YamlParser selectParser(boolean isV2, boolean isV3, boolean isV31, boolean isV32) {
Expand All @@ -133,7 +139,7 @@ private void scanFile(InputFile inputFile) {
boolean isV2 = !rootNode.at("/swagger").isMissing();
JsonNode openapiNode = rootNode.at("/openapi");
boolean isV3 = isV3Version(openapiNode);
boolean isV31 = !openapiNode.isMissing() && openapiNode.getTokenValue().equals("3.1.0");
boolean isV31 = isV31Version(openapiNode);
boolean isV32 = !openapiNode.isMissing() && openapiNode.getTokenValue().equals("3.2.0");
YamlParser targetParser = selectParser(isV2, isV3, isV31, isV32);
if (targetParser == null) return;
Expand Down
Loading