From f5435d1531b19d9eaca096a36c2ff1d62ace3641 Mon Sep 17 00:00:00 2001 From: Unknow Date: Mon, 8 Sep 2025 23:22:05 +0200 Subject: [PATCH] update openapi.json generation --- pom.xml | 4 +- unknow-server-maven/pom.xml | 2 +- .../server/maven/jaxrs/OpenApiBuilder.java | 74 ++++++++++++++++--- .../java/unknow/server/http/test/Rest.java | 21 ++++-- 4 files changed, 78 insertions(+), 23 deletions(-) diff --git a/pom.xml b/pom.xml index 11d2d4aa..41dda689 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ 3.26.1 5.10.3 5.12.0 - 2.2.22 + 2.2.36 4.0.3 2.17.1 0.0.3 @@ -349,7 +349,7 @@ io.swagger.core.v3 - swagger-annotations + swagger-annotations-jakarta ${swagger.version} diff --git a/unknow-server-maven/pom.xml b/unknow-server-maven/pom.xml index 20f6f9f9..82443e93 100644 --- a/unknow-server-maven/pom.xml +++ b/unknow-server-maven/pom.xml @@ -129,7 +129,7 @@ io.swagger.core.v3 - swagger-annotations + swagger-annotations-jakarta io.swagger.core.v3 diff --git a/unknow-server-maven/src/main/java/unknow/server/maven/jaxrs/OpenApiBuilder.java b/unknow-server-maven/src/main/java/unknow/server/maven/jaxrs/OpenApiBuilder.java index a456c581..b0f70603 100644 --- a/unknow-server-maven/src/main/java/unknow/server/maven/jaxrs/OpenApiBuilder.java +++ b/unknow-server-maven/src/main/java/unknow/server/maven/jaxrs/OpenApiBuilder.java @@ -15,6 +15,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import org.apache.maven.plugin.MojoExecutionException; @@ -22,8 +24,13 @@ import org.slf4j.LoggerFactory; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationConfig; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; +import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; import io.swagger.v3.oas.annotations.tags.Tags; import io.swagger.v3.oas.models.OpenAPI; @@ -41,6 +48,7 @@ import unknow.server.maven.jaxrs.JaxrsParam.JaxrsBeanParam.JaxrsBeanFieldParam; import unknow.server.maven.jaxrs.JaxrsParam.JaxrsBodyParam; import unknow.server.maven.jaxrs.JaxrsParam.JaxrsCookieParam; +import unknow.server.maven.jaxrs.JaxrsParam.JaxrsFormParam; import unknow.server.maven.jaxrs.JaxrsParam.JaxrsHeaderParam; import unknow.server.maven.jaxrs.JaxrsParam.JaxrsMatrixParam; import unknow.server.maven.jaxrs.JaxrsParam.JaxrsPathParam; @@ -134,9 +142,10 @@ private static Schema getDefault(String n) { private final Map schemas = new HashMap<>(); public void build(OpenAPI spec, JaxrsModel model, String file) throws MojoExecutionException { + SimpleModule module = new SimpleModule().setSerializerModifier(new IgnoreOpenapiField()); ObjectMapper m = new ObjectMapper().enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING).disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) - .setDefaultPropertyInclusion(Include.NON_EMPTY); + .setDefaultPropertyInclusion(Include.NON_EMPTY).registerModule(module); Path path = java.nio.file.Paths.get(file); try { Files.createDirectories(path.getParent()); @@ -172,9 +181,6 @@ private OpenAPI build(OpenAPI spec, JaxrsModel model) { o.setTags(tags); } - ApiResponses responses = new ApiResponses(); - o.setResponses(responses); - if (a != null) { a.member("tags").filter(v -> v.isSet()).map(v -> v.asArrayLiteral()).filter(v -> v.length > 0).ifPresent(v -> o.setTags(Arrays.asList(v))); a.member("summary").filter(v -> v.isSet()).map(v -> v.asLiteral()).filter(v -> !v.isEmpty()).ifPresent(v -> o.setSummary(v)); @@ -187,12 +193,13 @@ private OpenAPI build(OpenAPI spec, JaxrsModel model) { // TODO externalDocs, responses, security, servers, extensions } - if (responses.isEmpty() && !m.m.type().isVoid()) { + if (!m.m.type().isVoid()) { Content c = new Content(); for (String s : m.produce) c.addMediaType(s, new MediaType().schema(schema(m.m.type()))); - responses.addApiResponse("default", new ApiResponse().content(c)); - } + o.setResponses(new ApiResponses().addApiResponse("default", new ApiResponse().description("default").content(c))); + } else + o.setResponses(new ApiResponses().addApiResponse("default", new ApiResponse().description("empty"))); for (JaxrsParam param : m.params) addParameters(o, param); @@ -215,16 +222,24 @@ private void addParameters(Operation o, JaxrsParam param) { } if (param instanceof JaxrsBodyParam) { - MediaType mdi = new MediaType(); - mdi.examples(null); - mdi.schema(schema(param.type)); + MediaType mdi = new MediaType().schema(schema(param.type)); Content content = new Content(); content.addMediaType("*/*", mdi); o.setRequestBody(new RequestBody().content(content)); return; } - Parameter p = new Parameter().name(param.value).schema(schema(param.type)); + Optional annotation = param.p.annotation(io.swagger.v3.oas.annotations.media.Schema.class); + if (param instanceof JaxrsFormParam) { + RequestBody b = o.getRequestBody(); + if (b == null) + o.setRequestBody(b = new RequestBody().content(new Content())); + MediaType mdi = b.getContent().computeIfAbsent("application/x-www-form-urlencoded", k -> new MediaType().schema(new Schema<>().type("object"))); + mdi.getSchema().addProperty(param.value, fillFrom(schema(param.type), annotation)); + return; + } + + Parameter p = new Parameter().name(param.value).schema(fillFrom(schema(param.type), annotation)); if (param instanceof JaxrsHeaderParam) p.setIn("header"); else if (param instanceof JaxrsQueryParam) @@ -303,9 +318,16 @@ private Schema schema(TypeModel type) { List requied = new ArrayList<>(); s.type("object").setRequired(requied); ClassModel c = type.asClass(); + fillFrom(s, c.annotation(io.swagger.v3.oas.annotations.media.Schema.class)); for (FieldModel f : c.fields()) { - s.addProperty(f.name(), schema(f.type())); + if (f.isStatic() || f.isTransient()) + continue; + Optional a = f.annotation(io.swagger.v3.oas.annotations.media.Schema.class); + if (a.flatMap(o -> o.member("hidden")).map(o -> o.asBoolean()).orElse(false)) + continue; + + s.addProperty(f.name(), fillFrom(schema(f.type()), a)); // TODO add required value from jackson } // TODO exemple @@ -313,6 +335,16 @@ private Schema schema(TypeModel type) { return new Schema<>().$ref("#/components/schemas/" + n); } + private Schema fillFrom(Schema s, Optional annotation) { + if (annotation.isEmpty()) + return s; + AnnotationModel a = annotation.get(); + a.member("name").filter(v -> v.isSet()).ifPresent(v -> s.setName(v.asLiteral())); + a.member("title").filter(v -> v.isSet()).ifPresent(v -> s.setTitle(v.asLiteral())); + a.member("description").filter(v -> v.isSet()).ifPresent(v -> s.setDescription(v.asLiteral())); + return s; + } + /** * @param p * @param httpMethod @@ -394,4 +426,22 @@ public static AnnotationModel[] findTags(ClassModel m) { public Map getSchemas() { return schemas; } + + private static final class IgnoreOpenapiField extends BeanSerializerModifier { + private static final long serialVersionUID = 1L; + private static final Set fieldsToIgnore = Set.of("exampleSetFlag"); + + @Override + public List changeProperties(SerializationConfig config, BeanDescription beanDesc, List beanProperties) { + + // On filtre les propriétés à ignorer + List newProps = new ArrayList<>(); + for (BeanPropertyWriter writer : beanProperties) { + if (!fieldsToIgnore.contains(writer.getName())) { + newProps.add(writer); + } + } + return newProps; + } + } } \ No newline at end of file diff --git a/unknow-server-test/unknow-server-test-pojo/src/main/java/unknow/server/http/test/Rest.java b/unknow-server-test/unknow-server-test-pojo/src/main/java/unknow/server/http/test/Rest.java index 0d4be212..5f1f8740 100644 --- a/unknow-server-test/unknow-server-test-pojo/src/main/java/unknow/server/http/test/Rest.java +++ b/unknow-server-test/unknow-server-test-pojo/src/main/java/unknow/server/http/test/Rest.java @@ -18,6 +18,7 @@ import jakarta.ws.rs.HeaderParam; import jakarta.ws.rs.MatrixParam; import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; @@ -34,32 +35,35 @@ public class Rest { @GET @Path("t") - public void t() throws InterruptedException { + @SuppressWarnings("unused") + public void t(@PathParam("q") String q) throws InterruptedException { Thread.sleep(3000); throw new NullPointerException("test"); } @GET @Path("q/{v}") - public void q() { // ok + @SuppressWarnings("unused") + public void q(@PathParam("q") String q, @PathParam("v") String v) { // ok } - @GET + @POST public void oneWay(@PathParam("q") String q, @BeanParam Bean bean) { logger.info("oneWay>> q: '{}' bean: {}", q, bean); } - @GET + @PUT @Consumes({ "application/json", "application/x-ndjson" }) - public Response response(@PathParam("q") String q, @BeanParam Bean bean) { - logger.info("response>> q: '{}' bean: {}", q, bean); + public Response response(@PathParam("q") String q, @FormParam("k") String k) { + logger.info("response>> q: '{}' bean: {}", q, k); return Response.status(200).entity("echo").build(); } @POST @Consumes({ "application/x-protobuf", "application/json", "application/jsonl", "application/x-ndjson" }) @Produces({ "application/x-protobuf", "application/json", "application/jsonl", "application/x-ndjson" }) - public Truc call(Truc truc) { + @SuppressWarnings("unused") + public Truc call(@PathParam("q") String q, Truc truc) { return truc; } @@ -67,7 +71,8 @@ public Truc call(Truc truc) { @Path("list") @Consumes({ "application/x-protobuf", "application/json", "application/jsonl", "application/x-ndjson" }) @Produces({ "application/x-protobuf", "application/json", "application/jsonl", "application/x-ndjson" }) - public Collection list(Collection truc) { + @SuppressWarnings("unused") + public Collection list(@PathParam("q") String q, Collection truc) { return truc; }