-
Notifications
You must be signed in to change notification settings - Fork 93
Description
Hi,
I was confused why certain properties were included in our generated OpenAPI, although runtime behaviour does not include those fields in the response.
We have classes where @JsonView annotation is only on getter, not on the corresponding field, nor do we have setter.
Somehow this, maybe unorthodox, setup seems to be the source of confusion (I am rather noobie with JAX-RS+Jackson).
I tried to draft some tests to show my confusion in TDD spirit:
TypeResolverTests.java
The test below also asserts on readOnly/writeOnly as I was seeing writeOnly written out to the generated OpenAPI and was confused.
But they might not be related to this context where I think the isIgnored() is the most relevant.
Nevertheless left them there for reference...
Also noticed that all other JsonView tests were using view classes that inherit from each other, but in our code the Views.Public/Views.Private were not related to each other.
@Test
void testJsonViewExcludeGetter() {
class Views {
class Public {
}
class Private {
}
}
@SuppressWarnings("unused")
class Bean {
private String field0;
// adding the annotation to field fixes isIgnored == true assertion
// But readOnly/writeOnly are false/false respectively.
// @JsonView(Views.Private.class)
private String field1;
public Bean(String f1, String f2) {
this.field1 = f1;
this.field0 = f2;
}
public String getField0() {
return field0;
}
public void setField0(String field0) {
this.field0 = field0;
}
@JsonView(Views.Private.class)
public String getField1() {
return field1;
}
/* // Enabling the setter also fixes isIgnored === true
@JsonView(Views.Private.class)
public void setField1(String field) {
this.field1 = field;
} */
}
AnnotationScannerContext context = buildContext(emptyConfig(), Bean.class, Views.Private.class, Views.Public.class);
context.getJsonViews().put(Type.create(DotName.createSimple(Views.Public.class), Type.Kind.CLASS), true);
Map<String, TypeResolver> p0 = getProperties(context, Bean.class);
assertFalse(p0.get("field0").isIgnored());
// All these below fail, unless add setter or toggle field
assertTrue(p0.get("field1").isIgnored());
assertTrue(p0.get("field1").isReadOnly());
assertFalse(p0.get("field1").isWriteOnly());
}JAX-RS JsonViewTests
Here I tried to be minimal and show how our "getter only" Bean is written out wrongly IMHO.
I think it should not be included in the output document (to match Jackson runtime behaviour).
Although for example, adding the setter fixes the issue by not writing "secret" property to docs...
diff --git a/extension-jaxrs/src/test/java/io/smallrye/openapi/runtime/scanner/JsonViewTests.java b/extension-jaxrs/src/test/java/io/smallrye/openapi/runtime/scanner/JsonViewTests.java
index 0b69795f..bef41451 100644
--- a/extension-jaxrs/src/test/java/io/smallrye/openapi/runtime/scanner/JsonViewTests.java
+++ b/extension-jaxrs/src/test/java/io/smallrye/openapi/runtime/scanner/JsonViewTests.java
@@ -244,4 +244,80 @@ class JsonViewTests extends IndexScannerTestBase {
printToConsole(result);
assertJsonEquals("special.jsonview-with-ignored.json", result);
}
+
+ @Test
+ void testJsonViewGetterOnly() throws Exception {
+ class Views {
+ class Public {
+ }
+
+ class Internal {
+
+ }
+ }
+
+ @Schema(name = "BeanName")
+ class Bean {
+ String id;
+ String name;
+ String secret;
+
+ public Bean() {}
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @JsonView(Views.Internal.class)
+ public String getSecret() {
+ return secret;
+ }
+
+ }
+
+ @jakarta.ws.rs.Path("/item/{id}")
+ class TestResource {
+ @jakarta.ws.rs.GET
+ @jakarta.ws.rs.Path("internal")
+ @jakarta.ws.rs.Produces(jakarta.ws.rs.core.MediaType.APPLICATION_JSON)
+ @com.fasterxml.jackson.annotation.JsonView(Views.Internal.class)
+ public Bean getInternal() {
+ return null;
+ }
+
+ @jakarta.ws.rs.GET
+ @jakarta.ws.rs.Path("public")
+ @jakarta.ws.rs.Produces(jakarta.ws.rs.core.MediaType.APPLICATION_JSON)
+ @com.fasterxml.jackson.annotation.JsonView(Views.Public.class)
+ public Bean getPublic() {
+ return null;
+ }
+ }
+
+ Index index = Index.of(Views.Public.class, Views.Internal.class, Bean.class, TestResource.class);
+ OpenApiConfig config = dynamicConfig(SmallRyeOASConfig.SMALLRYE_REMOVE_UNUSED_SCHEMAS, Boolean.TRUE);
+ OpenApiAnnotationScanner scanner = new OpenApiAnnotationScanner(config, index);
+
+ OpenApiDocument document = OpenApiDocument.newInstance();
+ document.reset();
+ document.config(config);
+ document.modelFromAnnotations(scanner.scan());
+ document.initialize();
+
+ OpenAPI result = document.get();
+ printToConsole(result);
+ assertJsonEquals("special.jsonview-schemas-getter.json", result);
+ }
}special.jsonview-schemas-getter.json – notice that the tests currently PASS with this and I think it is wrong.
IMO "secret" should not be in BeanName_Public at all.
{
"openapi": "3.1.0",
"info": {
"title": "Generated API",
"version": "1.0"
},
"paths": {
"/item/{id}/internal": {
"get": {
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BeanName_Internal"
}
}
}
}
}
}
},
"/item/{id}/public": {
"get": {
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BeanName_Public"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"BeanName_Internal": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"secret": {
"type": "string"
}
}
},
"BeanName_Public": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"secret": {
"type": "string",
"writeOnly" : true
}
}
}
}
}
}
We can fix the issue by being more explicit in our Jackson classes e.g. not skipping setters which has been sort of 'read only marker' for us.
But I think this could be worth to take a look on, as I feel current behaviour clearly does not map to the Jackson runtime behaviour where response does not contain "secret" field.
I've also spent too much time on trying to find out what we were doing wrong, so might be time saver in the future for someone idk 😸
Also as stated, I am pretty noob in this field so might be that this is somewhat user error.
Unfortunately there isn't too much documentation available around how these things work together so I really haven't find "The Answer" on how to work with these.
Thanks and props for the efforts with this library!