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)

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 8ddc076
Show file tree
Hide file tree
Showing 127 changed files with 5,851 additions and 5,804 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"))));
}
}
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
12 changes: 6 additions & 6 deletions docs/mp/openapi/openapi.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,10 @@ include::{incdir}/openapi.adoc[tag=api]
== Examples
Helidon MP includes a link:{helidon-github-tree-url}/examples/microprofile/openapi-basic[complete OpenAPI example]
Helidon MP includes a link:{helidon-github-tree-url}/examples/microprofile/openapi[complete OpenAPI example]
based on the MP quick-start sample app. The rest of this section shows, step-by-step, how one might change the original QuickStart service to adopt OpenAPI.
=== Helidon MP Basic OpenAPI Example
=== Helidon MP OpenAPI Example
This example shows a simple greeting application, similar to the one from the
Helidon MP QuickStart, enhanced with OpenAPI support.
Expand Down Expand Up @@ -228,8 +228,8 @@ Having written the filter and model reader classes, identify them by adding conf
[source,properties]
----
mp.openapi.filter=io.helidon.microprofile.examples.openapi.basic.internal.SimpleAPIFilter
mp.openapi.model.reader=io.helidon.microprofile.examples.openapi.basic.internal.SimpleAPIModelReader
mp.openapi.filter=io.helidon.microprofile.examples.openapi.internal.SimpleAPIFilter
mp.openapi.model.reader=io.helidon.microprofile.examples.openapi.internal.SimpleAPIModelReader
----
Expand All @@ -238,7 +238,7 @@ Now just build and run:
[source,bash]
----
mvn package
java -jar target/helidon-examples-microprofile-openapi-basic.jar
java -jar target/helidon-examples-microprofile-openapi.jar
----
Try the endpoints:
Expand All @@ -255,7 +255,7 @@ curl -X GET http://localhost:8080/openapi
The output describes not only then endpoints from `GreetResource` but
also one contributed by the `SimpleAPIModelReader`.
Full example is available link:{helidon-github-tree-url}}/examples/microprofile/openapi-basic[in our official repository]
Full example is available link:{helidon-github-tree-url}}/examples/microprofile/openapi[in our official repository]
== Additional Information
Expand Down

This file was deleted.

This file was deleted.

Loading

0 comments on commit 8ddc076

Please sign in to comment.