All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.dinject.javalin.generator.openapi.SchemaDocBuilder Maven / Gradle / Ivy

There is a newer version: 1.22
Show newest version
package io.dinject.javalin.generator.openapi;

import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.MapSchema;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.ObjectSchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.RequestBody;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.validation.constraints.Email;
import javax.validation.constraints.Size;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import static io.dinject.javalin.generator.Util.typeDef;

/**
 * Help build OpenAPI Schema objects.
 */
class SchemaDocBuilder {

  private static final String APP_FORM = "application/x-www-form-urlencoded";
  private static final String APP_JSON = "application/json";

  private final Types types;
  private final KnownTypes knownTypes;
  private final TypeMirror iterableType;
  private final TypeMirror mapType;

  private final Map schemas = new TreeMap<>();

  SchemaDocBuilder(Types types, Elements elements) {
    this.types = types;
    this.knownTypes = new KnownTypes();
    this.iterableType = types.erasure(elements.getTypeElement("java.lang.Iterable").asType());
    this.mapType = types.erasure(elements.getTypeElement("java.util.Map").asType());
  }

  Map getSchemas() {
    return schemas;
  }

  Content createContent(TypeMirror returnType, String mediaType) {
    MediaType mt = new MediaType();
    mt.setSchema(toSchema(returnType));
    Content content = new Content();
    content.addMediaType(mediaType, mt);
    return content;
  }

  /**
   * Add parameter as a form parameter.
   */
  void addFormParam(Operation operation, String varName, Schema schema) {
    RequestBody body = requestBody(operation);
    Schema formSchema = requestFormParamSchema(body);
    formSchema.addProperties(varName, schema);
  }

  private Schema requestFormParamSchema(RequestBody body) {

    final Content content = body.getContent();
    MediaType mediaType = content.get(APP_FORM);

    Schema schema;
    if (mediaType != null) {
      schema = mediaType.getSchema();
    } else {
      schema = new Schema();
      schema.setType("object");
      mediaType = new MediaType();
      mediaType.schema(schema);
      content.addMediaType(APP_FORM, mediaType);
    }
    return schema;
  }

  /**
   * Add as request body.
   */
  void addRequestBody(Operation operation, Schema schema, boolean asForm, String description) {

    RequestBody body = requestBody(operation);
    body.setDescription(description);

    MediaType mt = new MediaType();
    mt.schema(schema);

    String mime = asForm ? APP_FORM : APP_JSON;
    body.getContent().addMediaType(mime, mt);
  }

  private RequestBody requestBody(Operation operation) {

    RequestBody body = operation.getRequestBody();
    if (body == null) {
      body = new RequestBody();
      body.setRequired(true);
      Content content = new Content();
      body.setContent(content);
      operation.setRequestBody(body);
    }
    return body;
  }

  Schema toSchema(TypeMirror type) {

    Schema schema = knownTypes.createSchema(typeDef(type));
    if (schema != null) {
      return schema;
    }
    if (types.isAssignable(type, mapType)) {
      return buildMapSchema(type);
    }

    if (type.getKind() == TypeKind.ARRAY) {
      return buildArraySchema(type);
    }

    if (types.isAssignable(type, iterableType)) {
      return buildIterableSchema(type);
    }

    return buildObjectSchema(type);
  }

  private Schema buildObjectSchema(TypeMirror type) {

    String objectSchemaKey = getObjectSchemaName(type);

    Schema objectSchema = schemas.get(objectSchemaKey);
    if (objectSchema == null) {
      // Put first to resolve recursive stack overflow
      objectSchema = new ObjectSchema();
      schemas.put(objectSchemaKey, objectSchema);
      populateObjectSchema(type, objectSchema);
    }

    Schema ref = new Schema();
    ref.$ref("#/components/schemas/" + objectSchemaKey);
    return ref;
  }

  private Schema buildIterableSchema(TypeMirror type) {

    Schema itemSchema = new ObjectSchema().format("unknownIterableType");

    if (type.getKind() == TypeKind.DECLARED) {
      List typeArguments = ((DeclaredType) type).getTypeArguments();
      if (typeArguments.size() == 1) {
        itemSchema = toSchema(typeArguments.get(0));
      }
    }

    ArraySchema arraySchema = new ArraySchema();
    arraySchema.setItems(itemSchema);
    return arraySchema;
  }

