Skip to content

Commit

Permalink
Add support for @JSONVIEW
Browse files Browse the repository at this point in the history
Fixes #797
  • Loading branch information
altro3 committed Jul 13, 2023
1 parent a73827d commit e7dc743
Show file tree
Hide file tree
Showing 6 changed files with 597 additions and 155 deletions.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,25 @@
import java.io.File;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Future;

import io.micronaut.core.annotation.AnnotationClassValue;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.util.ArrayUtils;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.Element;
import io.micronaut.inject.ast.TypedElement;

import com.fasterxml.jackson.annotation.JsonView;

/**
* Some util methods.
*
Expand Down Expand Up @@ -158,4 +167,33 @@ private static boolean findAnyAssignable(ClassElement type, List<String> typeNam
}
return false;
}

/**
* Read classnames from JsonView annotation.
*
* @param element The element
*
* @return List of classnames from JsonView annotation.
*/
@Nullable
public static List<String> getJsonViewClasses(@Nullable Element element) {
AnnotationValue<JsonView> classLvlJsonView = element != null ? element.getAnnotation(JsonView.class) : null;
if (classLvlJsonView == null) {
return null;
}
Map<CharSequence, Object> values = classLvlJsonView.getValues();
if (CollectionUtils.isEmpty(values)) {
return null;
}
AnnotationClassValue[] test = (AnnotationClassValue[]) values.get("value");
if (ArrayUtils.isEmpty(test)) {
return null;
}
List<String> jsonViewClasses = new ArrayList<>(test.length);
for (AnnotationClassValue value : test) {
jsonViewClasses.add(value.getName());
}

return jsonViewClasses;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -260,10 +260,18 @@ public class OpenApiApplicationVisitor extends AbstractOpenApiVisitor implements
* Final calculated openapi filenames.
*/
public static final String MICRONAUT_INTERNAL_OPENAPI_FILENAMES = "micronaut.internal.openapi.filenames";
/**
* Loaded micronaut-http-server-netty property (json-view.enabled).
*/
public static final String MICRONAUT_JACKSON_JSON_VIEW_ENABLED = "jackson.json-view.enabled";
/**
* Loaded micronaut environment.
*/
private static final String MICRONAUT_ENVIRONMENT = "micronaut.environment";
/**
* Loaded into context jackson.json-view.enabled property value.
*/
private static final String MICRONAUT_INTERNAL_JACKSON_JSON_VIEW_ENABLED = "micronaut.internal.jackson.json-view.enabled";
private static final String MICRONAUT_ENVIRONMENT_CREATED = "micronaut.environment.created";
private static final String MICRONAUT_OPENAPI_PROPERTIES = "micronaut.openapi.properties";
private static final String MICRONAUT_OPENAPI_ENDPOINTS = "micronaut.openapi.endpoints";
Expand Down Expand Up @@ -618,6 +626,19 @@ public static String getConfigurationProperty(String key, VisitorContext context
return environment != null ? environment.get(key, String.class).orElse(null) : null;
}

public static boolean isJsonViewEnabled(VisitorContext context) {

Boolean isJsonViewEnabled = context.get(MICRONAUT_INTERNAL_JACKSON_JSON_VIEW_ENABLED, Boolean.class).orElse(null);
if (isJsonViewEnabled != null) {
return isJsonViewEnabled;
}

isJsonViewEnabled = getBooleanProperty(MICRONAUT_JACKSON_JSON_VIEW_ENABLED, false, context);
context.put(MICRONAUT_INTERNAL_JACKSON_JSON_VIEW_ENABLED, isJsonViewEnabled);

return isJsonViewEnabled;
}

public static SecurityProperties getSecurityProperties(VisitorContext context) {

SecurityProperties securityProperties = context.get(MICRONAUT_INTERNAL_SECURITY_PROPERTIES, SecurityProperties.class).orElse(null);
Expand Down Expand Up @@ -1070,7 +1091,7 @@ private void copyOpenApi(OpenAPI to, OpenAPI from) {

private OpenAPI readOpenApi(ClassElement element, VisitorContext context) {
return element.findAnnotation(OpenAPIDefinition.class).flatMap(o -> {
Optional<OpenAPI> result = toValue(o.getValues(), context, OpenAPI.class);
Optional<OpenAPI> result = toValue(o.getValues(), context, OpenAPI.class, null);
result.ifPresent(openAPI -> {
List<io.swagger.v3.oas.models.security.SecurityRequirement> securityRequirements =
o.getAnnotations("security", SecurityRequirement.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import io.micronaut.inject.visitor.TypeElementVisitor;
import io.micronaut.inject.visitor.VisitorContext;
import io.micronaut.openapi.annotation.OpenAPIGroupInfo;
import io.micronaut.openapi.annotation.OpenAPIInclude;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.models.OpenAPI;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
package io.micronaut.openapi.visitor

import io.micronaut.openapi.AbstractOpenApiTypeElementSpec
import io.swagger.v3.oas.models.OpenAPI
import io.swagger.v3.oas.models.Operation
import io.swagger.v3.oas.models.media.Schema

class OpenApiJsonViewSpec extends AbstractOpenApiTypeElementSpec {

void "test build OpenAPI with JsonView"() {

setup:
System.setProperty(OpenApiApplicationVisitor.MICRONAUT_JACKSON_JSON_VIEW_ENABLED, "true")

when:
buildBeanDefinition('test.MyBean', '''
package test;
import java.util.List;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Post;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import com.fasterxml.jackson.annotation.JsonView;
@Controller
class OpenApiController {
@Get("/summary")
@JsonView(View.Summary.class)
@Operation(summary = "Return car summaries",
responses = @ApiResponse(responseCode = "200", content = @Content(array = @ArraySchema(schema = @Schema(implementation = Car.class)))))
public HttpResponse<?> getSummaries() {
return null;
}
@Get("/detail")
@JsonView(View.Detail.class)
@ApiResponse(responseCode = "200", description = "Return car detail", content = @Content(schema = @Schema(implementation = Car.class)))
@Operation(summary = "Return car detail")
public List<Car> getDetails() {
return null;
}
/**
* {@summary Return car sale summary}
*/
@Get("/sale")
@JsonView(View.Sale.class)
public List<Car> getSaleSummaries() {
return null;
}
@Post("/add")
public void addCar(@JsonView(View.Sale.class) @Body Car car) {
}
}
interface View {
interface Summary {}
interface Detail extends Summary {}
interface Sale {}
}
class Car {
@JsonView(View.Summary.class)
private String made;
@JsonView({View.Summary.class, View.Detail.class})
private String model;
@JsonView(View.Detail.class)
private List<Tire> tires;
@JsonView(View.Sale.class)
private int price;
@JsonView({View.Sale.class, View.Summary.class})
private int age;
// common
private String color;
public String getColor() {
return color;
}
public String getMade() {
return made;
}
public void setMade(String made) {
this.made = made;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public List<Tire> getTires() {
return tires;
}
public void setTires(List<Tire> tires) {
this.tires = tires;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void setColor(String color) {
this.color = color;
}
}
class Tire {
@JsonView(View.Summary.class)
private String made;
@JsonView(View.Detail.class)
private String condition;
public String getMade() {
return made;
}
public void setMade(String made) {
this.made = made;
}
public String getCondition() {
return condition;
}
public void setCondition(String condition) {
this.condition = condition;
}
}
@jakarta.inject.Singleton
class MyBean {}
''')
then: "the state is correct"
Utils.testReference != null

when: "The OpenAPI is retrieved"
OpenAPI openAPI = Utils.testReference
Schema carDetail = openAPI.components.schemas['Car_Detail']
Schema carSale = openAPI.components.schemas['Car_Sale']
Schema carSummary = openAPI.components.schemas['Car_Summary']
Schema tireDetail = openAPI.components.schemas['Tire_Detail']
Operation addOp = openAPI.paths."/add".post
Operation detailOp = openAPI.paths."/detail".get
Operation saleOp = openAPI.paths."/sale".get
Operation summaryOp = openAPI.paths."/summary".get

then:

addOp
addOp.requestBody.content.'application/json'.schema.$ref == '#/components/schemas/Car_Sale'

detailOp
detailOp.responses.'200'.content.'application/json'.schema.$ref == '#/components/schemas/Car_Detail'

saleOp
saleOp.responses.'200'.content.'application/json'.schema.items.$ref == '#/components/schemas/Car_Sale'

summaryOp
summaryOp.responses.'200'.content.'application/json'.schema.items.$ref == '#/components/schemas/Car_Summary'

carDetail
carDetail.properties.size() == 5
carDetail.properties.color
carDetail.properties.made
carDetail.properties.model
carDetail.properties.tires
carDetail.properties.age

carSale
carSale.properties.size() == 3
carSale.properties.color
carSale.properties.price
carSale.properties.age

carSummary
carSummary.properties.size() == 4
carSummary.properties.color
carSummary.properties.made
carSummary.properties.model
carSummary.properties.age

tireDetail
tireDetail.properties.size() == 2
tireDetail.properties.made
tireDetail.properties.condition

cleanup:
System.clearProperty(OpenApiApplicationVisitor.MICRONAUT_JACKSON_JSON_VIEW_ENABLED)
}
}

0 comments on commit e7dc743

Please sign in to comment.