001package io.avaje.http.generator.core.openapi; 002 003import com.fasterxml.jackson.annotation.JsonInclude; 004import com.fasterxml.jackson.databind.DeserializationFeature; 005import com.fasterxml.jackson.databind.ObjectMapper; 006import com.fasterxml.jackson.databind.SerializationFeature; 007import io.swagger.v3.oas.annotations.OpenAPIDefinition; 008import io.swagger.v3.oas.annotations.tags.Tag; 009import io.swagger.v3.oas.annotations.tags.Tags; 010import io.swagger.v3.oas.models.Components; 011import io.swagger.v3.oas.models.OpenAPI; 012import io.swagger.v3.oas.models.Operation; 013import io.swagger.v3.oas.models.PathItem; 014import io.swagger.v3.oas.models.Paths; 015import io.swagger.v3.oas.models.info.Info; 016import io.swagger.v3.oas.models.media.Content; 017import io.swagger.v3.oas.models.media.Schema; 018 019import javax.annotation.processing.Filer; 020import javax.annotation.processing.Messager; 021import javax.annotation.processing.ProcessingEnvironment; 022import javax.lang.model.element.Element; 023import javax.lang.model.element.TypeElement; 024import javax.lang.model.type.TypeMirror; 025import javax.lang.model.util.Elements; 026import javax.tools.Diagnostic; 027import javax.tools.FileObject; 028import javax.tools.StandardLocation; 029import java.io.IOException; 030import java.io.Writer; 031import java.util.Map; 032import java.util.TreeMap; 033 034/** 035 * Context for building the OpenAPI documentation. 036 */ 037public class DocContext { 038 039 private final boolean openApiAvailable; 040 041 private final Elements elements; 042 043 private final Filer filer; 044 045 private final Messager messager; 046 047 private final Map<String, PathItem> pathMap = new TreeMap<>(); 048 049 private final SchemaDocBuilder schemaBuilder; 050 051 private final OpenAPI openAPI; 052 053 public DocContext(ProcessingEnvironment env, boolean openApiAvailable) { 054 this.openApiAvailable = openApiAvailable; 055 this.elements = env.getElementUtils(); 056 this.filer = env.getFiler(); 057 this.messager = env.getMessager(); 058 this.schemaBuilder = new SchemaDocBuilder(env.getTypeUtils(), env.getElementUtils()); 059 this.openAPI = initOpenAPI(); 060 } 061 062 public boolean isOpenApiAvailable() { 063 return openApiAvailable; 064 } 065 066 private OpenAPI initOpenAPI() { 067 068 OpenAPI openAPI = new OpenAPI(); 069 openAPI.setPaths(new Paths()); 070 071 Info info = new Info(); 072 info.setTitle(""); 073 info.setVersion(""); 074 openAPI.setInfo(info); 075 076 return openAPI; 077 } 078 079 Schema toSchema(String rawType, Element element) { 080 TypeElement typeElement = elements.getTypeElement(rawType); 081 if (typeElement == null) { 082 // primitive types etc 083 return schemaBuilder.toSchema(element.asType()); 084 } else { 085 return schemaBuilder.toSchema(typeElement.asType()); 086 } 087 } 088 089 Content createContent(TypeMirror returnType, String mediaType) { 090 return schemaBuilder.createContent(returnType, mediaType); 091 } 092 093 PathItem pathItem(String fullPath) { 094 return pathMap.computeIfAbsent(fullPath, s -> new PathItem()); 095 } 096 097 void addFormParam(Operation operation, String varName, Schema schema) { 098 schemaBuilder.addFormParam(operation, varName, schema); 099 } 100 101 void addRequestBody(Operation operation, Schema schema, boolean asForm, String description) { 102 schemaBuilder.addRequestBody(operation, schema, asForm, description); 103 } 104 105 /** 106 * Return the OpenAPI adding the paths and schemas. 107 */ 108 private OpenAPI getApiForWriting() { 109 110 Paths paths = openAPI.getPaths(); 111 if (paths == null) { 112 paths = new Paths(); 113 openAPI.setPaths(paths); 114 } 115 // add paths by natural order 116 for (Map.Entry<String, PathItem> entry : pathMap.entrySet()) { 117 paths.addPathItem(entry.getKey(), entry.getValue()); 118 } 119 120 components().setSchemas(schemaBuilder.getSchemas()); 121 return openAPI; 122 } 123 124 /** 125 * Return the components creating if needed. 126 */ 127 private Components components() { 128 Components components = openAPI.getComponents(); 129 if (components == null) { 130 components = new Components(); 131 openAPI.setComponents(components); 132 } 133 return components; 134 } 135 136 private io.swagger.v3.oas.models.tags.Tag createTagItem(Tag tag){ 137 io.swagger.v3.oas.models.tags.Tag tagsItem = new io.swagger.v3.oas.models.tags.Tag(); 138 tagsItem.setName(tag.name()); 139 tagsItem.setDescription(tag.description()); 140 // tagsItem.setExtensions(tag.extensions()); # Not sure about the extensions 141 // tagsItem.setExternalDocs(tag.externalDocs()); # Not sure about the external docs 142 return tagsItem; 143 } 144 145 public void addTagsDefinition(Element element) { 146 Tags tags = element.getAnnotation(Tags.class); 147 if(tags == null) 148 return; 149 150 for(Tag tag: tags.value()){ 151 openAPI.addTagsItem(createTagItem(tag)); 152 } 153 } 154 155 public void addTagDefinition(Element element){ 156 Tag tag = element.getAnnotation(Tag.class); 157 if(tag == null) 158 return; 159 160 openAPI.addTagsItem(createTagItem(tag)); 161 } 162 163 public void readApiDefinition(Element element) { 164 165 OpenAPIDefinition openApi = element.getAnnotation(OpenAPIDefinition.class); 166 io.swagger.v3.oas.annotations.info.Info info = openApi.info(); 167 if (!info.title().isEmpty()) { 168 openAPI.getInfo().setTitle(info.title()); 169 } 170 if (!info.description().isEmpty()) { 171 openAPI.getInfo().setDescription(info.description()); 172 } 173 if (!info.version().isEmpty()) { 174 openAPI.getInfo().setVersion(info.version()); 175 } 176 177 } 178 179 public void writeApi() { 180 try (Writer metaWriter = createMetaWriter()) { 181 182 OpenAPI openAPI = getApiForWriting(); 183 ObjectMapper mapper = createObjectMapper(); 184 mapper.writeValue(metaWriter, openAPI); 185 186 } catch (IOException e) { 187 logError(null, "Error writing openapi file" + e.getMessage()); 188 e.printStackTrace(); 189 } 190 } 191 192 private ObjectMapper createObjectMapper() { 193 194 ObjectMapper mapper = new ObjectMapper(); 195 mapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL) 196 .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) 197 .enable(SerializationFeature.INDENT_OUTPUT); 198 199 return mapper; 200 } 201 202 private Writer createMetaWriter() throws IOException { 203 FileObject writer = filer.createResource(StandardLocation.CLASS_OUTPUT, "meta", "openapi.json", null); 204 return writer.openWriter(); 205 } 206 207 private void logError(Element e, String msg, Object... args) { 208 messager.printMessage(Diagnostic.Kind.ERROR, String.format(msg, args), e); 209 } 210 211}