  private Schema buildArraySchema(TypeMirror type) {

    ArrayType arrayType = types.getArrayType(type);
    Schema itemSchema = toSchema(arrayType.getComponentType());

    ArraySchema arraySchema = new ArraySchema();
    arraySchema.setItems(itemSchema);
    return arraySchema;
  }

  private Schema buildMapSchema(TypeMirror type) {

    Schema valueSchema = new ObjectSchema().format("unknownMapValueType");

    if (type.getKind() == TypeKind.DECLARED) {
      DeclaredType declaredType = (DeclaredType) type;
      List typeArguments = declaredType.getTypeArguments();
      if (typeArguments.size() == 2) {
        valueSchema = toSchema(typeArguments.get(1));
      }
    }

    MapSchema mapSchema = new MapSchema();
    mapSchema.setAdditionalProperties(valueSchema);
    return mapSchema;
  }

  private String getObjectSchemaName(TypeMirror type) {
    String canonicalName = type.toString();
    int pos = canonicalName.lastIndexOf('.');
    if (pos > -1) {
      canonicalName = canonicalName.substring(pos + 1);
    }
    return canonicalName;
  }

  private  void populateObjectSchema(TypeMirror objectType, Schema objectSchema) {
    Element element = types.asElement(objectType);
    for (VariableElement field : allFields(element)) {
      Schema propSchema = toSchema(field.asType());
      if (isNotNullable(field)) {
        propSchema.setNullable(Boolean.FALSE);
      }
      setLengthMinMax(field, propSchema);
      setFormatFromValidation(field, propSchema);
      objectSchema.addProperties(field.getSimpleName().toString(), propSchema);
    }
  }

  private void setFormatFromValidation(Element element, Schema propSchema) {
    if (element.getAnnotation(Email.class) != null) {
      propSchema.setFormat("email");
    }
  }

  private void setLengthMinMax(Element element, Schema propSchema) {
    final Size size = element.getAnnotation(Size.class);
    if (size != null) {
      if (size.min() > 0) {
        propSchema.setMinLength(size.min());
      }
      if (size.max() > 0) {
        propSchema.setMaxLength(size.max());
      }
    }
  }

  private boolean isNotNullable(Element element) {
    return element.getAnnotation(org.jetbrains.annotations.NotNull.class) != null
      || element.getAnnotation(javax.validation.constraints.NotNull.class) != null;
  }

  /**
   * Gather all the fields (properties) for the given bean element.
   */
  private List allFields(Element element) {

    List list = new ArrayList<>();
    gatherProperties(list, element);
    return list;
  }

  /**
   * Recursively gather all the fields (properties) for the given bean element.
   */
  private void gatherProperties(List fields, Element element) {

    if (element == null) {
      return;
    }
    if (element instanceof TypeElement) {
      Element mappedSuper = types.asElement(((TypeElement) element).getSuperclass());
      if (mappedSuper != null && !"java.lang.Object".equals(mappedSuper.toString())) {
        gatherProperties(fields, mappedSuper);
      }
      for (VariableElement field : ElementFilter.fieldsIn(element.getEnclosedElements())) {
        if (!ignoreField(field)) {
          fields.add(field);
        }
      }
    }
  }

  /**
   * Ignore static or transient fields.
   */
  private boolean ignoreField(VariableElement field) {
    return isStaticOrTransient(field) || isHiddenField(field);
  }

  private boolean isHiddenField(VariableElement field) {

    Hidden hidden = field.getAnnotation(Hidden.class);
    if (hidden != null) {
      return true;
    }
    for (AnnotationMirror annotationMirror : field.getAnnotationMirrors()) {
      String simpleName = annotationMirror.getAnnotationType().asElement().getSimpleName().toString();
      if ("JsonIgnore".equals(simpleName)) {
        return true;
      }
    }
    return false;
  }

  private boolean isStaticOrTransient(VariableElement field) {
    Set modifiers = field.getModifiers();
    return (modifiers.contains(Modifier.STATIC) || modifiers.contains(Modifier.TRANSIENT));
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy