Skip to content

Commit

Permalink
OpenAPI updates.
Browse files Browse the repository at this point in the history
- Minimalistic SE OpenAPI support with SPI to implement the MicroProfile support and OpenAPI UI
- Make openapi a multi-module with two sub-modules: openapi and openapi-ui
- Add openapi/tests and move helidon-iogh-5792 from tests/integration
  - Re-enabled test,  make it strictly a test (not an app) and use version.lib.snakeyaml to override the SnakeYAML version
- Created OpenApiFormat to formalize what was before OpenApiFeature.OpenAPIMediaType
- Microprofile OpenAPI refactorings:
  - MPOpenAPIBuilder into FilteredIndexViewsBuilder as a utility to create List<FilteredIndexView>
  - MpOpenApiManager implements OpenApiManager using SmallRye OpenAPI (what was before in MpOpenApiFeature
  - Prefix utility classes with OpenApi:
    - ParserHelper -> OpenApiParser
    - Serializer -> OpenApiSerializer
  - Renamed HelidonAnnotationScannerExtension to JsonpAnnotationScannerExtension to remove 'Helidon' from the class name
  - Renamed tests to use Test as a suffix instead of prefix
- Updated examples/openapi to remove the in-memory model related features (i.e. reader, filter)
- Renamed examples/microprofile/openapi-basic to examples/microprofile/openapi (to be symetrical with SE)
- Updated tests to use new testing patterns (i.e. helidon-microprofile-testing-junit5 for MP and helidon-webserver-testing-junit5 for SE)
- Generated config docs for openapi/openapi, openapi/openapi-ui, microprofile/openapi (Removed old files)

Fixes helidon-io#7247 (SE OpenAPI static file support)
Fixes helidon-io#7240 (Fix helidon-iogh-5792 integration test)
Fixes helidon-io#6130 (Port OpenAPI UI integration to 4.x)
Fixes helidon-io#7643 (OpenAPI parsing fails to handle default in some cases)
Fixes helidon-io#7668 (Routing path with optional sequence not supported)
  • Loading branch information
romain-grecourt committed Sep 26, 2023
1 parent 480446f commit 4d02968
Show file tree
Hide file tree
Showing 131 changed files with 5,988 additions and 5,815 deletions.
5 changes: 5 additions & 0 deletions bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,11 @@
<artifactId>helidon-openapi</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.openapi</groupId>
<artifactId>helidon-openapi-ui</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.microprofile.openapi</groupId>
<artifactId>helidon-microprofile-openapi</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.common.testing.junit5;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;

/**
* Hamcrest matchers for {@link java.util.Map}.
*/
public final class MapMatcher {
private MapMatcher() {
}

/**
* A matcher for an {@link java.util.Map} that performs a deep equality.
* <p>
* Usage example:
* <pre>
* assertThat(actualMap, isMapEqualTo(expectedMap));
* </pre>
*
* This method targets trees implemented using {@link java.util.Map} where values of type {@link java.util.Map}
* are considered tree nodes, and values with other types are considered leaf nodes.
* <p>
* The deep-equality is performed by diffing a flat string representation of each map. If the diff yields no differences,
* the maps are considered deeply equal.
* <p>
* The entries are compared using strings, both keys and leaf nodes must implement {@link Object#toString()}.
*
* @param expected expected map
* @param <K> type of the map keys
* @param <V> type of the map values
* @return matcher validating the {@link java.util.Map} is deeply equal
*/
public static <K, V> Matcher<Map<K, V>> mapEqualTo(Map<K, V> expected) {
return new DiffMatcher<>(expected);
}

private static final class DiffMatcher<K, V> extends TypeSafeMatcher<Map<K, V>> {

private final Map<K, V> expected;
private volatile Map<K, V> actual;
private volatile List<Diff> diffs;

private DiffMatcher(Map<K, V> expected) {
this.expected = expected;
}

@Override
protected boolean matchesSafely(Map<K, V> actual) {
this.actual = actual;
this.diffs = diffs(expected, actual);
return diffs.isEmpty();
}

@Override
public void describeTo(Description description) {
description.appendText("deep map equality");
}

@Override
protected void describeMismatchSafely(Map<K, V> item, Description mismatchDescription) {
List<Diff> diffs = actual == item ? this.diffs : diffs(expected, item);
mismatchDescription.appendText("found differences" + System.lineSeparator())
.appendText(String.join(System.lineSeparator(), diffs.stream().map(Diff::toString).toList()));
}

private static List<Diff> diffs(Map<?, ?> left, Map<?, ?> right) {
List<Diff> diffs = new ArrayList<>();
Iterator<Map.Entry<String, String>> leftEntries = flattenEntries(left, "").iterator();
Iterator<Map.Entry<String, String>> rightEntries = flattenEntries(right, "").iterator();
while (true) {
boolean hasLeft = leftEntries.hasNext();
boolean hasRight = rightEntries.hasNext();
if (hasLeft && hasRight) {
Map.Entry<String, String> leftEntry = leftEntries.next();
Map.Entry<String, String> rightEntry = rightEntries.next();
if (!leftEntry.equals(rightEntry)) {
diffs.add(new Diff(leftEntry, rightEntry));
}
} else if (hasLeft) {
diffs.add(new Diff(leftEntries.next(), null));
} else if (hasRight) {
diffs.add(new Diff(null, rightEntries.next()));
} else {
return diffs;
}
}
}

private static List<Map.Entry<String, String>> flattenEntries(Map<?, ?> map, String prefix) {
List<Map.Entry<String, String>> result = new ArrayList<>();
for (Map.Entry<?, ?> entry : map.entrySet()) {
if (entry.getValue() instanceof Map<?, ?> node) {
result.addAll(flattenEntries(node, prefix + entry.getKey() + "."));
} else {
result.add(Map.entry(prefix + entry.getKey(), entry.getValue().toString()));
}
}
result.sort(Map.Entry.comparingByKey());
return result;
}

private record Diff(Map.Entry<String, String> left, Map.Entry<String, String> right) {

@Override
public String toString() {
if (left == null && right != null) {
return "ADDED >> " + right;
}
if (left != null && right == null) {
return "REMOVED << " + left;
}
if (left != null) {
return "ADDED >> " + left + System.lineSeparator() + "REMOVED << " + right;
}
return "?";
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.helidon.common.testing;

import java.util.Map;

import org.junit.jupiter.api.Test;

import static io.helidon.common.testing.junit5.MapMatcher.mapEqualTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.not;

class MapMatcherTest {

@Test
void testIsMapEqual() {
assertThat(Map.of("foo", "bar"), is(mapEqualTo(Map.of("foo", "bar"))));
assertThat(Map.of("bar", "foo"), is(not(mapEqualTo(Map.of("foo", "bar")))));

assertThat(Map.of("foo", Map.of("bar", Map.of("bob", "alice"))),
is(mapEqualTo(Map.of("foo", Map.of("bar", Map.of("bob", "alice"))))));

assertThat(Map.of("foo", Map.of("bar", Map.of("bob", "alice"))),
is(not(mapEqualTo(Map.of("foo", Map.of("bar", Map.of("bob", "not-alice")))))));

assertThat(Map.of("foo", "bar", "bob", "alice"), is(mapEqualTo(Map.of("bob", "alice", "foo", "bar"))));
}
}
5 changes: 3 additions & 2 deletions docs/config/config_reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ The following section lists all configurable types in Helidon.
- xref:{rootdir}/config/io_helidon_common_configurable_AllowList.adoc[AllowList (common.configurable)]
- xref:{rootdir}/config/io_helidon_faulttolerance_Async.adoc[Async (faulttolerance)]
- xref:{rootdir}/config/io_helidon_security_providers_oidc_common_BaseBuilder.adoc[BaseBuilder (security.providers.oidc.common)]
- xref:{rootdir}/config/io_helidon_openapi_OpenApiUi_Builder.adoc[Builder (openapi.OpenApiUi)]
- xref:{rootdir}/config/io_helidon_security_providers_idcs_mapper_IdcsRoleMapperProviderBase_Builder.adoc[Builder (security.providers.idcs.mapper.IdcsRoleMapperProviderBase)]
- xref:{rootdir}/config/io_helidon_webserver_servicecommon_HelidonFeatureSupport_Builder.adoc[Builder (webserver.servicecommon.HelidonFeatureSupport)]
- xref:{rootdir}/config/io_helidon_faulttolerance_Bulkhead.adoc[Bulkhead (faulttolerance)]
Expand Down Expand Up @@ -79,7 +78,8 @@ The following section lists all configurable types in Helidon.
- xref:{rootdir}/config/io_helidon_common_configurable_ScheduledThreadPoolSupplier.adoc[ScheduledThreadPoolSupplier (common.configurable)]
- xref:{rootdir}/config/io_helidon_metrics_api_ScopeConfig.adoc[ScopeConfig (metrics.api)]
- xref:{rootdir}/config/io_helidon_metrics_api_ScopingConfig.adoc[ScopingConfig (metrics.api)]
- xref:{rootdir}/config/io_helidon_openapi_SeOpenApiFeature.adoc[SeOpenApiFeature (openapi)]
- xref:{rootdir}/config/io_helidon_openapi_OpenApiFeature.adoc[OpenApiFeature (openapi)]
- xref:{rootdir}/config/io_helidon_openapi_ui_OpenApiUi.adoc[OpenApiUi (openapi.ui)]
- xref:{rootdir}/config/io_helidon_security_Security.adoc[Security (security)]
- xref:{rootdir}/config/io_helidon_security_SecurityTime.adoc[SecurityTime (security)]
- xref:{rootdir}/config/io_helidon_microprofile_server_Server.adoc[Server (microprofile.server)]
Expand All @@ -99,3 +99,4 @@ The following section lists all configurable types in Helidon.
- xref:{rootdir}/config/io_helidon_tracing_providers_zipkin_ZipkinTracerBuilder.adoc[ZipkinTracerBuilder (tracing.providers.zipkin)]
- xref:{rootdir}/config/io_opentracing_Tracer.adoc[io_opentracing_Tracer]
- xref:{rootdir}/config/org_eclipse_microprofile_config_Config.adoc[org_eclipse_microprofile_config_Config]
- xref:{rootdir}/config/io_helidon_microprofile_openapi_MpOpenApiManagerConfig.adoc[MpOpenApiManagerConfig (microprofile.openapi)]
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
///////////////////////////////////////////////////////////////////////////////

Copyright (c) 2023 Oracle and/or its affiliates.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

///////////////////////////////////////////////////////////////////////////////
ifndef::rootdir[:rootdir: {docdir}/..]
:description: Configuration of io.helidon.microprofile.openapi.MpOpenApiManagerConfig
:keywords: helidon, config, io.helidon.microprofile.openapi.MpOpenApiManagerConfig
:basic-table-intro: The table below lists the configuration keys that configure io.helidon.microprofile.openapi.MpOpenApiManagerConfig
include::{rootdir}/includes/attributes.adoc[]
= MpOpenApiManagerConfig (microprofile.openapi) Configuration
// tag::config[]
Type: link:{javadoc-base-url}/io.helidon.microprofile.openapi/io/helidon/microprofile/openapi/MpOpenApiManagerConfig.html[io.helidon.microprofile.openapi.MpOpenApiManagerConfig]
== Configuration options
.Optional configuration options
[cols="3,3a,2,5a"]
|===
|key |type |default value |description
|`mp.openapi.extensions.helidon.use-jaxrs-semantics` |boolean |{nbsp} |If `true` and the `jakarta.ws.rs.core.Application` class returns a non-empty set, endpoints defined by
other resources are not included in the OpenAPI document.
@return `true` if enabled, `false` otherwise
|===
// end::config[]
68 changes: 68 additions & 0 deletions docs/config/io_helidon_openapi_OpenApiFeature.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
///////////////////////////////////////////////////////////////////////////////

Copyright (c) 2023 Oracle and/or its affiliates.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

///////////////////////////////////////////////////////////////////////////////
ifndef::rootdir[:rootdir: {docdir}/..]
:description: Configuration of io.helidon.openapi.OpenApiFeature
:keywords: helidon, config, io.helidon.openapi.OpenApiFeature
:basic-table-intro: The table below lists the configuration keys that configure io.helidon.openapi.OpenApiFeature
include::{rootdir}/includes/attributes.adoc[]
= OpenApiFeature (openapi) Configuration
// tag::config[]
Type: link:{javadoc-base-url}/io.helidon.openapi/io/helidon/openapi/OpenApiFeature.html[io.helidon.openapi.OpenApiFeature]
This is a standalone configuration type, prefix from configuration root: `openapi`
== Configuration options
.Optional configuration options
[cols="3,3a,2,5a"]
|===
|key |type |default value |description
|`cors` |xref:{rootdir}/config/io_helidon_cors_CrossOriginConfig.adoc[CrossOriginConfig] |{nbsp} |CORS config.
@return CORS config
|`enabled` |boolean |`true` |Sets whether the feature should be enabled.
@return `true` if enabled, `false` otherwise
|`manager` |io.helidon.openapi.OpenApiManager (service provider interface) |{nbsp} |OpenAPI manager.
@return the OpenAPI manager
|`services` |io.helidon.openapi.OpenApiService[&#93; (service provider interface) |{nbsp} |OpenAPI services.
@return the OpenAPI services
|`static-file` |string |{nbsp} |Path of the static OpenAPI document file. Default types are `json`, `yaml`, and `yml`.
@return location of the static OpenAPI document file
|`web-context` |string |`/openapi` |Web context path for the OpenAPI endpoint.
@return webContext to use
|===
// end::config[]
14 changes: 7 additions & 7 deletions docs/config/io_helidon_openapi_OpenApiUi_Builder.adoc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
///////////////////////////////////////////////////////////////////////////////

Copyright (c) 2023 Oracle and/or its affiliates.
Copyright (c) 2022, 2023 Oracle and/or its affiliates.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -17,17 +17,17 @@
///////////////////////////////////////////////////////////////////////////////
ifndef::rootdir[:rootdir: {docdir}/..]
:description: Configuration of io.helidon.openapi.OpenApiUi.Builder
:keywords: helidon, config, io.helidon.openapi.OpenApiUi.Builder
:basic-table-intro: The table below lists the configuration keys that configure io.helidon.openapi.OpenApiUi.Builder
:description: Configuration of io.helidon.openapi.OpenApiUi
:keywords: helidon, config, io.helidon.openapi.OpenApiUi
:basic-table-intro: The table below lists the configuration keys that configure io.helidon.openapi.OpenApiUi
include::{rootdir}/includes/attributes.adoc[]
= Builder (openapi.OpenApiUi) Configuration
= OpenApiUi (openapi) Configuration
// tag::config[]
Type: link:{javadoc-base-url}/io.helidon.openapi.OpenApiUi/io/helidon/openapi/OpenApiUi/Builder.html[io.helidon.openapi.OpenApiUi.Builder]
Type: link:{javadoc-base-url}/io.helidon.openapi/io/helidon/openapi/OpenApiUi.html[io.helidon.openapi.OpenApiUi]
[source,text]
Expand All @@ -49,7 +49,7 @@ ui
|key |type |default value |description
|`enabled` |boolean |`true` |Sets whether the UI should be enabled.
|`options` |Map&lt;string, string&gt; |{nbsp} |Merges implementation-specific UI options.
|`options` |Map&lt;string, string&gt; |{nbsp} |Sets implementation-specific UI options.
|`web-context` |string |{nbsp} |web context (path) where the UI will respond
|===
Expand Down
Loading

0 comments on commit 4d02968

Please sign in to comment.