diff --git a/http-api/src/main/java/io/avaje/http/api/MappedParam.java b/http-api/src/main/java/io/avaje/http/api/MappedParam.java new file mode 100644 index 00000000..023eb2a7 --- /dev/null +++ b/http-api/src/main/java/io/avaje/http/api/MappedParam.java @@ -0,0 +1,50 @@ +package io.avaje.http.api; + +import static java.lang.annotation.ElementType.MODULE; +import static java.lang.annotation.ElementType.PACKAGE; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a type to be mapped. + *

+ * The type needs to have a single constructor argument that is a String type, + * or have a factory method that has a single argument that is a String type. + */ +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.TYPE}) +public @interface MappedParam { + + /** Factory method name used to construct the type. Empty means use a constructor */ + String factoryMethod() default ""; + + /** + * Import a type to be mapped. + *

+ * The type needs to have a single constructor argument that is a String type, + * or have a factory method that has a single argument that is a String type. + */ + @Repeatable(MappedParam.Import.Imports.class) + @Retention(SOURCE) + @Target({TYPE, PACKAGE, MODULE}) + @interface Import { + + Class value(); + + /** Factory method name used to construct the type. Empty means use a constructor */ + String factoryMethod() default ""; + + @Retention(SOURCE) + @Target({TYPE, PACKAGE, MODULE}) + @interface Imports { + + Import[] value(); + } + } +} diff --git a/http-api/src/main/java/io/avaje/http/api/PathTypeConversion.java b/http-api/src/main/java/io/avaje/http/api/PathTypeConversion.java index 55706c0b..7db576a0 100644 --- a/http-api/src/main/java/io/avaje/http/api/PathTypeConversion.java +++ b/http-api/src/main/java/io/avaje/http/api/PathTypeConversion.java @@ -98,6 +98,12 @@ public static int asInt(String value) { } } + /** Convert to type (not nullable). */ + public static T asType(Function typeConversion, String value) { + checkNull(value); + return typeConversion.apply(value); + } + /** * Convert to enum. */ @@ -288,9 +294,15 @@ public static Integer asInteger(String value) { } } - /** - * Convert to enum of the given type. - */ + /** Convert to type */ + public static T toType(Function typeConversion, String value) { + if (isNullOrEmpty(value)) { + return null; + } + return typeConversion.apply(value); + } + + /** Convert to enum of the given type. */ @SuppressWarnings({"rawtypes"}) public static Enum toEnum(Class clazz, String value) { if (isNullOrEmpty(value)) { diff --git a/http-generator-core/src/main/java/io/avaje/http/generator/core/BaseProcessor.java b/http-generator-core/src/main/java/io/avaje/http/generator/core/BaseProcessor.java index 8ea2f187..3cb1e347 100644 --- a/http-generator-core/src/main/java/io/avaje/http/generator/core/BaseProcessor.java +++ b/http-generator-core/src/main/java/io/avaje/http/generator/core/BaseProcessor.java @@ -1,10 +1,6 @@ package io.avaje.http.generator.core; -import static io.avaje.http.generator.core.ProcessingContext.doc; -import static io.avaje.http.generator.core.ProcessingContext.elements; -import static io.avaje.http.generator.core.ProcessingContext.isOpenApiAvailable; -import static io.avaje.http.generator.core.ProcessingContext.logError; -import static io.avaje.http.generator.core.ProcessingContext.typeElement; +import static io.avaje.http.generator.core.ProcessingContext.*; import static java.util.stream.Collectors.toMap; import java.io.IOException; @@ -14,6 +10,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import java.util.Set; import javax.annotation.processing.AbstractProcessor; @@ -22,9 +19,12 @@ import javax.annotation.processing.SupportedOptions; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.util.ElementFilter; +import io.avaje.http.generator.core.TypeMap.CustomHandler; import io.avaje.prism.GenerateAPContext; import io.avaje.prism.GenerateModuleInfoReader; @@ -54,7 +54,11 @@ public SourceVersion getSupportedSourceVersion() { @Override public Set getSupportedAnnotationTypes() { return Set.of( - PathPrism.PRISM_TYPE, ControllerPrism.PRISM_TYPE, OpenAPIDefinitionPrism.PRISM_TYPE); + PathPrism.PRISM_TYPE, + ControllerPrism.PRISM_TYPE, + OpenAPIDefinitionPrism.PRISM_TYPE, + MappedParamPrism.PRISM_TYPE, + MapImportPrism.PRISM_TYPE); } @Override @@ -65,7 +69,6 @@ public synchronized void init(ProcessingEnvironment processingEnv) { try { var txtFilePath = APContext.getBuildResource(HTTP_CONTROLLERS_TXT); - if (txtFilePath.toFile().exists()) { Files.lines(txtFilePath).forEach(clientFQNs::add); } @@ -75,8 +78,8 @@ public synchronized void init(ProcessingEnvironment processingEnv) { } } } catch (IOException e) { - e.printStackTrace(); // not worth failing over + logWarn("Error reading test controllers %s", e); } } @@ -88,6 +91,17 @@ public boolean process(Set annotations, RoundEnvironment if (round.errorRaised()) { return false; } + + for (final var type : ElementFilter.typesIn(getElements(round, MappedParamPrism.PRISM_TYPE))) { + var prism = MappedParamPrism.getInstanceOn(type); + registerParamMapping(type, prism.factoryMethod()); + } + + for (final var type : getElements(round, MapImportPrism.PRISM_TYPE)) { + var prism = MapImportPrism.getInstanceOn(type); + registerParamMapping(APContext.asTypeElement(prism.value()), prism.factoryMethod()); + } + var pathElements = round.getElementsAnnotatedWith(typeElement(PathPrism.PRISM_TYPE)); APContext.setProjectModuleElement(annotations, round); if (contextPathString == null) { @@ -111,9 +125,7 @@ public boolean process(Set annotations, RoundEnvironment readSecuritySchemes(round); } - for (final var controller : - ElementFilter.typesIn( - round.getElementsAnnotatedWith(typeElement(ControllerPrism.PRISM_TYPE)))) { + for (final var controller : ElementFilter.typesIn(round.getElementsAnnotatedWith(typeElement(ControllerPrism.PRISM_TYPE)))) { writeAdapter(controller); } @@ -136,9 +148,33 @@ public boolean process(Set annotations, RoundEnvironment return false; } + private Set getElements(RoundEnvironment round, String name) { + return Optional.ofNullable(typeElement(name)) + .map(round::getElementsAnnotatedWith) + .orElse(Set.of()); + } + + private final void registerParamMapping(final TypeElement type, String factoryMethod) { + if (factoryMethod.isBlank()) { + Util.stringConstructor(type) + .ifPresentOrElse( + c -> TypeMap.add(new CustomHandler(UType.parse(type.asType()), "")), + () -> logError(type, "Missing constructor %s(String s)")); + + } else { + ElementFilter.methodsIn(type.getEnclosedElements()).stream() + .filter(m -> m.getSimpleName().contentEquals(factoryMethod) + && m.getModifiers().contains(Modifier.STATIC) + && Util.singleStringParam(m)) + .findAny() + .ifPresentOrElse( + c -> TypeMap.add(new CustomHandler(UType.parse(type.asType()), factoryMethod)), + () -> logError(type, "Missing static factory method %s(String s)", factoryMethod)); + } + } + private void readOpenApiDefinition(RoundEnvironment round) { - for (final Element element : - round.getElementsAnnotatedWith(typeElement(OpenAPIDefinitionPrism.PRISM_TYPE))) { + for (final Element element : round.getElementsAnnotatedWith(typeElement(OpenAPIDefinitionPrism.PRISM_TYPE))) { doc().readApiDefinition(element); } } @@ -147,19 +183,16 @@ private void readTagDefinitions(RoundEnvironment round) { for (final Element element : round.getElementsAnnotatedWith(typeElement(TagPrism.PRISM_TYPE))) { doc().addTagDefinition(element); } - for (final Element element : - round.getElementsAnnotatedWith(typeElement(TagsPrism.PRISM_TYPE))) { + for (final Element element : round.getElementsAnnotatedWith(typeElement(TagsPrism.PRISM_TYPE))) { doc().addTagsDefinition(element); } } private void readSecuritySchemes(RoundEnvironment round) { - for (final Element element : - round.getElementsAnnotatedWith(typeElement(SecuritySchemePrism.PRISM_TYPE))) { + for (final Element element : round.getElementsAnnotatedWith(typeElement(SecuritySchemePrism.PRISM_TYPE))) { doc().addSecurityScheme(element); } - for (final Element element : - round.getElementsAnnotatedWith(typeElement(SecuritySchemesPrism.PRISM_TYPE))) { + for (final Element element : round.getElementsAnnotatedWith(typeElement(SecuritySchemesPrism.PRISM_TYPE))) { doc().addSecuritySchemes(element); } } @@ -174,7 +207,6 @@ private void writeAdapter(TypeElement controller) { final var reader = new ControllerReader(controller, contextPath); reader.read(true); try { - writeControllerAdapter(reader); writeClientAdapter(reader); @@ -184,7 +216,6 @@ private void writeAdapter(TypeElement controller) { } private void writeClientAdapter(ControllerReader reader) { - try { if (reader.beanType().getInterfaces().isEmpty() && "java.lang.Object".equals(reader.beanType().getSuperclass().toString()) diff --git a/http-generator-core/src/main/java/io/avaje/http/generator/core/ElementReader.java b/http-generator-core/src/main/java/io/avaje/http/generator/core/ElementReader.java index d030763d..1c322b07 100644 --- a/http-generator-core/src/main/java/io/avaje/http/generator/core/ElementReader.java +++ b/http-generator-core/src/main/java/io/avaje/http/generator/core/ElementReader.java @@ -14,6 +14,7 @@ import javax.lang.model.element.*; import javax.lang.model.type.TypeMirror; +import io.avaje.http.generator.core.TypeMap.CustomHandler; import io.avaje.http.generator.core.openapi.MethodDocBuilder; import io.avaje.http.generator.core.openapi.MethodParamDocBuilder; @@ -76,7 +77,9 @@ public class ElementReader { if (!contextType) { readAnnotations(element, defaultType); useValidation = useValidation(); - HttpValidPrism.getOptionalOn(element.getEnclosingElement()).map(HttpValidPrism::groups).stream() + HttpValidPrism.getOptionalOn(element.getEnclosingElement()) + .map(HttpValidPrism::groups) + .stream() .flatMap(List::stream) .map(TypeMirror::toString) .forEach(validationGroups::add); @@ -101,37 +104,58 @@ private void beanParamImports(String rawType) { } TypeHandler initTypeHandler() { - if (specialParam) { - final var typeOp = Optional.ofNullable(type).or(() -> Optional.of(UType.parse(element.asType()))); + var handler = TypeMap.get(rawType); + final var typeOp = Optional.ofNullable(type).or(() -> Optional.of(UType.parse(element.asType()))); + + var customType = typeOp.orElseThrow(); + var actual = customType.isGeneric() ? UType.parse(customType.param0()) : customType; + + if (handler == null) { + Optional.ofNullable(APContext.typeElement(customType.full())) + .flatMap(MappedParamPrism::getOptionalOn) + .ifPresent(p -> TypeMap.add(new CustomHandler(actual, p.factoryMethod()))); + handler = TypeMap.get(rawType); + } + + if (handler == null && ParamPrism.isPresent(element)) { + handler = + Optional.ofNullable(APContext.typeElement(customType.full())) + .flatMap(Util::stringConstructor) + .map(m -> new CustomHandler(actual, "")) + .orElse(null); + } + + if (specialParam) { final var mainTypeEnum = - typeOp - .flatMap(t -> Optional.ofNullable(typeElement(t.mainType()))) - .map(TypeElement::getKind) - .filter(ElementKind.ENUM::equals) - .isPresent(); + typeOp + .flatMap(t -> Optional.ofNullable(typeElement(t.mainType()))) + .map(TypeElement::getKind) + .filter(ElementKind.ENUM::equals) + .isPresent(); final var isCollection = - typeOp - .filter(t -> t.isGeneric() && !t.mainType().startsWith("java.util.Map")) - .isPresent(); + typeOp + .filter(t -> t.isGeneric() && !t.mainType().startsWith("java.util.Map")) + .isPresent(); final var isMap = - !isCollection && typeOp.filter(t -> t.mainType().startsWith("java.util.Map")).isPresent(); + !isCollection && typeOp.filter(t -> t.mainType().startsWith("java.util.Map")).isPresent(); - final var isOptional = typeOp.filter(t -> t.mainType().startsWith("java.util.Optional")).isPresent(); + final var isOptional = + typeOp.filter(t -> t.mainType().startsWith("java.util.Optional")).isPresent(); if (mainTypeEnum) { return TypeMap.enumParamHandler(typeOp.orElseThrow()); } else if (isCollection || isOptional) { final var isEnumContainer = - typeOp - .flatMap(t -> Optional.ofNullable(typeElement(t.param0()))) - .map(TypeElement::getKind) - .filter(ElementKind.ENUM::equals) - .isPresent(); + typeOp + .flatMap(t -> Optional.ofNullable(typeElement(t.param0()))) + .map(TypeElement::getKind) + .filter(ElementKind.ENUM::equals) + .isPresent(); - if (isOptional) {//Needs to be checked first, as 'isCollection' is too broad + if (isOptional) { // needs to be checked first, as 'isCollection' is too broad return TypeMap.optionalHandler(typeOp.orElseThrow(), isEnumContainer); } this.isParamCollection = true; @@ -142,7 +166,7 @@ TypeHandler initTypeHandler() { } } - return TypeMap.get(rawType); + return handler; } private boolean useValidation() { @@ -312,7 +336,7 @@ void writeValidate(Append writer) { } void writeCtxGet(Append writer, PathSegments segments) { - if (isPlatformContext() || (paramType == ParamType.BODY && platform().isBodyMethodParam())) { + if (isPlatformContext() || paramType == ParamType.BODY && platform().isBodyMethodParam()) { // body passed as method parameter (Helidon) return; } @@ -347,9 +371,9 @@ private boolean setValue(Append writer, PathSegments segments, String shortType) // path or matrix parameter final boolean requiredParam = segment.isRequired(varName); final String asMethod = - (typeHandler == null) - ? null - : (requiredParam) ? typeHandler.asMethod() : typeHandler.toMethod(); + typeHandler == null + ? null + : requiredParam ? typeHandler.asMethod() : typeHandler.toMethod(); if (asMethod != null) { writer.append(asMethod); } @@ -362,7 +386,7 @@ private boolean setValue(Append writer, PathSegments segments, String shortType) } } - final String asMethod = (typeHandler == null) ? null : typeHandler.toMethod(); + final String asMethod = typeHandler == null ? null : typeHandler.toMethod(); if (asMethod != null) { writer.append(asMethod); } @@ -382,7 +406,8 @@ private boolean setValue(Append writer, PathSegments segments, String shortType) } else if (hasParamDefault()) { platform().writeReadParameter(writer, paramType, paramName, paramDefault.get(0)); } else { - final var checkNull = notNullKotlin || (paramType == ParamType.FORMPARAM && typeHandler.isPrimitive()); + final var checkNull = + notNullKotlin || paramType == ParamType.FORMPARAM && typeHandler.isPrimitive(); if (checkNull) { writer.append("checkNull("); } diff --git a/http-generator-core/src/main/java/io/avaje/http/generator/core/ParamPrism.java b/http-generator-core/src/main/java/io/avaje/http/generator/core/ParamPrism.java new file mode 100644 index 00000000..29a433f8 --- /dev/null +++ b/http-generator-core/src/main/java/io/avaje/http/generator/core/ParamPrism.java @@ -0,0 +1,41 @@ +package io.avaje.http.generator.core; + +import java.util.Optional; + +import javax.lang.model.element.Element; + +import io.avaje.prism.GeneratePrism; + +@GeneratePrism( + value = io.avaje.http.api.QueryParam.class, + publicAccess = true, + superInterfaces = ParamPrism.class) +@GeneratePrism( + value = io.avaje.http.api.Cookie.class, + publicAccess = true, + superInterfaces = ParamPrism.class) +@GeneratePrism( + value = io.avaje.http.api.FormParam.class, + publicAccess = true, + superInterfaces = ParamPrism.class) +@GeneratePrism( + value = io.avaje.http.api.Header.class, + publicAccess = true, + superInterfaces = ParamPrism.class) +@GeneratePrism( + value = io.avaje.http.api.MatrixParam.class, + publicAccess = true, + superInterfaces = ParamPrism.class) +public interface ParamPrism { + + static boolean isPresent(Element e) { + return Optional.empty() + .or(() -> QueryParamPrism.getOptionalOn(e)) + .or(() -> CookiePrism.getOptionalOn(e)) + .or(() -> FormParamPrism.getOptionalOn(e)) + .or(() -> HeaderPrism.getOptionalOn(e)) + .or(() -> MatrixParamPrism.getOptionalOn(e)) + .isPresent(); + } + +} diff --git a/http-generator-core/src/main/java/io/avaje/http/generator/core/TypeMap.java b/http-generator-core/src/main/java/io/avaje/http/generator/core/TypeMap.java index 8b6452f5..a7741f2f 100644 --- a/http-generator-core/src/main/java/io/avaje/http/generator/core/TypeMap.java +++ b/http-generator-core/src/main/java/io/avaje/http/generator/core/TypeMap.java @@ -14,7 +14,7 @@ final class TypeMap { private static final Map types = new HashMap<>(); - private static void add(TypeHandler h) { + static void add(TypeHandler h) { types.put(h.importTypes().get(0), h); } @@ -342,13 +342,12 @@ private CollectionHandler(TypeHandler handler, boolean set, boolean isEnum) { this.importTypes.add("io.avaje.http.api.PathTypeConversion"); this.shortName = handler.shortName(); String _toMethod = - (set ? "set" : "list") - + "(" - + (isEnum - ? "qp -> " + handler.toMethod() + " qp)" - : "PathTypeConversion::as" + shortName) - + ", "; - + (set ? "set" : "list") + + "(" + + (isEnum || handler instanceof CustomHandler + ? "qp -> " + handler.toMethod() + " qp)" + : "PathTypeConversion::as" + shortName) + + ", "; this.toMethod = _toMethod.replace("PathTypeConversion::asString", "Object::toString"); } @@ -397,7 +396,7 @@ private OptionalHandler(TypeHandler handler, boolean isEnum) { } static String buildToMethod(TypeHandler handler, boolean isEnum) { - if (isEnum) { + if (isEnum || handler instanceof CustomHandler) { return "optional(qp -> " + handler.toMethod() + " qp), "; } if ("String".equals(handler.shortName())) { @@ -433,6 +432,32 @@ public String toMethod() { } } + static final class CustomHandler extends ObjectHandler { + + private final UType type; + private final String factory; + + CustomHandler(UType type, String factory) { + super(type.mainType(), type.shortName()); + this.type = type; + this.factory = factory; + } + + @Override + public String toMethod() { + return "toType(" + + type.shortTypeNested() + + "::" + + (factory.isBlank() ? "new" : factory) + + ", "; + } + + @Override + public String asMethod() { + return "asType(" + type.shortTypeNested() + "::" + (factory.isBlank() ? "new" : factory); + } + } + static abstract class ObjectHandler implements TypeHandler { private final String importType; diff --git a/http-generator-core/src/main/java/io/avaje/http/generator/core/Util.java b/http-generator-core/src/main/java/io/avaje/http/generator/core/Util.java index 3a6a79b6..8ba9204a 100644 --- a/http-generator-core/src/main/java/io/avaje/http/generator/core/Util.java +++ b/http-generator-core/src/main/java/io/avaje/http/generator/core/Util.java @@ -1,20 +1,25 @@ package io.avaje.http.generator.core; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; import javax.lang.model.util.SimpleAnnotationValueVisitor8; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -public class Util { +public final class Util { // whitespace not in quotes private static final Pattern WHITE_SPACE_REGEX = Pattern.compile("\\s+(?=([^\"]*\"[^\"]*\")*[^\"]*$)"); @@ -266,4 +271,22 @@ public List visitEnumConstant(VariableElement roleEnum, Object o) { return fullRoles; } } + + static Optional stringConstructor(TypeElement typeElement) { + return ElementFilter.constructorsIn(typeElement.getEnclosedElements()).stream() + .filter(Util::singleStringParam) + .findAny(); + } + + static boolean singleStringParam(ExecutableElement m) { + return m.getParameters().size() == 1 && firstParamIsString(m); + } + + static boolean firstParamIsString(ExecutableElement m) { + return m.getParameters() + .get(0) + .asType() + .toString() + .equals(String.class.getTypeName()); + } } diff --git a/http-generator-core/src/main/java/io/avaje/http/generator/core/package-info.java b/http-generator-core/src/main/java/io/avaje/http/generator/core/package-info.java index 50ff9161..e71d98d9 100644 --- a/http-generator-core/src/main/java/io/avaje/http/generator/core/package-info.java +++ b/http-generator-core/src/main/java/io/avaje/http/generator/core/package-info.java @@ -1,18 +1,15 @@ -/** Generate the prisms to access annotation info */ +/** + * Generate the prisms to access annotation info + */ @GeneratePrism(value = io.avaje.http.api.Controller.class, publicAccess = true, superInterfaces = WebAPIPrism.class) @GeneratePrism(value = io.avaje.http.api.Client.class, publicAccess = true, superInterfaces = WebAPIPrism.class) @GeneratePrism(value = io.avaje.http.api.BeanParam.class, publicAccess = true) @GeneratePrism(value = io.avaje.http.api.Ignore.class, publicAccess = true) -@GeneratePrism(value = io.avaje.http.api.QueryParam.class, publicAccess = true) -@GeneratePrism(value = io.avaje.http.api.Cookie.class, publicAccess = true) @GeneratePrism(value = io.avaje.http.api.BodyString.class, publicAccess = true) @GeneratePrism(value = io.avaje.http.api.Default.class, publicAccess = true) @GeneratePrism(value = io.avaje.http.api.Delete.class, publicAccess = true) @GeneratePrism(value = io.avaje.http.api.Form.class, publicAccess = true) -@GeneratePrism(value = io.avaje.http.api.FormParam.class, publicAccess = true) @GeneratePrism(value = io.avaje.http.api.Get.class, publicAccess = true) -@GeneratePrism(value = io.avaje.http.api.Header.class, publicAccess = true) -@GeneratePrism(value = io.avaje.http.api.MatrixParam.class, publicAccess = true) @GeneratePrism(value = io.avaje.http.api.Patch.class, publicAccess = true) @GeneratePrism(value = io.avaje.http.api.Path.class, publicAccess = true) @GeneratePrism(value = io.avaje.http.api.Post.class, publicAccess = true) @@ -22,6 +19,8 @@ @GeneratePrism(value = io.avaje.http.api.Filter.class) @GeneratePrism(value = io.avaje.http.api.InstrumentServerContext.class) @GeneratePrism(value = io.avaje.http.api.ExceptionHandler.class) +@GeneratePrism(value = io.avaje.http.api.MappedParam.class) +@GeneratePrism(value = io.avaje.http.api.MappedParam.Import.class, name = "MapImportPrism") @GeneratePrism(value = io.swagger.v3.oas.annotations.OpenAPIDefinition.class, publicAccess = true) @GeneratePrism(value = io.swagger.v3.oas.annotations.Operation.class, publicAccess = true) @GeneratePrism(value = io.swagger.v3.oas.annotations.tags.Tag.class, publicAccess = true) diff --git a/tests/test-nima-jsonb/src/main/java/org/example/TestController.java b/tests/test-nima-jsonb/src/main/java/org/example/TestController.java index bbb0db33..afcd656b 100644 --- a/tests/test-nima-jsonb/src/main/java/org/example/TestController.java +++ b/tests/test-nima-jsonb/src/main/java/org/example/TestController.java @@ -1,7 +1,10 @@ package org.example; import java.io.InputStream; -import java.util.*; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import io.avaje.http.api.BodyString; import io.avaje.http.api.Controller; @@ -12,6 +15,7 @@ import io.avaje.http.api.FormParam; import io.avaje.http.api.Get; import io.avaje.http.api.InstrumentServerContext; +import io.avaje.http.api.MappedParam; import io.avaje.http.api.Path; import io.avaje.http.api.Post; import io.avaje.http.api.Produces; @@ -162,4 +166,45 @@ Person maybePerson(boolean maybe) { List maybePersonList(boolean maybe) { return maybe ? List.of(new Person(9, "hi")) : null; // Collections.emptyList(); } + + @MappedParam + @MappedParam.Import(Simple2.class) + record Simple(String name) {} + + record Simple2(String name) {} + + @Form + @Get("/typeForm") + String typeForm(Simple s, Simple2 type) { + return type.name(); + } + + @MappedParam(factoryMethod = "build") + record Static(String name) { + static Static build(String name) { + return new Static(name); + } + } + + @Get("/typeFormParam") + String typeFormParam(@FormParam String s, @FormParam Static type) { + return type.name(); + } + + @Get("/typeQuery") + String typeQuery(@QueryParam @Default("FFA") Static type) { + return type.name(); + } + + @Get("/typeQuery2") + String typeMultiQuery(@QueryParam @Default({"FFA", "PROXY"}) Set type) { + return type.toString(); + } + + record Implied(String name) {} + + @Post("/typeQueryImplied") + String typeQueryImplied(String s, @QueryParam Implied type) { + return type.name(); + } }