Skip to content

Commit 8ebc0d8

Browse files
committed
Add documentation, tests, and clean up resolver
Signed-off-by: Ricardo Zanini <[email protected]>
1 parent bca94f4 commit 8ebc0d8

File tree

6 files changed

+108
-51
lines changed

6 files changed

+108
-51
lines changed

README.md

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -277,21 +277,29 @@ org.acme.openapi.simple.api.DefaultApi/byeGet/CircuitBreaker/successThreshold=22
277277
````
278278

279279
## Sending multipart/form-data
280-
The rest client also supports request with mime-type multipart/form-data and, if the schema of the request body is known in advance, we can also automatically generate the models of the request bodies.
280+
281+
The rest client also supports request with mime-type multipart/form-data and, if the schema of the request body is known in advance, we can also automatically generate the models of the request
282+
bodies.
281283

282284
You need to add the following additional dependency to your `pom.xml`:
285+
283286
```xml
287+
284288
<dependency>
285289
<groupId>io.quarkus</groupId>
286290
<artifactId>quarkus-resteasy-multipart</artifactId>
287291
</dependency>
288292
```
293+
289294
For any multipart/form-data operation a model for the request body will be generated. Each part of the multipart is a field in this model that is annotated with the following annotations:
290-
- `javax.ws.rs.FormParam`, where the value parameter denotes the part name,
295+
296+
- `javax.ws.rs.FormParam`, where the value parameter denotes the part name,
291297
- `org.jboss.resteasy.annotations.providers.multipart.PartType`, where the parameter is the jax-rs MediaType of the part (see below for details),
292-
- and, if the part contains a file, `org.jboss.resteasy.annotations.providers.multipart.PartFilename`, with a generated default parameter that will be passed as the fileName sub-header in the Content-Disposition header of the part.
298+
- and, if the part contains a file, `org.jboss.resteasy.annotations.providers.multipart.PartFilename`, with a generated default parameter that will be passed as the fileName sub-header in the
299+
Content-Disposition header of the part.
300+
301+
For example, the model for a request that requires a file, a string and some complex object will look like this:
293302

294-
For example, the model for a request that requires a file, a string and some complex object will look like this:
295303
```java
296304
public class MultipartBody {
297305

@@ -311,33 +319,41 @@ public class MultipartBody {
311319
```
312320

313321
Then in the client the `org.jboss.resteasy.annotations.providers.multipart.MultipartForm` annotation is added in front of the multipart parameter:
322+
314323
```java
315324
@Path("/echo")
316325
@RegisterRestClient
317326
public interface MultipartService {
318-
327+
319328
@POST
320329
@Consumes(MediaType.MULTIPART_FORM_DATA)
321330
@Produces(MediaType.TEXT_PLAIN)
322331
String sendMultipartData(@MultipartForm MultipartBody data);
323-
332+
324333
}
325334
```
326-
See [Quarkus - Using the REST Client with Multipart](https://quarkus.io/guides/rest-client-multipart) and the [RESTEasy JAX-RS specifications](https://docs.jboss.org/resteasy/docs/4.7.5.Final/userguide/html_single/index.html) for more details.
327335

328-
Importantly, if some multipart request bodies contain complex objects (i.e. non-primitives) you need to explicitly tell the Open API generator to create models for these objects by setting the `skip-form-model` property corresponding to your spec in the `application.properties` to `false`, e.g.:
336+
See [Quarkus - Using the REST Client with Multipart](https://quarkus.io/guides/rest-client-multipart) and
337+
the [RESTEasy JAX-RS specifications](https://docs.jboss.org/resteasy/docs/4.7.5.Final/userguide/html_single/index.html) for more details.
338+
339+
Importantly, if some multipart request bodies contain complex objects (i.e. non-primitives) you need to explicitly tell the Open API generator to create models for these objects by setting
340+
the `skip-form-model` property corresponding to your spec in the `application.properties` to `false`, e.g.:
341+
329342
```properties
330343
quarkus.openapi-generator.codegen.spec."my-multipart-requests.yml".skip-form-model=false
331344
```
332345

333346
### Default content-types according to OpenAPI Specification and limitations
347+
334348
The [OAS 3.0](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#special-considerations-for-multipart-content) specifies the following default content-types for a multipart:
349+
335350
- If the property is a primitive, or an array of primitive values, the default Content-Type is `text/plain`
336351
- If the property is complex, or an array of complex values, the default Content-Type is `application/json`
337352
- If the property is a `type: string` with `format: binary` or `format: base64` (aka a file object), the default Content-Type is `application/octet-stream`
338353

339-
A different content-type may be defined in your api spec, but this is not yet supported in the code generation. Also, this "annotation-oriented" approach of RestEasy (i.e. using `@MultipartForm` to denote the multipart body parameter) does not seem to properly support the unmarshalling of arrays of the same type (e.g. array of files), in these cases it uses Content-Type equal to `application/json`.
340-
354+
A different content-type may be defined in your api spec, but this is not yet supported in the code generation. Also, this "annotation-oriented" approach of RestEasy (i.e. using `@MultipartForm` to
355+
denote the multipart body parameter) does not seem to properly support the unmarshalling of arrays of the same type (e.g. array of files), in these cases it uses Content-Type equal
356+
to `application/json`.
341357

342358
## Generating files via InputStream
343359

@@ -347,6 +363,13 @@ saved locally in your project.
347363

348364
See the example implementation [here](test-utils/src/main/java/io/quarkiverse/openapi/generator/testutils/codegen/ClassPathPetstoreOpenApiSpecInputProvider.java)
349365

366+
## Skip Deprecated Attributes in Model classes
367+
368+
The domain objects are classes generated in the `model` package. These classes might have [deprecated attributes](https://spec.openapis.org/oas/v3.1.0#fixed-fields-9) in the Open API specification
369+
file. By default, these attributes are generated. You can fine tune this behavior if the deprecated attributes should not be generated.
370+
371+
Use the property key `<base_package>.model.MyClass.generateDeprecated=false` to disable the deprecated attributes in the given model. For example `org.acme.weather.Country.generatedDeprecated=false`.
372+
350373
## Known Limitations
351374

352375
These are the known limitations of this pre-release version:

deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/template/ModelConfigValueResolver.java

Lines changed: 0 additions & 37 deletions
This file was deleted.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package io.quarkiverse.openapi.generator.deployment.template;
2+
3+
import java.util.HashMap;
4+
import java.util.concurrent.CompletableFuture;
5+
import java.util.concurrent.CompletionStage;
6+
import java.util.concurrent.ExecutionException;
7+
8+
import io.quarkus.qute.EvalContext;
9+
import io.quarkus.qute.Expression;
10+
import io.quarkus.qute.NamespaceResolver;
11+
12+
/**
13+
* Collection of OpenAPI specific methods to use in the templates.
14+
* It can be enhanced to have even more methods. See the usage of the method #genDeprecatedModelAttr to understand how to
15+
* implement and use them.
16+
*/
17+
public class OpenApiNamespaceResolver implements NamespaceResolver {
18+
private static final String GENERATE_DEPRECATED_PROP = "generateDeprecated";
19+
20+
static final OpenApiNamespaceResolver INSTANCE = new OpenApiNamespaceResolver();
21+
22+
private OpenApiNamespaceResolver() {
23+
}
24+
25+
/**
26+
* @param pkg name of the given package
27+
* @param classname name of the Model class
28+
* @param codegenConfig Map with the model codegen properties
29+
* @return true if the given model class should generate the deprecated attributes
30+
*/
31+
public boolean genDeprecatedModelAttr(final String pkg, final String classname,
32+
final HashMap<String, Object> codegenConfig) {
33+
final String key = String.format("%s.%s.%s", pkg, classname, GENERATE_DEPRECATED_PROP);
34+
return Boolean.parseBoolean(codegenConfig.getOrDefault(key, "true").toString());
35+
}
36+
37+
@Override
38+
public CompletionStage<Object> resolve(EvalContext context) {
39+
try {
40+
Class<?>[] classArgs = new Class[context.getParams().size()];
41+
Object[] args = new Object[context.getParams().size()];
42+
int i = 0;
43+
for (Expression expr : context.getParams()) {
44+
args[i] = context.evaluate(expr).toCompletableFuture().get();
45+
classArgs[i] = args[i].getClass();
46+
i++;
47+
}
48+
return CompletableFuture
49+
.completedFuture(this.getClass().getMethod(context.getName(), classArgs).invoke(this, args));
50+
} catch (ReflectiveOperationException | InterruptedException | ExecutionException ex) {
51+
return CompletableFuture.failedStage(ex);
52+
}
53+
}
54+
55+
@Override
56+
public String getNamespace() {
57+
return "openapi";
58+
}
59+
}

deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/template/QuteTemplatingEngineAdapter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public QuteTemplatingEngineAdapter() {
3737
.addDefaults()
3838
.addValueResolver(new ReflectionValueResolver())
3939
.addNamespaceResolver(QuteTemplatingExtension.INSTANCE)
40+
.addNamespaceResolver(OpenApiNamespaceResolver.INSTANCE)
4041
.removeStandaloneLines(true)
4142
.strictRendering(false)
4243
.build();

deployment/src/main/resources/templates/pojo.qute

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
3232
public class {m.classname} {#if m.parent}extends {m.parent}{/if}{#if m.serializableModel} implements Serializable{/if} {
3333

3434
{#for v in m.vars}
35-
{#if !v.deprecated || generateModelDeprecated()}
35+
{#if !v.deprecated || openapi:genDeprecatedModelAttr(package, m.classname, codegen)}
3636
{#if v.isEnum}
3737
{#if v.isContainer && v.mostInnerItems}
3838
{#include enumClass.qute e=v/}
@@ -57,7 +57,7 @@ public class {m.classname} {#if m.parent}extends {m.parent}{/if}{#if m.serializa
5757
{/for}
5858

5959
{#for v in m.vars}
60-
{#if !v.deprecated || generateModelDeprecated()}
60+
{#if !v.deprecated || openapi:genDeprecatedModelAttr(package, m.classname, codegen)}
6161
/**
6262
{#if v.description}
6363
* {v.description}
@@ -127,7 +127,7 @@ public class {m.classname} {#if m.parent}extends {m.parent}{/if}{#if m.serializa
127127
{#if m.parent}
128128
sb.append(" ").append(toIndentedString(super.toString())).append("\n");{/if}
129129
{#for v in m.vars}
130-
{#if !v.deprecated || generateModelDeprecated()}
130+
{#if !v.deprecated || openapi:genDeprecatedModelAttr(package, m.classname, codegen)}
131131
sb.append(" {v.name}: ").append(toIndentedString({v.name})).append("\n");
132132
{/if}
133133
{/for}

deployment/src/test/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapperTest.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,18 @@ void verifyDeprecatedFields() throws URISyntaxException, FileNotFoundException {
104104
.flatMap(v -> v.getVariables().stream())
105105
.anyMatch(f -> f.getNameAsString().equals("allowUpgrade")))
106106
.isFalse();
107-
//TODO: add a condition where we know there is a deprecated attribute and verify it's been generated
107+
108+
// this class has a deprecated attribute, so we check the default behavior
109+
final Optional<File> connectorDeploymentStatus = generatedFiles.stream()
110+
.filter(f -> f.getName().endsWith("ConnectorDeploymentStatus.java")).findFirst();
111+
assertThat(connectorDeploymentStatus).isPresent();
112+
final CompilationUnit cu3 = StaticJavaParser.parse(connectorDeploymentStatus.orElseThrow());
113+
final List<FieldDeclaration> fields3 = cu3.findAll(FieldDeclaration.class);
114+
115+
assertThat(fields3.stream()
116+
.flatMap(v -> v.getVariables().stream())
117+
.anyMatch(f -> f.getNameAsString().equals("availableUpgrades")))
118+
.isTrue();
108119
}
109120

110121
@Test

0 commit comments

Comments
 (0)