-
Notifications
You must be signed in to change notification settings - Fork 91
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support configuration of schemas for complex types
Signed-off-by: Michael Edgar <[email protected]>
- Loading branch information
Showing
6 changed files
with
329 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
198 changes: 198 additions & 0 deletions
198
core/src/main/java/io/smallrye/openapi/runtime/util/TypeParser.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
package io.smallrye.openapi.runtime.util; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.stream.Stream; | ||
|
||
import org.jboss.jandex.ArrayType; | ||
import org.jboss.jandex.ClassType; | ||
import org.jboss.jandex.DotName; | ||
import org.jboss.jandex.ParameterizedType; | ||
import org.jboss.jandex.Type; | ||
import org.jboss.jandex.WildcardType; | ||
|
||
/** | ||
* Parse a type signature String to a Jandex Type | ||
*/ | ||
public class TypeParser { | ||
|
||
private static final WildcardType UNBOUNDED_WILDCARD = WildcardType.createUpperBound((Type) null); | ||
private String signature; | ||
private int pos; | ||
|
||
public static Type parse(String signature) { | ||
return new TypeParser(signature).parse(); | ||
} | ||
|
||
private TypeParser(String signature) { | ||
this.signature = signature; | ||
this.pos = 0; | ||
} | ||
|
||
private Type parse() { | ||
return parseReferenceType(); | ||
} | ||
|
||
private Type parseClassTypeSignature() { | ||
DotName name = parseName(); | ||
Type[] types = parseTypeArguments(); | ||
Type type = null; | ||
|
||
if (types.length > 0) { | ||
type = ParameterizedType.create(name, types, null); | ||
} | ||
|
||
return type != null ? type : ClassType.create(name); | ||
} | ||
|
||
private Type[] parseTypeArguments() { | ||
if (pos >= signature.length() || signature.charAt(pos) != '<') { | ||
return Type.EMPTY_ARRAY; | ||
} | ||
pos++; | ||
|
||
List<Type> types = new ArrayList<>(); | ||
for (;;) { | ||
Type t = parseTypeArgument(); | ||
if (t == null) { | ||
break; | ||
} | ||
advanceNot(','); | ||
types.add(t); | ||
} | ||
return types.toArray(new Type[types.size()]); | ||
} | ||
|
||
private Type parseTypeArgument() { | ||
requireIncomplete(); | ||
char c = signature.charAt(pos++); | ||
|
||
if (c == '>') { | ||
return null; | ||
} | ||
if (c == '?') { | ||
if (signature.startsWith(" extends ", pos)) { | ||
pos += " extends ".length(); | ||
return parseWildCard(true); | ||
} else if (signature.startsWith(" super ", pos)) { | ||
pos += " super ".length(); | ||
return parseWildCard(false); | ||
} else { | ||
requireIncomplete(); | ||
if (signature.charAt(pos) != '>') { | ||
throw new IllegalStateException(); | ||
} | ||
return UNBOUNDED_WILDCARD; | ||
} | ||
} | ||
|
||
pos--; | ||
return parseReferenceType(); | ||
} | ||
|
||
private Type parseWildCard(boolean isExtends) { | ||
Type bound = parseReferenceType(); | ||
|
||
return isExtends ? WildcardType.createUpperBound(bound) : WildcardType.createLowerBound(bound); | ||
} | ||
|
||
private Type parseReferenceType() { | ||
int mark = pos; | ||
int typeArgsStart = signature.indexOf('<', mark); | ||
int typeArgsEnd = signature.indexOf('>', mark); | ||
int arrayStart = signature.indexOf('[', mark); | ||
|
||
return Stream.of(typeArgsEnd, typeArgsStart, arrayStart) | ||
.filter(v -> v > -1) | ||
.min(Integer::compare) | ||
.map(firstDelimiter -> { | ||
Type type = null; | ||
|
||
if (firstDelimiter == arrayStart) { | ||
type = parsePrimitive(); | ||
} | ||
|
||
if (type == null) { | ||
type = parseClassTypeSignature(); | ||
} | ||
|
||
if (pos < signature.length() && signature.charAt(pos) == '[') { | ||
type = parseArrayType(type); | ||
} | ||
|
||
return type; | ||
}) | ||
.orElseGet(() -> { | ||
Type primitive = parsePrimitive(); | ||
|
||
if (primitive != null) { | ||
return primitive; | ||
} | ||
|
||
return parseClassTypeSignature(); | ||
}); | ||
} | ||
|
||
private Type parseArrayType(Type type) { | ||
int dimensions = 0; | ||
|
||
while (pos < signature.length() && signature.charAt(pos) == '[') { | ||
pos++; | ||
requireIncomplete(); | ||
|
||
if (signature.charAt(pos++) == ']') { | ||
dimensions++; | ||
} else { | ||
throw new IllegalArgumentException(); | ||
} | ||
} | ||
|
||
return ArrayType.create(type, dimensions); | ||
} | ||
|
||
private Type parsePrimitive() { | ||
int mark = pos; | ||
DotName name = parseName(); | ||
Type type = Type.create(name, Type.Kind.PRIMITIVE); | ||
if (type != null) { | ||
return type; | ||
} | ||
pos = mark; | ||
return null; | ||
} | ||
|
||
private int advanceNot(char c) { | ||
requireIncomplete(); | ||
|
||
while (signature.charAt(pos) == c) { | ||
pos++; | ||
} | ||
|
||
return pos; | ||
} | ||
|
||
private DotName parseName() { | ||
int start = pos; | ||
int end = advanceNameEnd(); | ||
return DotName.createSimple(signature.substring(start, end)); | ||
} | ||
|
||
private int advanceNameEnd() { | ||
int end = pos; | ||
|
||
for (; end < signature.length(); end++) { | ||
char c = signature.charAt(end); | ||
if (c == '[' || c == '<' || c == ',' || c == '>') { | ||
return pos = end; | ||
} | ||
} | ||
|
||
return pos = end; | ||
} | ||
|
||
private void requireIncomplete() { | ||
if (pos >= signature.length()) { | ||
throw new IllegalStateException("Unexpected end of input"); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
61 changes: 61 additions & 0 deletions
61
core/src/test/java/io/smallrye/openapi/runtime/util/TypeParserTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package io.smallrye.openapi.runtime.util; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
|
||
import org.jboss.jandex.Type; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.ValueSource; | ||
|
||
class TypeParserTest { | ||
|
||
@Test | ||
void testParameterizedArrayComponent() { | ||
Type result = TypeParser.parse("java.util.List<? extends java.lang.String[]>[][]"); | ||
assertNotNull(result); | ||
assertEquals(Type.Kind.ARRAY, result.kind()); | ||
assertEquals(2, result.asArrayType().dimensions()); | ||
assertEquals(Type.Kind.PARAMETERIZED_TYPE, result.asArrayType().constituent().kind()); | ||
|
||
Type typeArg = result.asArrayType().constituent().asParameterizedType().arguments().get(0); | ||
|
||
assertEquals(Type.Kind.WILDCARD_TYPE, typeArg.kind()); | ||
assertEquals(Type.Kind.ARRAY, typeArg.asWildcardType().extendsBound().kind()); | ||
assertEquals(1, typeArg.asWildcardType().extendsBound().asArrayType().dimensions()); | ||
assertEquals(Type.Kind.CLASS, typeArg.asWildcardType().extendsBound().asArrayType().constituent().kind()); | ||
assertEquals(String.class.getName(), | ||
typeArg.asWildcardType().extendsBound().asArrayType().constituent().asClassType().name().toString()); | ||
} | ||
|
||
@Test | ||
void testPrimitive() { | ||
Type result = TypeParser.parse("float"); | ||
assertNotNull(result); | ||
assertEquals(Type.Kind.PRIMITIVE, result.kind()); | ||
assertEquals("float", result.name().toString()); | ||
} | ||
|
||
@Test | ||
void testClassType() { | ||
Type result = TypeParser.parse("java.lang.Object"); | ||
assertNotNull(result); | ||
assertEquals(Type.Kind.CLASS, result.kind()); | ||
assertEquals("java.lang.Object", result.name().toString()); | ||
} | ||
|
||
@ParameterizedTest | ||
@ValueSource(strings = { | ||
"java.util.List<? super java.lang.String>", | ||
"java.util.List<? extends java.lang.String>", | ||
"java.util.List<?>" | ||
}) | ||
void testWildcards(String typeSignature) { | ||
Type result = TypeParser.parse(typeSignature); | ||
assertNotNull(result); | ||
assertEquals(Type.Kind.PARAMETERIZED_TYPE, result.kind()); | ||
assertEquals("java.util.List", result.name().toString()); | ||
assertEquals(1, result.asParameterizedType().arguments().size()); | ||
assertEquals(Type.Kind.WILDCARD_TYPE, result.asParameterizedType().arguments().get(0).kind()); | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
...smallrye/openapi/runtime/scanner/components.schemas.parameterized-type-schema-config.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"openapi" : "3.0.3", | ||
"components" : { | ||
"schemas" : { | ||
"Bean" : { | ||
"type" : "object", | ||
"properties" : { | ||
"nullableString" : { | ||
"$ref" : "#/components/schemas/NullableStringArray" | ||
} | ||
} | ||
}, | ||
"NullableStringArray" : { | ||
"type" : "array", | ||
"items" : { | ||
"type" : "string" | ||
}, | ||
"nullable" : true | ||
} | ||
} | ||
} | ||
} |