From 80ab753f08830b9576038e6bc0a373fbce36d485 Mon Sep 17 00:00:00 2001 From: Joshua Bauer Date: Mon, 22 Apr 2019 18:21:27 -0700 Subject: [PATCH] Added support for more flexible schemas. --- .../proteus/modules/ApplicationModule.java | 3 + .../proteus/modules/JacksonModule.java | 5 - .../{swagger => }/test/controllers/Tests.java | 5 +- .../{swagger => }/test/models/User.java | 2 +- .../test/server/DefaultServer.java | 4 +- .../test/server/TestControllerEndpoints.java | 6 +- openapi/pom.xml | 9 +- .../proteus/openapi/jaxrs2/Reader.java | 11 +- .../openapi/jaxrs2/ServerModelResolver.java | 41 ++- .../jaxrs2/ServerParameterExtension.java | 10 +- .../openapi/models/MoneyModelConverter.java | 157 +++++++++ .../openapi/services/OpenAPIService.java | 5 +- .../test/controllers/OpenAPITests.java | 316 ++++++++++++++++++ .../proteus/openapi/test/models/Pojo.java | 40 +++ .../test/server/OpenAPIDefaultServer.java | 129 +++++++ .../TestOpenAPIControllerEndpoints.java | 107 ++++++ .../{swagger => }/test/controllers/Tests.java | 17 +- .../test/server/DefaultServer.java | 4 +- .../test/server/TestControllerEndpoints.java | 8 +- openapi/src/test/resources/application.conf | 93 ++++++ openapi/src/test/resources/development.jks | Bin 0 -> 3916 bytes openapi/src/test/resources/development.ts | Bin 0 -> 1471 bytes openapi/src/test/resources/logback-test.xml | 8 +- .../swagger/test/controllers/Tests.java | 14 +- .../test/server/TestControllerEndpoints.java | 21 +- .../proteus/test/controllers/Tests.java | 261 +++++++++++++++ .../proteus/test/server/DefaultServer.java | 129 +++++++ .../test/server/TestControllerEndpoints.java | 88 +++++ 28 files changed, 1447 insertions(+), 46 deletions(-) rename core/src/test/java/io/sinistral/proteus/{swagger => }/test/controllers/Tests.java (98%) rename core/src/test/java/io/sinistral/proteus/{swagger => }/test/models/User.java (94%) rename core/src/test/java/io/sinistral/proteus/{swagger => }/test/server/DefaultServer.java (95%) rename core/src/test/java/io/sinistral/proteus/{swagger => }/test/server/TestControllerEndpoints.java (98%) create mode 100644 openapi/src/main/java/io/sinistral/proteus/openapi/models/MoneyModelConverter.java create mode 100644 openapi/src/test/java/io/sinistral/proteus/openapi/test/controllers/OpenAPITests.java create mode 100644 openapi/src/test/java/io/sinistral/proteus/openapi/test/models/Pojo.java create mode 100644 openapi/src/test/java/io/sinistral/proteus/openapi/test/server/OpenAPIDefaultServer.java create mode 100644 openapi/src/test/java/io/sinistral/proteus/openapi/test/server/TestOpenAPIControllerEndpoints.java rename openapi/src/test/java/io/sinistral/proteus/{swagger => }/test/controllers/Tests.java (95%) rename openapi/src/test/java/io/sinistral/proteus/{swagger => }/test/server/DefaultServer.java (95%) rename openapi/src/test/java/io/sinistral/proteus/{swagger => }/test/server/TestControllerEndpoints.java (91%) create mode 100644 openapi/src/test/resources/application.conf create mode 100644 openapi/src/test/resources/development.jks create mode 100644 openapi/src/test/resources/development.ts create mode 100644 swagger/src/test/java/io/sinistral/proteus/test/controllers/Tests.java create mode 100644 swagger/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java create mode 100644 swagger/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java diff --git a/core/src/main/java/io/sinistral/proteus/modules/ApplicationModule.java b/core/src/main/java/io/sinistral/proteus/modules/ApplicationModule.java index 6889df0..b6a13ef 100644 --- a/core/src/main/java/io/sinistral/proteus/modules/ApplicationModule.java +++ b/core/src/main/java/io/sinistral/proteus/modules/ApplicationModule.java @@ -81,6 +81,9 @@ public void bindMappers() } + this.requestStaticInjection(Extractors.class); + this.requestStaticInjection(ServerResponse.class); + } @SuppressWarnings("unchecked") diff --git a/core/src/main/java/io/sinistral/proteus/modules/JacksonModule.java b/core/src/main/java/io/sinistral/proteus/modules/JacksonModule.java index fa5a7f0..b66e00e 100644 --- a/core/src/main/java/io/sinistral/proteus/modules/JacksonModule.java +++ b/core/src/main/java/io/sinistral/proteus/modules/JacksonModule.java @@ -5,8 +5,6 @@ import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import com.fasterxml.jackson.module.afterburner.AfterburnerModule; import com.google.inject.AbstractModule; -import io.sinistral.proteus.server.Extractors; -import io.sinistral.proteus.server.ServerResponse; public class JacksonModule extends AbstractModule { @@ -25,8 +23,5 @@ protected void configure() objectMapper.registerModule(new Jdk8Module()); this.bind(ObjectMapper.class).toInstance(objectMapper); - - this.requestStaticInjection(Extractors.class); - this.requestStaticInjection(ServerResponse.class); } } diff --git a/core/src/test/java/io/sinistral/proteus/swagger/test/controllers/Tests.java b/core/src/test/java/io/sinistral/proteus/test/controllers/Tests.java similarity index 98% rename from core/src/test/java/io/sinistral/proteus/swagger/test/controllers/Tests.java rename to core/src/test/java/io/sinistral/proteus/test/controllers/Tests.java index 3a2f3ed..aa852b6 100644 --- a/core/src/test/java/io/sinistral/proteus/swagger/test/controllers/Tests.java +++ b/core/src/test/java/io/sinistral/proteus/test/controllers/Tests.java @@ -1,7 +1,7 @@ /** * */ -package io.sinistral.proteus.swagger.test.controllers; +package io.sinistral.proteus.test.controllers; import static io.sinistral.proteus.server.ServerResponse.response; @@ -37,10 +37,9 @@ import com.google.inject.Singleton; import io.sinistral.proteus.annotations.Blocking; -import io.sinistral.proteus.annotations.Debug; import io.sinistral.proteus.server.ServerRequest; import io.sinistral.proteus.server.ServerResponse; -import io.sinistral.proteus.swagger.test.models.User; +import io.sinistral.proteus.test.models.User; import io.undertow.server.HttpServerExchange; diff --git a/core/src/test/java/io/sinistral/proteus/swagger/test/models/User.java b/core/src/test/java/io/sinistral/proteus/test/models/User.java similarity index 94% rename from core/src/test/java/io/sinistral/proteus/swagger/test/models/User.java rename to core/src/test/java/io/sinistral/proteus/test/models/User.java index 32d81c2..04e405e 100644 --- a/core/src/test/java/io/sinistral/proteus/swagger/test/models/User.java +++ b/core/src/test/java/io/sinistral/proteus/test/models/User.java @@ -1,7 +1,7 @@ /** * */ -package io.sinistral.proteus.swagger.test.models; +package io.sinistral.proteus.test.models; /** diff --git a/core/src/test/java/io/sinistral/proteus/swagger/test/server/DefaultServer.java b/core/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java similarity index 95% rename from core/src/test/java/io/sinistral/proteus/swagger/test/server/DefaultServer.java rename to core/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java index a65fab2..4f651d7 100644 --- a/core/src/test/java/io/sinistral/proteus/swagger/test/server/DefaultServer.java +++ b/core/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java @@ -1,11 +1,11 @@ /** * */ -package io.sinistral.proteus.swagger.test.server; +package io.sinistral.proteus.test.server; import java.util.List; -import io.sinistral.proteus.swagger.test.controllers.Tests; +import io.sinistral.proteus.test.controllers.Tests; import org.junit.runner.Description; import org.junit.runner.Result; import org.junit.runner.notification.RunListener; diff --git a/core/src/test/java/io/sinistral/proteus/swagger/test/server/TestControllerEndpoints.java b/core/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java similarity index 98% rename from core/src/test/java/io/sinistral/proteus/swagger/test/server/TestControllerEndpoints.java rename to core/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java index ae48ec1..64aaf53 100644 --- a/core/src/test/java/io/sinistral/proteus/swagger/test/server/TestControllerEndpoints.java +++ b/core/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java @@ -1,7 +1,7 @@ /** * */ -package io.sinistral.proteus.swagger.test.server; +package io.sinistral.proteus.test.server; import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.containsString; @@ -36,8 +36,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.restassured.http.ContentType; -import io.sinistral.proteus.swagger.test.models.User; -import io.sinistral.proteus.swagger.test.models.User.UserType; +import io.sinistral.proteus.test.models.User; +import io.sinistral.proteus.test.models.User.UserType; /* * import static io.restassured.RestAssured.*; import static io.restassured.matcher.RestAssuredMatchers.*; import static org.hamcrest.Matchers.*; diff --git a/openapi/pom.xml b/openapi/pom.xml index 7badaa1..1758cea 100644 --- a/openapi/pom.xml +++ b/openapi/pom.xml @@ -29,7 +29,7 @@ src/test/resources - src/test/java + src/test/java/sinistral/proteus/openapi **/*.java @@ -109,6 +109,13 @@ swagger-integration ${openapi.version} + + + org.zalando + jackson-datatype-money + 1.1.1 + + ${project.groupId} proteus-core diff --git a/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/Reader.java b/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/Reader.java index 69522a4..b3e3d7d 100644 --- a/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/Reader.java +++ b/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/Reader.java @@ -13,6 +13,7 @@ import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; import com.fasterxml.jackson.databind.introspect.AnnotatedParameter; import com.fasterxml.jackson.databind.type.TypeFactory; +import io.sinistral.proteus.openapi.models.MoneyModelConverter; import io.sinistral.proteus.server.ServerRequest; import io.sinistral.proteus.server.ServerResponse; import io.swagger.v3.core.converter.AnnotatedType; @@ -1373,11 +1374,19 @@ else if (classType.getRawClass().isAssignableFrom(CompletableFuture.class)) } } - ResolvedSchema resolvedSchema = ModelConverters.getInstance() + ModelConverters converters = ModelConverters.getInstance(); + + converters.addConverter(new ServerModelResolver()); + converters.addConverter(new MoneyModelConverter()); + + + ResolvedSchema resolvedSchema = converters .resolveAsResolvedSchema(new AnnotatedType(returnType).resolveAsRef(true).jsonViewAnnotation(jsonViewAnnotation)); if (resolvedSchema.schema != null) { + System.out.println("resolvedSchema: " + resolvedSchema.schema); + Schema returnTypeSchema = resolvedSchema.schema; Content content = new Content(); MediaType mediaType = new MediaType().schema(returnTypeSchema); diff --git a/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerModelResolver.java b/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerModelResolver.java index 0944968..0d364f5 100644 --- a/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerModelResolver.java +++ b/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerModelResolver.java @@ -8,12 +8,15 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.introspect.Annotated; import com.fasterxml.jackson.databind.type.TypeFactory; +import io.sinistral.proteus.openapi.models.MoneyModelConverter; import io.sinistral.proteus.server.ServerResponse; import io.swagger.v3.core.converter.AnnotatedType; import io.swagger.v3.core.converter.ModelConverter; import io.swagger.v3.core.converter.ModelConverterContext; import io.swagger.v3.core.util.Json; import io.swagger.v3.oas.models.media.Schema; +import org.javamoney.moneta.Money; +import org.zalando.jackson.datatype.money.MoneyModule; import java.io.File; import java.lang.annotation.Annotation; @@ -30,9 +33,19 @@ public class ServerModelResolver extends io.swagger.v3.core.jackson.ModelResolve { private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ServerModelResolver.class.getCanonicalName()); + static final ObjectMapper mapper = Json.mapper(); + + static + { + mapper.registerModule(new MoneyModule()); + } + public ServerModelResolver() { - super(Json.mapper()); + super(mapper); + + + System.out.println("not from super"); } /** @@ -40,7 +53,8 @@ public ServerModelResolver() */ public ServerModelResolver(ObjectMapper mapper) { - super(mapper); + super(ServerModelResolver.mapper); + } /* @@ -83,6 +97,11 @@ else if (rawClass.isAssignableFrom(CompletableFuture.class)) if (resolvedType != null) { +// if (resolvedType.getTypeName().contains("org.javamoney.moneta.Money")) +// { +// resolvedType = TypeFactory.defaultInstance().constructFromCanonical(Money.class.getName()); +// } + if (resolvedType.getTypeName().contains("java.lang.Void")) { resolvedType = TypeFactory.defaultInstance().constructFromCanonical(Void.class.getName()); @@ -120,8 +139,24 @@ else if (resolvedType.getTypeName().contains("Optional")) try { + // log.info("Processing " + annotatedType + " " + classType + " " + annotatedType.getName()); - return super.resolve(annotatedType, context, next); + + if(annotatedType.getType().getTypeName().contains("org.javamoney.moneta.Money")) + { + MoneyModelConverter.MoneySchema schema = new MoneyModelConverter.MoneySchema(); + + context.defineModel("money",schema); + + return schema; + } + else { + + Schema schema = super.resolve(annotatedType, context, next); + + return schema; + } + } catch (Exception e) { diff --git a/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerParameterExtension.java b/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerParameterExtension.java index 5d6c85b..56e9efb 100644 --- a/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerParameterExtension.java +++ b/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerParameterExtension.java @@ -4,6 +4,8 @@ */ package io.sinistral.proteus.openapi.jaxrs2; +import org.zalando.jackson.datatype.money.MoneyModule; + import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.JavaType; @@ -38,7 +40,13 @@ public class ServerParameterExtension extends AbstractOpenAPIExtension private static String COOKIE_PARAM = "cookie"; private static String PATH_PARAM = "path"; private static String FORM_PARAM = "form"; - final ObjectMapper mapper = Json.mapper(); + + final static ObjectMapper mapper = Json.mapper(); + + static + { + mapper.registerModule(new MoneyModule()); + } @Override public ResolvedParameter extractParameters(List annotations, Type type, Set typesToSkip, Components components, javax.ws.rs.Consumes classConsumes, diff --git a/openapi/src/main/java/io/sinistral/proteus/openapi/models/MoneyModelConverter.java b/openapi/src/main/java/io/sinistral/proteus/openapi/models/MoneyModelConverter.java new file mode 100644 index 0000000..f37539e --- /dev/null +++ b/openapi/src/main/java/io/sinistral/proteus/openapi/models/MoneyModelConverter.java @@ -0,0 +1,157 @@ +package io.sinistral.proteus.openapi.models; + + +import com.fasterxml.jackson.databind.JavaType; +import com.google.common.collect.ImmutableMap; +import io.swagger.v3.core.converter.AnnotatedType; +import io.swagger.v3.core.converter.ModelConverter; +import io.swagger.v3.core.converter.ModelConverterContext; +import io.swagger.v3.core.util.Json; +import io.swagger.v3.oas.models.media.NumberSchema; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.media.StringSchema; +import org.javamoney.moneta.Money; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class MoneyModelConverter implements ModelConverter +{ + + @Override + public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterator chain) { + + JavaType _type = Json.mapper().constructType(type.getType()); + + if(_type != null && (_type.getRawClass().equals(Money.class))) + { + return new MoneySchema(); + } + + if (type.getType() instanceof Class) { + + System.out.println("MoneyModelConverter resolving class: " + ((Class) type.getType()).getName()); + + Class cls = (Class) type.getType(); + + if(cls.isAssignableFrom(Money.class)) + { + return new MoneySchema(); + } + } + else if(type.getType().getTypeName().equals("[simple type, class org.javamoney.moneta.Money]")) + { + System.out.println("is org.javamoney.moneta.Money"); + return new MoneySchema(); + } + else if (type.isSchemaProperty()) { + _type = Json.mapper().constructType(type.getType()); + + if (_type != null) { + Class cls = _type.getRawClass(); + if (Money.class.isAssignableFrom(cls)) { + return new MoneySchema(); + } + } + } + + if (chain.hasNext()) { + System.out.println("skipped resolving " + _type); + + return chain.next().resolve(type, context, chain); + } + + return null; + + } + + + public static class MoneySchema extends Schema + { + private String _type = "object"; + + private String _$ref = null;// "#/components/schemas/Money"; + + private String _description = "A monetary amount"; + + private Map _properties = ImmutableMap.of("number",new NumberSchema(),"currency",new StringSchema()); + + private List _required = Arrays.asList("number","currency"); + + public MoneySchema() + { + super(); + super.setName("Money"); + super.setType("object"); + super.set$ref(_$ref); + super.description(_description); + super.properties(_properties); + } + + @Override + protected Money cast(Object value) + { + if (value != null) { + try { + if (value instanceof Money) { + return (Money) value; + } + } catch (Exception e) { + } + } + return null; + } + + @Override + public boolean equals(java.lang.Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MoneySchema MoneySchema = (MoneySchema) o; + return Objects.equals(this._type, MoneySchema._type) && + Objects.equals(this._properties, MoneySchema._properties) && + super.equals(o); + } + + @Override + public int hashCode() + { + return Objects.hash(_type, _properties, super.hashCode()); + } + +// @Override +// public String toString() +// { +// StringBuilder sb = new StringBuilder(); +// sb.append("class MoneySchema {\n"); +// sb.append(" ").append(toIndentedString(super.toString())).append("\n"); +// sb.append(" title: ").append(toIndentedString(getTitle())).append("\n"); +// sb.append(" type: ").append(toIndentedString(_type)).append("\n"); +// sb.append(toIndentedString(_properties.toString())).append("\n"); +// sb.append(" }").append("\n"); +// +// sb.append("}"); +// return sb.toString(); +// } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(java.lang.Object o) + { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + } +} diff --git a/openapi/src/main/java/io/sinistral/proteus/openapi/services/OpenAPIService.java b/openapi/src/main/java/io/sinistral/proteus/openapi/services/OpenAPIService.java index 09a2ca0..0d5aeeb 100644 --- a/openapi/src/main/java/io/sinistral/proteus/openapi/services/OpenAPIService.java +++ b/openapi/src/main/java/io/sinistral/proteus/openapi/services/OpenAPIService.java @@ -12,7 +12,9 @@ import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -27,6 +29,7 @@ import io.sinistral.proteus.openapi.jaxrs2.Reader; import io.sinistral.proteus.openapi.jaxrs2.ServerModelResolver; import io.sinistral.proteus.openapi.jaxrs2.ServerParameterExtension; +import io.sinistral.proteus.openapi.models.MoneyModelConverter; import io.sinistral.proteus.services.DefaultService; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; @@ -265,7 +268,7 @@ protected void generateSpec() throws Exception SwaggerConfiguration config = new SwaggerConfiguration().resourceClasses(classes.stream().map(Class::getName).collect(Collectors.toSet())).openAPI(openApi); - config.setModelConverterClassess(Collections.singleton(ServerModelResolver.class.getName())); + config.setModelConverterClassess(new HashSet<>(Arrays.asList(ServerModelResolver.class.getName()))); OpenApiContext ctx = new GenericOpenApiContext().openApiConfiguration(config) .openApiReader(new Reader(config)) diff --git a/openapi/src/test/java/io/sinistral/proteus/openapi/test/controllers/OpenAPITests.java b/openapi/src/test/java/io/sinistral/proteus/openapi/test/controllers/OpenAPITests.java new file mode 100644 index 0000000..9ddefb8 --- /dev/null +++ b/openapi/src/test/java/io/sinistral/proteus/openapi/test/controllers/OpenAPITests.java @@ -0,0 +1,316 @@ +/** + * + */ +package io.sinistral.proteus.openapi.test.controllers; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.Files; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import io.sinistral.proteus.annotations.Blocking; +import io.sinistral.proteus.openapi.test.models.Pojo; +import io.sinistral.proteus.server.ServerRequest; +import io.sinistral.proteus.server.ServerResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.tags.Tags; +import io.undertow.server.HttpServerExchange; +import org.javamoney.moneta.Money; + +import javax.ws.rs.BeanParam; +import javax.ws.rs.Consumes; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import java.io.File; +import java.nio.ByteBuffer; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +import static io.sinistral.proteus.server.ServerResponse.response; + +/** + * @author jbauer + * + */ + +@Tags({@Tag(name = "tests")}) +@Path("/tests") +@Produces((MediaType.APPLICATION_JSON)) +@Consumes((MediaType.MEDIA_TYPE_WILDCARD)) +@Singleton +public class OpenAPITests +{ + private static final ByteBuffer buffer; + static { + String message = "Hello, World!"; + byte[] messageBytes = message.getBytes(java.nio.charset.StandardCharsets.US_ASCII); + buffer = ByteBuffer.allocateDirect(messageBytes.length); + buffer.put(messageBytes); + buffer.flip(); + } + + @Inject + protected ObjectMapper objectMapper; + + + @GET + @Path("exchange/plaintext") + @Produces((MediaType.TEXT_PLAIN)) + @Operation(description = "Plaintext endpoint" ) + public void exchangePlaintext(HttpServerExchange exchange) + { + response("Hello, World!").textPlain().send(exchange); + + } + + + @GET + @Path("response/plaintext") + @Produces((MediaType.TEXT_PLAIN)) + @Operation(description = "Plaintext endpoint" ) + public ServerResponse responsePlaintext(ServerRequest request) + { + return response("Hello, World!").textPlain(); + + } + + @GET + @Path("response/future/map") + @Operation(description = "Future map endpoint" ) + public CompletableFuture>> responseFutureMap( ServerRequest request ) + { + Map map = ImmutableMap.of("message", "success"); + return CompletableFuture.completedFuture(response( map ).applicationJson()); + } + + @GET + @Path("response/map") + @Operation(description = "Map endpoint" ) + public ServerResponse> futureMap( ServerRequest request ) + { + Map map = ImmutableMap.of("message", "success"); + return response( map ).applicationJson(); + } + + @POST + @Path("response/file/path") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Operation(description = "Upload file path endpoint" ) + public ServerResponse responseUploadFilePath(ServerRequest request, @FormParam("file") java.nio.file.Path file ) throws Exception + { + return response(ByteBuffer.wrap(Files.toByteArray(file.toFile()))).applicationOctetStream(); + } + + @POST + @Path("response/file/path/optional") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Operation(description = "Upload optional file path endpoint" ) + public ServerResponse responseUploadOptionalFilePath(ServerRequest request, @FormParam("file") Optional file ) throws Exception + { + if(file.isPresent()) + { + return response(ByteBuffer.wrap(Files.toByteArray(file.get().toFile()))).applicationOctetStream(); + } + else + { + return response().noContent(); + } + } + + @GET + @Path("generic/set") + @Produces((MediaType.APPLICATION_JSON)) + @Operation(description = "Generic set endpoint" ) + public ServerResponse> genericSet( ServerRequest request, @QueryParam("ids") Set ids ) throws Exception + { + return response( ids ).applicationJson(); + } + + + @GET + @Path("types/money") + @Produces((MediaType.APPLICATION_JSON)) + @Operation(description = "Money type endpoint" ) + public ServerResponse getMoney(ServerRequest request ) throws Exception + { + return response( Money.of(100.0,"USD") ).applicationJson(); + } + + @GET + @Path("types/pojo2") + @Produces((MediaType.APPLICATION_JSON)) + @Operation(description = "Money type endpoint" ) + public ServerResponse getPojo2(ServerRequest request ) throws Exception + { + return response( new Pojo(100L,"USD") ).applicationJson(); + } + + @GET + @Path("types/pojo") + @Produces((MediaType.APPLICATION_JSON)) + @Operation(description = "Money type endpoint" ) + public ServerResponse getPojo(ServerRequest request ) throws Exception + { + return response( new Pojo(100L,"USD") ).applicationJson(); + } + + @POST + @Path("generic/set/bean") + @Produces((MediaType.APPLICATION_JSON)) + @Consumes(MediaType.APPLICATION_JSON) + @Operation(description = "Generic bean set endpoint" ) + public ServerResponse> genericBeanSet( ServerRequest request, @BeanParam Set ids ) throws Exception + { + return response( ids ).applicationJson(); + } + + + @POST + @Path("generic/list/bean") + @Produces((MediaType.APPLICATION_JSON)) + @Consumes(MediaType.APPLICATION_JSON) + + @Operation(description = "Generic bean list endpoint" ) + public ServerResponse> genericBeanList( ServerRequest request, @BeanParam List ids ) throws Exception + { + return response( ids ).applicationJson(); + } + + @GET + @Path("optional/set") + @Produces((MediaType.APPLICATION_JSON)) + @Operation(description = "Generic optional set endpoint" ) + public ServerResponse> genericOptionalSet( ServerRequest request, @QueryParam("ids") Optional> ids ) throws Exception + { + return response( ids.get() ).applicationJson(); + } + + + @POST + @Path("response/parse/ids") + @Blocking + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + @Operation(description = "Convert ids") + public ServerResponse> listConversion( ServerRequest request, @BeanParam List ids ) throws Exception + { + + return response( ids ).applicationJson(); + + + } + + @GET + @Path("response/parse/timestamp") + @Blocking + @Produces(MediaType.TEXT_PLAIN) + @Operation(description = "Convert timestamp") + public ServerResponse timestampConversion( ServerRequest request, @QueryParam("timestamp") Timestamp timestamp ) throws Exception + { + return response().body(timestamp.toString()).textPlain(); + } + + @GET + @Path("response/parse/instant") + @Blocking + @Produces(MediaType.TEXT_PLAIN) + @Operation(description = "Convert instant") + public ServerResponse instantConversion( ServerRequest request, @QueryParam("instant") Instant instant ) throws Exception + { + + return response().body(instant.toString()).textPlain(); + + + } + + @POST + @Path("response/bytebuffer") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Consumes("*/*") + @Operation(description = "Upload file path endpoint") + public ServerResponse responseUploadByteBuffer(ServerRequest request, @FormParam("file") ByteBuffer file ) throws Exception + { + + return response(file).applicationOctetStream(); + + + } + + @POST + @Path("response/file") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Consumes("*/*") + @Operation(description = "Upload file path endpoint") + public ServerResponse responseUploadFile(ServerRequest request, @FormParam("file") File file ) throws Exception + { + + ByteBuffer response = ByteBuffer.wrap(Files.asByteSource(file).read()); + + + return response(response).applicationOctetStream(); + + + } + + @GET + @Path("response/debug") + @Operation(description = "Debug endpoint") + public ServerResponse> debugEndpoint(ServerRequest request) + { + try + { + Map map = ImmutableMap.of("message", "Hello, World!"); + + return response( map ).applicationJson(); + } catch(Exception e) + { + return response().badRequest(e); + } + } + + @GET + @Path("response/debug/blocking") + @Blocking + @Operation(description="Debug blocking endpoint") + public ServerResponse> debugBlockingEndpoint(ServerRequest request) + { + try + { + Map map = ImmutableMap.of("message", "Hello, World!"); + + return response( map ).applicationJson(); + } catch(Exception e) + { + return response().badRequest(e); + } + } + + + @GET + @SecurityRequirement(name = "testRequirement") + @Path("secure/resource") + @Operation(description="Secure resource") + @Produces(MediaType.APPLICATION_JSON) + public ServerResponse> responseSecureContext() + { + Map responseMap = new HashMap<>(); + responseMap.put("secure",true); + + return response(responseMap); + } +} diff --git a/openapi/src/test/java/io/sinistral/proteus/openapi/test/models/Pojo.java b/openapi/src/test/java/io/sinistral/proteus/openapi/test/models/Pojo.java new file mode 100644 index 0000000..5d9e8af --- /dev/null +++ b/openapi/src/test/java/io/sinistral/proteus/openapi/test/models/Pojo.java @@ -0,0 +1,40 @@ +package io.sinistral.proteus.openapi.test.models; + +public class Pojo +{ + + public Long id; + + public String name; + + + public Pojo(Long id, String name) + { + this.id = id; + this.name = name; + } + + public Pojo() + { + } + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } +} diff --git a/openapi/src/test/java/io/sinistral/proteus/openapi/test/server/OpenAPIDefaultServer.java b/openapi/src/test/java/io/sinistral/proteus/openapi/test/server/OpenAPIDefaultServer.java new file mode 100644 index 0000000..d22cac3 --- /dev/null +++ b/openapi/src/test/java/io/sinistral/proteus/openapi/test/server/OpenAPIDefaultServer.java @@ -0,0 +1,129 @@ +/** + * + */ +package io.sinistral.proteus.openapi.test.server; + +import io.restassured.RestAssured; +import io.sinistral.proteus.ProteusApplication; +import io.sinistral.proteus.openapi.services.OpenAPIService; +import io.sinistral.proteus.openapi.test.controllers.OpenAPITests; +import io.sinistral.proteus.services.AssetsService; +import org.junit.runner.Description; +import org.junit.runner.Result; +import org.junit.runner.notification.RunListener; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.model.InitializationError; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * @author jbauer + */ +public class OpenAPIDefaultServer extends BlockJUnit4ClassRunner +{ + private static Logger log = LoggerFactory.getLogger(OpenAPIDefaultServer.class.getCanonicalName()); + + private static boolean first = true; + + /** + * @param clazz + * @throws InitializationError + */ + public OpenAPIDefaultServer(Class clazz) throws InitializationError + { + super(clazz); + } + + @Override + public void run(final RunNotifier notifier) + { + notifier.addListener(new RunListener() + { + @Override + public void testStarted(Description description) throws Exception + { + + super.testStarted(description); + } + + @Override + public void testFinished(Description description) throws Exception + { + + super.testFinished(description); + } + }); + + runInternal(notifier); + + super.run(notifier); + } + + private static void runInternal(final RunNotifier notifier) + { + + if (first) + { + + first = false; + + final ProteusApplication app = new ProteusApplication(); + + app.addService(OpenAPIService.class); + app.addService(AssetsService.class); + + app.addController(OpenAPITests.class); + + app.start(); + + int port = 0; + + try + { + Thread.sleep(5000); + + System.out.println(app.getPorts()); + + List ports = app.getPorts(); + + port = ports.get(0); + + } catch (Exception e) + { + e.printStackTrace(); + } + + + + RestAssured.baseURI = String.format("http://localhost:%d/v1",port); + + RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); + + while (!app.isRunning()) + { + try + { + Thread.sleep(100L); + } catch (InterruptedException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + notifier.addListener(new RunListener() + { + @Override + public void testRunFinished(final Result result) throws Exception + { + app.shutdown(); + }; + }); + } + + } + +} diff --git a/openapi/src/test/java/io/sinistral/proteus/openapi/test/server/TestOpenAPIControllerEndpoints.java b/openapi/src/test/java/io/sinistral/proteus/openapi/test/server/TestOpenAPIControllerEndpoints.java new file mode 100644 index 0000000..7f003ce --- /dev/null +++ b/openapi/src/test/java/io/sinistral/proteus/openapi/test/server/TestOpenAPIControllerEndpoints.java @@ -0,0 +1,107 @@ +/** + * + */ +package io.sinistral.proteus.openapi.test.server; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.nio.file.Files; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.stream.LongStream; + +import static io.restassured.RestAssured.when; + +/* + * import static io.restassured.RestAssured.*; import static io.restassured.matcher.RestAssuredMatchers.*; import static org.hamcrest.Matchers.*; + */ + +/** + * @author jbauer + */ +@RunWith(OpenAPIDefaultServer.class) +public class TestOpenAPIControllerEndpoints +{ + + private File file = null; + + private Set idSet = new HashSet<>(); + + @Before + public void setUp() + { + try + { + byte[] bytes = new byte[8388608]; + Random random = new Random(); + random.nextBytes(bytes); + + file = Files.createTempFile("test-asset", ".mp4").toFile(); + + LongStream.range(1L,10L).forEach( l -> { + + idSet.add(l); + }); + + + } catch (Exception e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + + + @Test + public void testOpenAPIDocs() + { + when().get("openapi.yaml").then().log().all().statusCode(200); + } + + public void testPojoType() + { + when().get("tests/types/pojo").then().log().all().statusCode(200); + } + + @Test public void testPojoType2() + { + when().get("tests/types/pojo2").then().log().all().statusCode(200); + } + + public void testMoneyType() + { + when().get("tests/types/money").then().log().all().statusCode(200); + } + + + @Test + public void testSecurityRequirementEndpoint() + { + when().get("tests/secure/resource").then().statusCode(200); + } + + + @After + public void tearDown() + { + try + { + if(file.exists()) + { + file.delete(); + } + + } catch (Exception e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + +} diff --git a/openapi/src/test/java/io/sinistral/proteus/swagger/test/controllers/Tests.java b/openapi/src/test/java/io/sinistral/proteus/test/controllers/Tests.java similarity index 95% rename from openapi/src/test/java/io/sinistral/proteus/swagger/test/controllers/Tests.java rename to openapi/src/test/java/io/sinistral/proteus/test/controllers/Tests.java index 71bf8dd..6616627 100644 --- a/openapi/src/test/java/io/sinistral/proteus/swagger/test/controllers/Tests.java +++ b/openapi/src/test/java/io/sinistral/proteus/test/controllers/Tests.java @@ -1,7 +1,7 @@ /** * */ -package io.sinistral.proteus.swagger.test.controllers; +package io.sinistral.proteus.test.controllers; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableMap; @@ -16,6 +16,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tags; import io.undertow.server.HttpServerExchange; +import org.javamoney.moneta.Money; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; @@ -127,8 +128,18 @@ public ServerResponse> genericSet( ServerRequest request, @QueryParam { return response( ids ).applicationJson(); } - - + + + @GET + @Path("types/money") + @Produces((MediaType.APPLICATION_JSON)) + @Operation(description = "Money type endpoint" ) + public ServerResponse getMoney(ServerRequest request ) throws Exception + { + return response( Money.of(100.0,"USD") ).applicationJson(); + } + + @POST @Path("generic/set/bean") @Produces((MediaType.APPLICATION_JSON)) diff --git a/openapi/src/test/java/io/sinistral/proteus/swagger/test/server/DefaultServer.java b/openapi/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java similarity index 95% rename from openapi/src/test/java/io/sinistral/proteus/swagger/test/server/DefaultServer.java rename to openapi/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java index 5d1ea93..fa501cf 100644 --- a/openapi/src/test/java/io/sinistral/proteus/swagger/test/server/DefaultServer.java +++ b/openapi/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java @@ -1,13 +1,13 @@ /** * */ -package io.sinistral.proteus.swagger.test.server; +package io.sinistral.proteus.test.server; import io.restassured.RestAssured; import io.sinistral.proteus.ProteusApplication; import io.sinistral.proteus.openapi.services.OpenAPIService; import io.sinistral.proteus.services.AssetsService; -import io.sinistral.proteus.swagger.test.controllers.Tests; +import io.sinistral.proteus.test.controllers.Tests; import org.junit.runner.Description; import org.junit.runner.Result; import org.junit.runner.notification.RunListener; diff --git a/openapi/src/test/java/io/sinistral/proteus/swagger/test/server/TestControllerEndpoints.java b/openapi/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java similarity index 91% rename from openapi/src/test/java/io/sinistral/proteus/swagger/test/server/TestControllerEndpoints.java rename to openapi/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java index c91cb8d..2f03a19 100644 --- a/openapi/src/test/java/io/sinistral/proteus/swagger/test/server/TestControllerEndpoints.java +++ b/openapi/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java @@ -1,7 +1,7 @@ /** * */ -package io.sinistral.proteus.swagger.test.server; +package io.sinistral.proteus.test.server; import static io.restassured.RestAssured.given; import static io.restassured.RestAssured.when; @@ -66,6 +66,12 @@ public void testOpenAPIDocs() when().get("openapi.yaml").then().statusCode(200); } + @Test + public void testMoneyType() + { + when().get("tests/types/money").then().statusCode(200); + } + @Test public void testSecurityRequirementEndpoint() diff --git a/openapi/src/test/resources/application.conf b/openapi/src/test/resources/application.conf new file mode 100644 index 0000000..ddd47d4 --- /dev/null +++ b/openapi/src/test/resources/application.conf @@ -0,0 +1,93 @@ + +application { + + env = dev + + version = "1.0" + + name="proteus" + + path = "/v1" + + host = "localhost" + + ports { + http = 0 + # https = 8443 + } + + charset = UTF-8 + + fallbackHandler = "io.sinistral.proteus.server.handlers.ServerFallbackHandler" + + defaultResponseListener = "io.sinistral.proteus.server.handlers.ServerDefaultResponseListener" + + jacksonModule = "io.sinistral.proteus.modules.JacksonModule" + + tmpdir = ${java.io.tmpdir}/${application.name} + + # path to default favicon file + favicon = "/io/sinistral/proteus/favicon.ico" + +} + +api.version="v1" + +globalHeaders +{ +# Access-Control-Allow-Origin: "*" +# Access-Control-Allow-Methods: "*" +# Access-Control-Allow-Headers: "*" + Server = ${application.name} +} + +assets { + # the base path assets will be server from + path = "/public" + # the directory to load the assets from + dir = "./assets" + cache { + # cache timeout for the assets + time = 500 + } + + +} + + + + + +undertow +{ + server { + enableHttp2 = false + alwaysSetDate = true + alwaysSetKeepAlive = false + recordRequestStartTime = false + maxEntitySize = 100M + bufferPipelinedData = false + } + + socket { + backlog = 10000 + } + + + ssl { + enabled=false + keystorePath="development.jks" + truststorePath="development.ts" + keystorePassword="password" + truststorePassword="password" + } + + enableHttp2=false + # x AvailableProcessors + ioThreadsMultiplier = 2 + workerThreadMultiplier = 8 + bufferSize = 16K + directBuffers = true +} + + \ No newline at end of file diff --git a/openapi/src/test/resources/development.jks b/openapi/src/test/resources/development.jks new file mode 100644 index 0000000000000000000000000000000000000000..96d0454c8fe518f8eaad45b4578608019d43f207 GIT binary patch literal 3916 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 - + - - - + + + diff --git a/swagger/src/test/java/io/sinistral/proteus/swagger/test/controllers/Tests.java b/swagger/src/test/java/io/sinistral/proteus/swagger/test/controllers/Tests.java index c83bfff..fd4c29c 100644 --- a/swagger/src/test/java/io/sinistral/proteus/swagger/test/controllers/Tests.java +++ b/swagger/src/test/java/io/sinistral/proteus/swagger/test/controllers/Tests.java @@ -15,13 +15,23 @@ import io.swagger.annotations.ApiOperation; import io.undertow.server.HttpServerExchange; -import javax.ws.rs.*; +import javax.ws.rs.BeanParam; +import javax.ws.rs.Consumes; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import java.io.File; import java.nio.ByteBuffer; import java.sql.Timestamp; import java.time.Instant; -import java.util.*; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.CompletableFuture; import static io.sinistral.proteus.server.ServerResponse.response; diff --git a/swagger/src/test/java/io/sinistral/proteus/swagger/test/server/TestControllerEndpoints.java b/swagger/src/test/java/io/sinistral/proteus/swagger/test/server/TestControllerEndpoints.java index 131200b..178fdc2 100644 --- a/swagger/src/test/java/io/sinistral/proteus/swagger/test/server/TestControllerEndpoints.java +++ b/swagger/src/test/java/io/sinistral/proteus/swagger/test/server/TestControllerEndpoints.java @@ -3,31 +3,26 @@ */ package io.sinistral.proteus.swagger.test.server; -import static io.restassured.RestAssured.given; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; +import io.restassured.http.ContentType; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import java.io.File; -import java.io.InputStream; import java.nio.file.Files; import java.util.HashSet; import java.util.Random; import java.util.Set; import java.util.stream.LongStream; -import ch.qos.logback.classic.LoggerContext; -import ch.qos.logback.classic.joran.JoranConfigurator; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import io.restassured.http.ContentType; -import org.slf4j.LoggerFactory; +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.is; /* * import static io.restassured.RestAssured.*; import static io.restassured.matcher.RestAssuredMatchers.*; import static org.hamcrest.Matchers.*; */ + /** * @author jbauer */ diff --git a/swagger/src/test/java/io/sinistral/proteus/test/controllers/Tests.java b/swagger/src/test/java/io/sinistral/proteus/test/controllers/Tests.java new file mode 100644 index 0000000..dc2b9aa --- /dev/null +++ b/swagger/src/test/java/io/sinistral/proteus/test/controllers/Tests.java @@ -0,0 +1,261 @@ +/** + * + */ +package io.sinistral.proteus.test.controllers; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.Files; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import io.sinistral.proteus.annotations.Blocking; +import io.sinistral.proteus.server.ServerRequest; +import io.sinistral.proteus.server.ServerResponse; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.undertow.server.HttpServerExchange; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import java.io.File; +import java.nio.ByteBuffer; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +import static io.sinistral.proteus.server.ServerResponse.response; + +/** + * @author jbauer + * + */ + +@Api(tags="tests") +@Path("/tests") +@Produces((MediaType.APPLICATION_JSON)) +@Consumes((MediaType.MEDIA_TYPE_WILDCARD)) +@Singleton +public class Tests +{ + private static final ByteBuffer buffer; + static { + String message = "Hello, World!"; + byte[] messageBytes = message.getBytes(java.nio.charset.StandardCharsets.US_ASCII); + buffer = ByteBuffer.allocateDirect(messageBytes.length); + buffer.put(messageBytes); + buffer.flip(); + } + + @Inject + protected ObjectMapper objectMapper; + + + @GET + @Path("exchange/plaintext") + @Produces((MediaType.TEXT_PLAIN)) + @ApiOperation(value = "Plaintext endpoint" ) + public void exchangePlaintext(HttpServerExchange exchange) + { + response("Hello, World!").textPlain().send(exchange); + + } + + + @GET + @Path("response/plaintext") + @Produces((MediaType.TEXT_PLAIN)) + @ApiOperation(value = "Plaintext endpoint" ) + public ServerResponse responsePlaintext(ServerRequest request) + { + return response("Hello, World!").textPlain(); + + } + + @GET + @Path("response/future/map") + @ApiOperation(value = "Future map endpoint" ) + public CompletableFuture>> responseFutureMap( ServerRequest request ) + { + Map map = ImmutableMap.of("message", "success"); + return CompletableFuture.completedFuture(response( map ).applicationJson()); + } + + @GET + @Path("response/map") + @ApiOperation(value = "Map endpoint" ) + public ServerResponse> futureMap( ServerRequest request ) + { + Map map = ImmutableMap.of("message", "success"); + return response( map ).applicationJson(); + } + + @POST + @Path("response/file/path") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @ApiOperation(value = "Upload file path endpoint" ) + public ServerResponse responseUploadFilePath(ServerRequest request, @FormParam("file") java.nio.file.Path file ) throws Exception + { + return response(ByteBuffer.wrap(Files.toByteArray(file.toFile()))).applicationOctetStream(); + } + + @POST + @Path("response/file/path/optional") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @ApiOperation(value = "Upload optional file path endpoint" ) + public ServerResponse responseUploadOptionalFilePath(ServerRequest request, @FormParam("file") Optional file ) throws Exception + { + if(file.isPresent()) + { + return response(ByteBuffer.wrap(Files.toByteArray(file.get().toFile()))).applicationOctetStream(); + } + else + { + return response().noContent(); + } + } + + @GET + @Path("generic/set") + @Produces((MediaType.APPLICATION_JSON)) + @ApiOperation(value = "Generic set endpoint" ) + public ServerResponse> genericSet( ServerRequest request, @QueryParam("ids") Set ids ) throws Exception + { + return response( ids ).applicationJson(); + } + + + @POST + @Path("generic/set/bean") + @Produces((MediaType.APPLICATION_JSON)) + @Consumes(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Generic bean set endpoint" ) + public ServerResponse> genericBeanSet( ServerRequest request, @BeanParam Set ids ) throws Exception + { + return response( ids ).applicationJson(); + } + + + @POST + @Path("generic/list/bean") + @Produces((MediaType.APPLICATION_JSON)) + @Consumes(MediaType.APPLICATION_JSON) + + @ApiOperation(value = "Generic bean list endpoint" ) + public ServerResponse> genericBeanList( ServerRequest request, @BeanParam List ids ) throws Exception + { + return response( ids ).applicationJson(); + } + + @GET + @Path("optional/set") + @Produces((MediaType.APPLICATION_JSON)) + @ApiOperation(value = "Generic optional set endpoint" ) + public ServerResponse> genericOptionalSet( ServerRequest request, @QueryParam("ids") Optional> ids ) throws Exception + { + return response( ids.get() ).applicationJson(); + } + + + @POST + @Path("response/parse/ids") + @Blocking + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Convert ids") + public ServerResponse> listConversion( ServerRequest request, @BeanParam List ids ) throws Exception + { + + return response( ids ).applicationJson(); + + + } + + @GET + @Path("response/parse/timestamp") + @Blocking + @Produces(MediaType.TEXT_PLAIN) + @ApiOperation(value = "Convert timestamp") + public ServerResponse timestampConversion( ServerRequest request, @QueryParam("timestamp") Timestamp timestamp ) throws Exception + { + return response().body(timestamp.toString()).textPlain(); + } + + @GET + @Path("response/parse/instant") + @Blocking + @Produces(MediaType.TEXT_PLAIN) + @ApiOperation(value = "Convert instant") + public ServerResponse instantConversion( ServerRequest request, @QueryParam("instant") Instant instant ) throws Exception + { + + return response().body(instant.toString()).textPlain(); + + + } + + @POST + @Path("response/bytebuffer") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Consumes("*/*") + @ApiOperation(value = "Upload file path endpoint") + public ServerResponse responseUploadByteBuffer(ServerRequest request, @FormParam("file") ByteBuffer file ) throws Exception + { + + return response(file).applicationOctetStream(); + + + } + + @POST + @Path("response/file") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Consumes("*/*") + @ApiOperation(value = "Upload file path endpoint") + public ServerResponse responseUploadFile(ServerRequest request, @FormParam("file") File file ) throws Exception + { + + ByteBuffer response = ByteBuffer.wrap(Files.asByteSource(file).read()); + + + return response(response).applicationOctetStream(); + + + } + + @GET + @Path("response/debug") + @ApiOperation(value = "Debug endpoint") + public ServerResponse> debugEndpoint(ServerRequest request) + { + try + { + Map map = ImmutableMap.of("message", "Hello, World!"); + + return response( map ).applicationJson(); + } catch(Exception e) + { + return response().badRequest(e); + } + } + + @GET + @Path("response/debug/blocking") + @Blocking + @ApiOperation(value="Debug blocking endpoint") + public ServerResponse> debugBlockingEndpoint(ServerRequest request) + { + try + { + Map map = ImmutableMap.of("message", "Hello, World!"); + + return response( map ).applicationJson(); + } catch(Exception e) + { + return response().badRequest(e); + } + } + +} diff --git a/swagger/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java b/swagger/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java new file mode 100644 index 0000000..af414eb --- /dev/null +++ b/swagger/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java @@ -0,0 +1,129 @@ +/** + * + */ +package io.sinistral.proteus.test.server; + +import io.restassured.RestAssured; +import io.sinistral.proteus.ProteusApplication; +import io.sinistral.proteus.services.AssetsService; +import io.sinistral.proteus.swagger.services.SwaggerService; +import io.sinistral.proteus.test.controllers.Tests; +import org.junit.runner.Description; +import org.junit.runner.Result; +import org.junit.runner.notification.RunListener; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.model.InitializationError; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * @author jbauer + */ +public class DefaultServer extends BlockJUnit4ClassRunner +{ + private static Logger log = LoggerFactory.getLogger(DefaultServer.class.getCanonicalName()); + + private static boolean first = true; + + /** + * @param clazz + * @throws InitializationError + */ + public DefaultServer(Class clazz) throws InitializationError + { + super(clazz); + } + + @Override + public void run(final RunNotifier notifier) + { + notifier.addListener(new RunListener() + { + @Override + public void testStarted(Description description) throws Exception + { + + super.testStarted(description); + } + + @Override + public void testFinished(Description description) throws Exception + { + + super.testFinished(description); + } + }); + + runInternal(notifier); + + super.run(notifier); + } + + private static void runInternal(final RunNotifier notifier) + { + + if (first) + { + + first = false; + + final ProteusApplication app = new ProteusApplication(DefaultServer.class.getClassLoader().getResource("application.conf")); + + app.addService(SwaggerService.class); + app.addService(AssetsService.class); + + app.addController(Tests.class); + + app.start(); + + int port = 0; + + try + { + Thread.sleep(5000); + + System.out.println(app.getPorts()); + + List ports = app.getPorts(); + + port = ports.get(0); + + } catch (Exception e) + { + e.printStackTrace(); + } + + + + RestAssured.baseURI = String.format("http://localhost:%d/v1",port); + + RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); + + while (!app.isRunning()) + { + try + { + Thread.sleep(100L); + } catch (InterruptedException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + notifier.addListener(new RunListener() + { + @Override + public void testRunFinished(final Result result) throws Exception + { + app.shutdown(); + }; + }); + } + + } + +} diff --git a/swagger/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java b/swagger/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java new file mode 100644 index 0000000..4a4864f --- /dev/null +++ b/swagger/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java @@ -0,0 +1,88 @@ +/** + * + */ +package io.sinistral.proteus.test.server; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.io.File; +import java.nio.file.Files; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.stream.LongStream; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import io.restassured.http.ContentType; + +/* + * import static io.restassured.RestAssured.*; import static io.restassured.matcher.RestAssuredMatchers.*; import static org.hamcrest.Matchers.*; + */ +/** + * @author jbauer + */ +@RunWith(DefaultServer.class) +public class TestControllerEndpoints +{ + + private File file = null; + + private Set idSet = new HashSet<>(); + + + @Before + public void setUp() + { + try + { + byte[] bytes = new byte[8388608]; + Random random = new Random(); + random.nextBytes(bytes); + + file = Files.createTempFile("test-asset", ".mp4").toFile(); + + LongStream.range(1L,10L).forEach( l -> { + + idSet.add(l); + }); + + + } catch (Exception e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + @Test + public void testSwaggerDocs() + { + given().accept(ContentType.JSON).when().get("swagger.json").then().statusCode(200).and().body("basePath", is("/v1")); + } + + + + @After + public void tearDown() + { + try + { + if(file.exists()) + { + file.delete(); + } + + } catch (Exception e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + +}