flywayã®ã¡ã¢
Maven Goal - Flyway - Product Documentation
Flywayのマイグレーションの管理を考えてみる(Spring Bootでのサンプル付き) - CLOVER🍀
【Java】シンプルなデータベースマイグレーションツール「flyway」を導入する - ほんじゃーねっと
Flyway は複数人での開発に向かないという誤解について - tototoshi の日記
FlywayをJavaプログラムから使ってみる、その1 | GWT Center
SpringBoot × Flyway3パターン(FlywayAutoConfiguration/Java API/Mavenプラグイン) #Java - Qiita
Lombokã®ã¡ã¢
使ãæ¹
Lombok Experimental features - abcdefg.....
Lombokの@BuilderがCSVファイル生成に役立った話 - STORES Product Blog
JavaDoc生成の際、Lombokの@Builderを使うと「エラー:シンボルを見つけられません」が発生する #javadoc - Qiita
SpotBugsが可変オブジェクトでないものを可変オブジェクトと判定してしまう場合の対処法 - エキサイト TechBlog.
Lombokを使っているときにJacocoのカバレッジから自動生成分を除外する方法 #Java - Qiita
lombok.config
lombok.extern.findbugs.addSuppressFBWarnings = true lombok.addLombokGeneratedAnnotation = true
OpenAPI(MicroProfile)ãæ¡å¼µãã¦ã¿ã
ã¯ããã«
ã§OpenAPIï¼MicroProfileï¼ã®å®è£
ããã¦ã¿ã¾ããããããå°ã楽ãã§ããããã«ãããã¨æãã¾ããã
ãã¨ãã£ã¦å®è£
ãã®ãã®ãæ¡å¼µããã大ããããªãã®ãã¤ããã»ã©ã§ãç¡ããç¨ããç¨åº¦ã«ãæ¡å¼µããã¦ã¿ã¾ããã
æ¡å¼µãã¤ã³ã
ããã¸ã§ã¯ãå ¨ä½ã®å®ç¾©
OpenApiModelReader
ï¼OASModelReaderã®å®è£
ï¼ã«
- Lisence
- ããã¥ã¡ã³ã
- ã»ãã¥ãªãã£ï¼ãã¼ã¯ã³ã®è¨å®ï¼
ãªã©ã®ããã¸ã§ã¯ãã®åºæ¬æ å ±ãè¨å®ãã¾ãã
ãªãã¸ã§ã¯ãã®Schemaã追å
OpenApiSchema
ã«å¤æããããã¯ã©ã¹ã¨ ref
ã§ä½¿ç¨ãããã¼å¤ãç»é²ãã¾ãã
public static final String Gender = prifixPath + "Gender";
ãã¼å¤ã®ãã¢ã¨ãªããSchemaçæé¢æ°ãç»é²ãã¾ã.
// openApiã®Utilã¯å®è£ ä¾åã¨ãªãããå®è¡æã«æ±ºå®ããããã«é¢æ°ã§æå®ãã¾ã. private static final Map<String, Supplier<Schema>> schemaMap = Map.ofEntries( entry(Gender, () -> OpenApiSchemaUtil.createEnumSchema(Gender.class)), entry(Genders, () -> OpenApiSchemaUtil.createEnumListSchema(Gender.class)), entry(UploadFile, () -> OpenApiSchemaUtil.createUploadFileSchema()), entry(UploadFiles, () -> OpenApiSchemaUtil.createUploadFileListSchema()));
å®éã«çæããã®ã¯ OpenApiModelReader#buildModel
å
ã§OpenApiSchema.appendSchema(components);
ã§è¡ãã¾ãã
ç»é²ãããã¼å¤ã¯@Schema
ã®ref
ã¨ãã¦æå®ããã¾ãã
@Parameters({ @Parameter(name = "gender", description = "æ§å¥", schema = @Schema(ref = OpenApiSchema.Gender)), @Parameter(name = "name", description = "ã¦ã¼ã¶ã¼å", example = "user name") }) public Response getUsersByQuery( @QueryParam("gender") Gender gender, @QueryParam("name") String name) {
ãªã«ãããããã
ã¹ãã¼ãã®æå®ãã¯ã©ã¹ã§æå®ã§ããã®ã§ãæ¸ãééãã«ããå®è¡æã¨ã©ã¼ãåé¿ã§ãã¾ãã
å®è£
ref
ã®æå®ã¨ãã¦ã¡ã½ããã使ç¨ã§ããªããããå®è£
ã¨ãã¦ã¯ãçä¼¼ Enumãªã¹ãã©ãã¸ã¼ããä½ã£ã¦å¯¾å¿ããã¾ããã
/** OpenApiSchemaã®å¤æããã³Schemaã«refã«æå®ããå®æ°ã管çãã¾ã. */ public class OpenApiSchema { private static final String prifixPath = "#/components/schemas/"; public static final String Gender = prifixPath + "Gender"; public static final String Genders = prifixPath + "Genders"; public static final String UploadFile = prifixPath + "UploadFile"; public static final String UploadFiles = prifixPath + "UploadFiles"; // openApiã®Utilã¯å®è£ ä¾åã¨ãªãããå®è¡æã«æ±ºå®ããããã«é¢æ°ã§æå®ãã¾ã. private static final Map<String, Supplier<Schema>> schemaMap = Map.ofEntries( entry(Gender, () -> OpenApiSchemaUtil.createEnumSchema(Gender.class)), entry(Genders, () -> OpenApiSchemaUtil.createEnumListSchema(Gender.class)), entry(UploadFile, () -> OpenApiSchemaUtil.createUploadFileSchema()), entry(UploadFiles, () -> OpenApiSchemaUtil.createUploadFileListSchema())); /** * ããããã£ã§æå®ããã¯ã©ã¹ãSchemaã¸å¤æãã¦OpenAPIã®ã³ã³ãã¼ãã³ãã¸è¿½è¨ãã¾ã. * * @param components OpenAPIã®components */ public static void appendSchema(Components components) { validate(); int startIndex = prifixPath.length(); schemaMap.entrySet().stream() .forEach( entrySet -> { var key = entrySet.getKey().substring(startIndex); components.addSchema(key, entrySet.getValue().get()); }); } /** Publicãã£ã¼ã«ãã¨ã¹ãã¼ãã®è¨å®ãããMapã®æ´åæ§ãåãã¦ãããã¨ãæ¤è¨¼ãã¾ã. */ private static void validate() { var fieldList = Stream.of(OpenApiSchema.class.getFields()) .filter(f -> f.getType().isPrimitive() == false) .filter(f -> f.getType().isInstance("")) .map(f -> f.getName()) .collect(Collectors.toSet()); if (schemaMap.entrySet().size() != fieldList.size()) { throw new IllegalArgumentException( "public static field is not match schemaMap. append Schema must match."); } } }
@ExampleObjectã«jsonãæå®
@ExampleObject
ã®value
ã«jsonãè¨è¿°ããããã¨ãã§ãã¾ãããresourceé
ä¸ã®jsonãã¡ã¤ã«ãæå®ãããã¨ã¯ã§ãã¾ããã
æ¬æ¥ãexternalValue
ã¯http
ã使ç¨ãã¦å¤é¨ãªã½ã¼ã¹ãåç
§ãããã®ã§ããããã¾ã使ç¨ãããã¨ã¯ç¡ãã¨èããexternalValue
ã«ãªã½ã¼ã¹ãã¹ãæå®ãã¦èªã¿è¾¼ããããã«ãã¾ããã
externalValue = "openapi/user/get_response_default.json"
src/main/resources/openapi/user/get_response_default.json
å
é¨çã«ã¯ãexternalValue
ã®jsonãå±éããçµæãvalue
ã¸è»¢è¨ãã¦ãOpenAPI.yamlã¨ãã¦ããã®ã¾ã¾ä½¿ããããã«ãã¦ãã¾ãã
ãªã«ãããããã
åãã¬ã¹ãã³ã¹ã®åã使ã£ãExampleã®è¨è¿°ãå®çµã«è¨è¼ã§ãã¾ãã
ã¾ãã¨ã¹ã±ã¼ãã®ç¡ãjsonã®è¨è¿°ãã§ããã®ã§è¦ãããã§ãã
å®è£
OASFilter
ã®å®è£
ã§ããOpenApiFilter
ã§ãªã¯ã¨ã¹ãããã£ã¨ã¬ã¹ãã³ã¹ããã£ã®@ExampleObject
ã®ä¸èº«ãæ¸ãæãã¾ã.
/** OpenAPIã®OASFilterã®å®è£ . */ public class OpenApiFilter implements OASFilter { @Override public RequestBody filterRequestBody(RequestBody requestBody) { OpenApiExampleObjectUtil.convertExternalValueToValue(requestBody.getContent()); return OASFilter.super.filterRequestBody(requestBody); } @Override @SuppressWarnings("checkstyle:AbbreviationAsWordInName") public APIResponse filterAPIResponse(APIResponse apiResponse) { OpenApiExampleObjectUtil.convertExternalValueToValue(apiResponse.getContent()); return OASFilter.super.filterAPIResponse(apiResponse); } }
/** OpenApiExampleObjectUtil. */ public class OpenApiExampleObjectUtil { /** * ExampleObjectã®ExternalValueã§æå®ããJsonãValueã¨ãã¦å±éãã¾ã. * * <p>Contentãç´æ¥ä¸æ¸ããã¾ã. * * <p>ä¸æ¸ãã«ä½¿ç¨ãã externalValue ã¯æ¶å»ãã¾ã. * * <p>valueã«è¨è¿°ãããå ´åã¯valueã®è¨è¿°ãåªå ãã¾ã. * * @param content openApiã®@Content * @throws UncheckedIOException IOExceptionãçºçãããå¦çãä¸æãã¾ã. */ public static void convertExternalValueToValue(Content content) { content.getMediaTypes().entrySet().stream() .filter(e -> Objects.nonNull(e)) .forEach( e1 -> { e1.getValue().getExamples().entrySet().stream() .filter( e2 -> Objects.isNull(e2.getValue().getValue()) || e2.getValue().getValue().equals("")) .filter(e2 -> Objects.nonNull(e2.getValue().getExternalValue())) .forEach( example -> { var externalValue = example.getValue().getExternalValue(); ClassLoader loader = OpenApiExampleObjectUtil.class.getClassLoader(); try (var inputStream = loader.getResourceAsStream(externalValue)) { if (Objects.isNull(inputStream)) { throw new FileNotFoundException( "externalValue =[" + externalValue + "] cloud not find resource path."); } String json = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); example.getValue().setValue(json); example.getValue().setExternalValue(""); } catch (IOException ex) { throw new UncheckedIOException( "externalValue =[" + externalValue + "] could not find resource path or could not read resource file.", ex); } }); }); } }
ã¢ãã¯ã¨ãã¦jsonã使ç¨ãã
OpenAPIã¨ããããããRESTfulAPIã便å©ã«ãããã®ã§ãã
éçºåæã§ã¯ãã¤ã³ã¿ã¼ãã§ã¼ã¹ã¨ãã¦ã®OpenAPIã®å®ç¾©è¨å®ã«ããã㦠ã¢ããªã±ã¼ã·ã§ã³ãµã¼ãã¼ãã¢ãã¯ãµã¼ãã¨ãã¦ä½¿ç¨ãããã±ã¼ã¹ãããã¾ãã
ã¡ã½ããã®æ»ãå¤ã®åã¯Response
ã§ã¯ãªããç´æ¥ã¬ã¹ãã³ã¹ãè¿å´ãã¦ãã¾ãã
ï¼ãã ããå®è¡çµæã®Httpã200ãåºå®ã«ãªãã¾ãï¼
public UserResponse.UserResponseBody getJsonUserById(@PathParam("id") String id) { var response = JsonUtil.readFromResource( "openapi/user/get_response_default.json", UserResponse.UserResponseBody.class); return response.get(); }
ãªã«ãããããã
ãã§ã«ä½ææ¸ã¿ã®@ExampleObject
ã®jsonããã®ã¾ã¾ä½¿ç¨ãããã¨ãã§ããã®ã§æ¥½ãã§ãã¾ãã
å
¥åå¤ã§è¿å´å¤ãå¤ãããå ´åã¯ãè¤æ°ã®jsonãä½æãã¦å¼æ°ãå
ã«è¿å´ãåãæ¿ããããã«ããã ãã§å¯¾å¿ã§ãã¾ãã
å®è£
jsonã®èªã¿è¾¼ã¿ã§jackson
ã使ç¨ããã®ã§ãpom.xmlã«ä¾åã追å ãã¾ãã
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.16.1</version> <type>jar</type> </dependency>
ã¢ãã¯ã§ã®ä½¿ç¨ãåæã¨ãã¦ããã®ã§ãJsonã®æä½æã®ã¨ã©ã¼ãã³ããªã³ã°ãç°¡ç¥åãã¦*1å®è¡æä¾å¤ã¯ããã¦å¡ãã¤ã¶ãã¦ãã¾ãã
/** JsonUtil. */ public class JsonUtil { private static final Logger logger = Logger.getLogger(JsonUtil.class.getName()); /** * Jsonæååãã¯ã©ã¹ã«ãããã³ã°ãã¾ã. * * @param <T> ãããã³ã°ããã¯ã©ã¹ã®å * @param json jsonæåå * @param classType ãããã³ã°ããã¯ã©ã¹ã®å * @return ãããã³ã°ããã¤ã³ã¹ã¿ã³ã¹.ä¾å¤ããã£ãå ´å㯠{@code Optional.empty()} */ public static <T> Optional<T> read(String json, Class<T> classType) { ObjectMapper mapper = new ObjectMapper(); try { T object = mapper.readValue(json, classType); return Optional.of(object); } catch (JsonProcessingException ex) { logger.log(Level.SEVERE, "Json could not parse.", ex); return Optional.empty(); } } /** * Jsonãªã½ã¼ã¹ãã¯ã©ã¹ã«ãããã³ã°ãã¾ã. * * @param <T> ãããã³ã°ããã¯ã©ã¹ã®å * @param resourcePath ãªã½ã¼ã¹ãã¹ * @param classType ãããã³ã°ããã¯ã©ã¹ã®å * @return ãããã³ã°ããã¤ã³ã¹ã¿ã³ã¹.ä¾å¤ããã£ãå ´å㯠{@code Optional.empty()} */ public static <T> Optional<T> readFromResource(String resourcePath, Class<T> classType) { ClassLoader loader = Thread.currentThread().getContextClassLoader(); try (var inputStream = loader.getResourceAsStream(resourcePath)) { if (Objects.isNull(inputStream)) { throw new FileNotFoundException( "resourcePath =[" + resourcePath + "] cloud not find resource path."); } String json = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); return read(json, classType); } catch (IOException ex) { logger.log(Level.SEVERE, "resourcePath =[" + resourcePath + "] ioexception.", ex); } return Optional.empty(); } }
Code
experimentation/ee10-02-openapi at openapi-extend · vermeerlab/experimentation · GitHub
ãããã«
è²ã
ã¨èªåçæçãªãã¨ããããã¨ãæã£ãã®ã§ãããæ¨æºæ©è½ã«ããæ¡å¼µãã¤ã³ãã§åºæ¥ããã¨ã«ããã¦éå®ãã¦ããã»ãããEEç³»ã®å ´åã«ã¯è¯ãããªï¼ã¨æã£ã¦ ãã®ãããã«ãã¾ããããããã§ãéåã¨åé·ãªè¨è¿°ãæ¸ãã®ã§ã¯ãªãããªï¼ã¨æã£ã¦ãã¾ãã
ããã§ã¯Payaraã使ã£ã¦ãããããMicroProfileã®å®è£
ã¨ãã¦ã¯æ£ç´è²§å¼±ã ã¨æãã¾ãããQuarkusãHelidonã¨ãã£ãMicroProfileã®å®è£
ã使ãã°ï¼importã«Quarkusã®ã©ã¤ãã©ãªã使ããããªæ¡å¼µã使ãã°ï¼ããã£ã¨ä¾¿å©ãªãã®ããã§ã«æä¾ããã¦ããå¯è½æ§ã¯å¤§ãã«ããã¾ãã
*1:Eitherã®ä»£ããã«Optionalã§ç°¡æçã«å¦ç½®
Karateã®featureãOpenAPIããä½æï¼ZenWave Karate IDEï¼
ã¯ããã«
OpenAPIã®å®ç¾©ãDSLçã«ä½¿ã£ã¦ãKarateã®featureãèªåçæããVSCodeã®æ¡å¼µã§ãã
åºæ¬çãªä½¿ãæ¹ã¯æ¡å¼µæ©è½ã®ãµã¤ãã«ããåç»ãè¦ãã®ãåãããããã®ã§å²æãã¾ãã
ããã§ã¯èªåçæãããè³ç£ã«ã¤ãã¦ã®è£è¶³ã§ãã
èªå¯ãã¼ã¯ã³ã®ãã¡ã¤ã«ã®åºåå ´æ
OpenAPIã§èªå¯ãã¼ã¯ã³ããã«ããã¨ããã®è¾ºãã®è³ç£ãèªåçæãã¦ããã¾ãã
karate-auth.js
ã«èªå¯ãã¼ã¯ã³ã«é¢ããå
±éã®è¨å®ãè¨è¿°ã§ãã¾ãã
ãã ããã®ãã¡ã¤ã«ã®çæå
ã®ãã£ã¬ã¯ããªãããã¸ã§ã¯ãã®ã«ã¼ãã§ããã
ããã ã¨featureããã®ãã¹æå®ä½ç½®ã¨ãã¦ãµãããããªãããããã¹ãã³ã¼ãã®ã«ã¼ãã¨ãªããã£ã¬ã¯ããªã«ãã¡ã¤ã«ã移åããã¾ããã
ããã©ã«ãã¯ãã¼ã·ãã¯èªè¨¼ã«ãªã£ã¦ããã¿ããã§ãã
ä»åã¯èªå¯ãã¼ã¯ã³ãã¤ãã£ããã¸ãã¯ãªã©ã¯å®è£
ãã¦ããªãã®ã§ã³ã¡ã³ãã¢ã¦ããã¾ããã
èªåçæã§åºæ¥ãªãã£ãã¨ããã®æç´ã
é常ã®RESTfulãªä»æ§ã®ç¯å²ã¯ç¹ã«ã¨ã©ã¼ã«ãªããªãã£ãã§ãã
ãã ãã¡ã¤ã«ã®ã¢ãããã¼ãã ã㯠multi-part ã§æå®ãã¦ãããªãã£ãã®ã§ä¿®æ£ããã¾ãã*1ã
And multipart file file = {read:'test-data/FileToUpload1.txt', filename:'FileToUpload1.txt',Content-type:'mulitpart/form-data'}
Code
ã§ä½æããEEã®ã¢ããªãCargoã§èµ·åãã¦ãkarateã®ãã¹ããåããã¾ãã
æé ã¯ãä¸è¨ã®ãªãã¸ããªã®READMEã«è¨è¼ãã¦ããã®ã§ããã¡ããåèã«ãã¦ãã ããã
experimentation/karate-01-openapi at openapi · vermeerlab/experimentation · GitHub
ãããã«
OpenAPIãã¡ããã¨ã¤ããã¨ããããDSLã¨ãã¦è²ã
ã¨ã§ããã¨ããã®ã¯é常ã«è¯ãã§ããã
ãã¹ãã³ã¼ãã®æ§é åãã¦ããã¨ããã¨ããããè¦ããµã³ãã«ãããä¸æ©è¸ã¿è¾¼ãã æããªã®ã§è²ã
ã¨å¦ã³ãå¤ãã£ãããã«æãã¾ãã
Payaraã§OpenAPI(MicroProfile)
ã¯ããã«
Payaraã§OpenAPI-UIãã¤ãã£ã¦ãWebAPIã®ä»æ§ã¨å®è¡ã¤ã³ã¿ã¼ãã§ã¼ã¹ãæºåããå®è£
ã§ãã
軽ã触ã£ã¦ã¿ãæãã®å®è£
ä¾ããQuarkusï¼MicroProfileã®å®è£
ï¼ã使ã£ããã®ã¯ãã£ãã®ã§ããæ¨æºä»æ§ã ãã®ç¯çã ãã§ã§ãããã®ã¯ãªããªãè¦ã¤ãããªãã£ãã®ã§è²ã
ã¨è©¦ããªãããã£ã¦ãã£ã¦ã¿ã¾ããã
ã¡ã¤ã³ãOpenAPIã«ããã®ã§JAX-RSï¼Jakarta RESTful Web Servicesï¼ã«ã¤ãã¦ã®èª¬æã¯å²æãã¾ãã
- ã¯ããã«
- å®è¡ç°å¢
- ãã£ã¦ã¿ã¦æã£ããã¨ã»ããã£ããã¨
- OpenAPIã®è¨è¿°ã¯å®è£ ãããåªå
- ã¢ããã¼ã·ã§ã³ã§åºæ¥ãªãã¨ãã¯ã³ã¼ãã§ä½ãè¾¼ã¿
- OpenAPIã®è¨è¿°ã¨å®è¡è¨è¿°ã¯åãã¦ãããã
- tagãã¤ãã£ã¦ã³ã³ããã¼ã©ã¼åä½ã§éç´
- ã¬ã¹ãã³ã¹ã®åã¯Genericã使ããªãããã«ç´°ããä½æ
- @ExampleObjectãã¡ããã¨æ¸ã
- ãã©ã¡ã¼ã¿ã®è¨è¼ã¯ @Parameters ã使ã
- OpenAPI yamlã§ã®åºåããéç®ãã¦èãã
- pom
- Responseã®æ§é ä½
- GET
- POST
- PUT
- DELETE
- ãã¡ã¤ã«ã¢ãããã¼ã
- Enumã®ãªã¹ããã¯ã¨ãªãã©ã¡ã¼ã¿
- Exceptionï¼2xx系以å¤ã®ã¹ãã¼ã¿ã¹ï¼
- Code
- åè
- ãããã«
å®è¡ç°å¢
ãã£ã¦ã¿ã¦æã£ããã¨ã»ããã£ããã¨
å®éã«ãã£ã¦ã¿ã¦æã£ããã¨ãªã©ãå
ã«ã
ã©ããã¦ãããªã£ã¦ããã®ï¼ã¨ããã®ãåãã£ã¦ããã¨ä»¥ä¸ã®ã³ã¼ãã®çç±ãåãããããããã«æã£ãã®ã§ã
OpenAPIã®è¨è¿°ã¯å®è£ ãããåªå
å®è£
ããããç¨åº¦èªåçæã¯ããã¾ããã
OpenAPIã®è¨è¿°ã®æ¹ãåªå
ãããã®ã§OpenAPI-UIã§ã¤ããã¤ã³ã¿ã¼ãã§ã¼ã¹ã®ä½ãè¾¼ã¿ã¯èªåã§ã³ãã³ãæ¸ãè¾¼ãæ¹ã¨å²ãåã£ãæ¹ãè¯ãããã§ãã
ä¾ãã°Enumã¯ã©ã¹ã¯Enumã®è¦ç´ ã enumeration
ã§åæããã®ãåºæ¬ã¨ãªãã¿ããã§ããã¡ãã£ã¨é¢åã§ããâ¦ã
ã¡ãªã¿ã« @BeanParam
ã§å®ç¾©ãããã®ã¯ããã®ã¾ã¾ã§ã¯ã©ããã£ã¦ãOpenAPI-UIã§ä½¿ããªãã®ã§ãããOpenAPIã®è¨è¿°ãå®è£
ãããåªå
ããããã¨ã§ä½ãè¾¼ããã¨ãã§ãã¾ã*1ã
ã¢ããã¼ã·ã§ã³ã§åºæ¥ãªãã¨ãã¯ã³ã¼ãã§ä½ãè¾¼ã¿
ãªã¹ãã®ã¯ã¨ãªãã©ã¡ã¼ã¿ã¯ã¢ããã¼ã·ã§ã³ã§ã¯å®ç¾åºæ¥ã¾ããã§ããã
ãããªã¨ã㯠OASModelReader
ã®å®è£
ã®ä¸ã§ OASFactory.createComponents()
ãã¤ãã£ã¦ã³ã¼ãã§ä½ãè¾¼ãã¨å®ç¾ã§ãããã¨ãããã¾ãã
ã¢ããã¼ã·ã§ã³ã§é å¼µã£ã¦ä¸æããããªãã¨ãã¯å²ãåã£ã¦ã³ã¼ããæ¸ãã®ãä¸æ¡ã§ãã
ã¡ãªã¿ã«ãä¸è¨ã®ãEnumã¯enumeration
ã®åæãã§ãããã³ã¼ãã§ã¹ãã¼ããå®ç¾©ããã°å
±éåã§ãã¾ãã
OpenAPIã®è¨è¿°ã¨å®è¡è¨è¿°ã¯åãã¦ãããã
ã¡ã½ããã®å¼æ°ã«@Parameter
ãä»ããæ¹æ³ãããã¾ãããå人çã«ã¯ã¡ã½ããã¢ããã¼ã·ã§ã³ã«å¯ããæ¹ãã³ã¼ããèªã¿ãããããªã¨æãã¾ãã
ã¤ã¡ã¼ã¸ã¨ãã¦ã¯RESTfulã®å®è£
ãããä¸ã§ããã®ã³ã¼ãé¨åã«ã¯æãå ããã«ã¡ã½ããã¢ããã¼ã·ã§ã³ã§æ
å ±ãä»ä¸ããã¨ããæµãã§æ¸ã足ããæ¹ã好ã¿ã§ãã
ä»®å¼æ°ã®ä¸ã«è¤éãªã¢ããã¼ã·ã§ã³ãè¨è¿°ãã¦ããã¨ãã³ã¼ãã®å¼æ°ãã©ãã«ããã®ãè¦èªæ§ãæªããªãã¨ããã®ãçç±ã§ãã
ãã ãããã¾ãããããæ¸ãæ¹ãæ¨å¥¨ããã¨ããæ
å ±ãç¡ãã£ãã®ã§çéã§ã¯ãªããããããªãã§ãã
tagãã¤ãã£ã¦ã³ã³ããã¼ã©ã¼åä½ã§éç´
tagã使ãã®URLãã¾ã¨ãããã¨ãåºæ¥ã¾ãã
以åãSpringBoot & springdoc-openapi ã使ã£ãæã¯tagã使ããªãã£ãã®ã§å
¨é¨ããã©ããã«ãªã£ã¦ããã¥ã¡ã³ãã¨ãã¦ã®è¦èªæ§ãæªãã£ãããã«æãã¾ãã
ãªããtagãè¤æ°ä»ä¸ããã¨tagã®åã ãåãURLãåãOpenAPI-UIã«è¡¨ç¤ºããã¾ãã
OpenAPIã®å®ç¾©ãDSLã¨ãã¦ããã«èªåçæãããã¨ãä¸å
·åã«ç¹ãããããããªãã®ã§è¤æ°tagãä»ä¸ããã¨ãã¯ããã®å
ãå«ãã¦äºåã«æè¡æ¤è¨¼ããã¦ãããã¨ããããããã¾ãã
ã¬ã¹ãã³ã¹ã®åã¯Genericã使ããªãããã«ç´°ããä½æ
@Schema(implementation = Hoge.class)
ã¿ããã«è¨è¿°ãããã¨ã§ã¬ã¹ãã³ã¹ã®åã®æå®ãã§ããã®ã§ããGenericã使ãã¾ããã
ãªã®ã§ãã¬ã¹ãã³ã¹ã¯ã©ã¹ã代表ã¨ãã¦ããã®æ´¾çã¯ã©ã¹ãã¤ã³ãã¼ã¯ã©ã¹ã¨ãã¦å®ç¾©ãã¾ããã
public class UserResponse extends UserRequest { ï¼ç¥ï¼ static class UserResponseBody extends ResponseBody<UserResponse> {} }
@ExampleObjectãã¡ããã¨æ¸ã
Genericã使ããªããã°åæ
å ±ããããç¨åº¦ã¯OpenAPI-UIã§è¡¨ç¤ºå¯è½ãªå®ç¾©ãå®è£
ããèªåçæãã¦ããã¾ãã
ãã ä»å㯠ResponseBody
ã¨ããå
±éå®ç¾©ã«ã¬ã¹ãã³ã¹ãGenericã§å®ç¾©ãã¦å
¥ãåæ§é ãªæãã«ããã®ã§ããã®ãããã¯èªåã§ã¯ã§ãã*2ã
ãªã¯ã¨ã¹ãã¯å
¥ãåæ§é ã«ããªãã£ãã®ã§ãããObjectåï¼ä¾ãã°Enumï¼ã使ã£ããããã¨ãexampleãæã£ãããã«åºåãããªãã£ãã®ã§ããã¡ããã³ãã³ãå®ç¾©ãã¾ããã
ã¬ã¹ãã³ã¹ã¯ã¨ããããæ¡ä»¶ãæå®ãã¦å®éã«åºåãããã°exampleãç¡ãã¦ãææªãªãã¨ãã§ããªãã¯ãªãã®ã§ããããªã¯ã¨ã¹ãã¯POSTãPUTãããã¨ãã«OpenAPI-UIã§ä½¿ãããã®ã§æéãããã£ãã¨ãã¦ãããã¡ãã¨è¨è¿°ãããã¨ããå§ããã¾ãã
@ExampleObject
ãè¨è¿°ããã¨ãã®çæäºé
ã¨ãã¦
- MediaTypeãæãã
- nameãexampleã®ãã¼ã«ãªãã®ã§è¨è¿°ãå¿ããªã
ã¨ããã®ãããã£ãã¨ããã§ããã
ãã©ã¡ã¼ã¿ã®è¨è¼ã¯ @Parameters
ã使ã
å¼æ°ã§ã¯ãªãã¡ã½ããã«ãã©ã¡ã¼ã¿ãæå®ããå ´åããã©ã¡ã¼ã¿ãï¼ã¤ã®å ´åã§ãã@Parameters
ã使ããªãã¨OpenAPIã®åºåã¨ãã¦æ
å ±ãåºåããã¾ããã§ããã
OpenAPI yamlã§ã®åºåããéç®ãã¦èãã
ä¾ãã°ããã¡ã¤ã«ã¢ãããã¼ãã®æ¸ãæ¹ã«ã¤ãã¦ã¢ããã¼ã·ã§ã³ãã³ã¼ãã§ã®å®ç¾©æ¹æ³ãæ¢ãã¦ããã«è¦ã¤ãããªãå ´åã¯ãæçµçãªOpenAPIã®yamlã§ã®æ¸ãæ¹ã調ã¹ã¦ãããããããã©ããã£ããã¢ããã¼ã·ã§ã³ãããã¯ã³ã¼ãã§å®ç¾©ã§ããã®ãï¼ã¨ããã®ãéå¼ãã§çµã¿ç«ã¦ãã¨ã¹ã ã¼ãºã«è©¦è¡ãé²ãããã¾ããã
ä»åã®å®è£
ã§ããã¨
- Enumãæ¤ç´¢ç¨ã¯ã¨ãªã¨ãã¦ãªã¹ãã«ããã
- è¤æ°ãã¡ã¤ã«ã®ã¢ãããã¼ãã®è¨å®ã®ããæ¹
ããéå¼ãããè¦ã¤ããããå®ç¾©ã§ããã
pom
OpenAPI-UIã使ãããã®ã©ã¤ãã©ãªã
ããããªãã¨OpenAPI-UIã®ãã¼ã¸ãä½ãããªãã§ãã
<dependency> <groupId>org.microprofile-ext.openapi-ext</groupId> <artifactId>openapi-ui</artifactId> <version>2.1.0</version> </dependency>
ããã¦
http://localhost:8080/ee10-01-openapi/api/openapi-ui/index.html
ã¿ãããªæãã§ãã¢ããªã®ã«ã¼ããããopenapi-ui/index.htmlãã¸ã¢ã¯ã»ã¹ãããã¨OpenAPI-UIã®ç»é¢ã表示ããã¾ãã
ã¾ãã以ä¸ã«ã¢ã¯ã»ã¹ããã㨠OpenAPIã®å®ç¾©ï¼ãã¡ãã¯Payaraã®å®è£
ãä½æãã¦ãããã®ï¼ã確èªãããã¨ãã§ãã¾ãã
ãªã®ã§ãèªåãæå³ããç»é¢è¡¨ç¤ºãã§ãã¦ããªãã¨ãã¯ããã¡ãã確èªã㦠ã©ãããå®ç¾©ã«ãªãã°è¯ãã®ã確èªãããªããä½æ¥ãé²ãããã¨ã«ãªãã¾ãã
[http://localhost:8080/openapi]
Responseã®æ§é ä½
ã¾ãã¯Responseã®æ§é ä½ããã
å
±éã®æ§é ä½ãã©ãããããã®ãã¬ã¹ãã³ã¹ããããã«ãããã¨æãã¾ãã
ããã³ãã§HTTPã¹ãã¼ã¿ã¹ã§å¤å®ãããã®ãè¯ãã§ãããæ£å¸¸ç°å¸¸ãifæã§åãããã¨ããããããä»å æ
å ±ãè¨ãããã±ã¼ã¹ãæ³å®ãããã®ã§ãã
ãã¡ãããå
±éã¯ã©ã¹ãextendããããæ¹ã§ãè¯ãã¨æãã¾ãã
@Schema public class BaseResponseBody { @Schema(title = "ã¬ã¹ãã³ã¹æå¦", description = "æ£å¸¸ã®å ´åã¯true", readOnly = true) boolean ok;
@Schema public class ResponseBody<T> extends BaseResponseBody { @Schema(title = "ã¬ã¹ãã³ã¹ããã£", description = "ã¬ã¹ãã³ã¹æ¯ã®ç¬èªã®åã®ãªãã¸ã§ã¯ã", readOnly = true) private T body; ï¼getter setter ä»ã¯çç¥ï¼
ãªã¹ãæ§é ã®å ´åã¯ãã¡ãã使ãã¾ãã
@Schema public class ResponseListBody<T> extends BaseResponseBody { @Schema(title = "ã¬ã¹ãã³ã¹ããã£", description = "ã¬ã¹ãã³ã¹æ¯ã®ç¬èªã®åã®ãªãã¸ã§ã¯ã", readOnly = true) private List<T> body; public ResponseListBody() {} ï¼getter setter ä»ã¯çç¥ï¼
ãããä»ä¸ããããã®Factoryã使ã£ã¦Responseãã¤ããã¨ããæµãã§ãã
ã¡ãªã¿ã« GenericEntity
ã使ã£ã¦ãã¾ãããå¤å使ããªãã¦ãçµæã¯åããããªãããªï¼ã¨æãã¾ã*3ã
public static <T> Response success(T body) { var entity = new ResponseBody<T>(true, body); var responseBuilder = Response.ok(new GenericEntity<ResponseBody<T>>(entity) {}); return responseBuilder.build(); }
GET
ã·ã³ãã«ãªãã¹ã¢ã¯ã»ã¹
ä¸çªã·ã³ãã«ãªè¨è¼ã¯ãããªæãã§ãã
ããã§ãã¤ã³ãã«ãªãã®ã¯ã¬ã¹ãã³ã¹ã®åã®æå®ã¨ãã®å®è£
ã§ãã
static class UserResponseBody extends ResponseBody<UserResponse> {}
ã¨ããæãã§ãOpenAPIç¨ã«åã¯ã©ã¹ãä½ã£ã¦ããããæå®ãã¾ãã
å¿è«ãã¹ãã¼ããåå¥ã«å®ç¾©ããæ¹æ³ãããã¾ãããOASModelReader
ã«è¨è¼ããå¿
è¦ãããã®ã¨æååã§ã®æå®ã«ãªãã®ã§ã¯ã©ã¹è¨è¿°ã§å¯¾å¿ã§ãããã®ã¯æ¥µåãã¡ãã§å¯¾å¿ããã¦ããã®ãè¯ãã¨æãã¾ãã
@ExampleObject
ã§ãããexternalValue
ã«ã¯ã©ã¹ãã¹ãæå®ãã¦ããããã«ããJSONãã¡ã¤ã«ãèªã¿è¾¼ãã§ã¯ããã¾ããã§ãã*4ã
ãªã®ã§ã³ãã³ãã¨ãã¿ã§æ¸ãè¾¼ããã¨ã«ãªãã¾ã*5ã
@GET @Path("{id}") @Produces(MediaType.APPLICATION_JSON) @Operation(summary = "ã¦ã¼ã¶ã¼æ å ±ãæ¤ç´¢ãã¾ã") @APIResponse( content = { @Content( mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = UserResponse.UserResponseBody.class), examples = { @ExampleObject( name = "default", value = "{\n" + " \"body\": {\n" + " \"gender\": \"OTHER\",\n" + " \"name\": \"Name:example\",\n" + " \"id\": \"100\"\n" + " },\n" + " \"ok\": true\n" + "}") }) }, responseCode = "200") @Parameters({@Parameter(name = "id", description = "ã¦ã¼ã¶ã¼ID")}) public Response getUserById(@PathParam("id") String id) {
ã¬ã¹ãã³ã¹ã®å®ç¾©ãã¡ããã¨åºã¦ãã¾ããã
ï¼ç¹ã解決ãåºæ¥ãªãã£ããã¨ãããã¾ãã
ãªã¯ã¨ã¹ãã¨ã¬ã¹ãã³ã¹ãç¶æ¿ã§å®è£
ããã®ã§ããããããããã®å½±é¿ã§Enumé¨åã®è¦ç´ ãéè¤ãã¦ç»é²ããã¦ãã¾ãã¾ããã
ã¾ãããã¬ã¹ãã³ã¹ã¯åãæãªã®ã§å®å®³ã¯å°ãªãã¨ãããã¨ã§ãããã¯ç®ãã¤ã¶ããã¨ã«ãã¾ããã
ã¯ã¨ãªã使ã£ãåãåãã
ã¯ã¨ãªã使ã£ãå ´åã¯è¤æ°ã®çµæãè¿å´ãããã¨ãããã®ã§é
åãæ»ãå¤ã®åã«ãªãã¾ãã
å®ç¾©ã®ã³ã¼ãå®è£
ã¯ãããªæãã§ãã
static class UserResponseListBody extends BaseResponseBody { @SuppressFBWarnings("UUF_UNUSED_FIELD") private List<UserResponse> body; }
æ¬æ¥ãResponseListBodyã®ããããã£ãè¨è¿°ããªãã¦è¯ãã®ã§ãããOpenAPIã§å®ç¾©ããåæ å ±ã¨ãã¦ã¯ãããããã£ãä¸æ¸ããããããªã³ã¼ããæ¸ããã¨ã«ãªãã¾ãã
æè¦çã«ã¯ä»¥ä¸ãªã®ã§ãããããã ã¨Genericã List
ã®å
¥ãåã«ãªã£ã¦ãã¦åºåãããOpenAPIã®å®ç¾©ã«ã¯ã©ã¹åã®ã¹ãã¼ããè¨å®ããã¾ãã*6ã
static class UserResponseListNgBody extends ResponseListBody<UserResponse> {}
ãããã«ãã¦ãããã®ã¤ã³ãã¼ã¯ã©ã¹ã¯ãOpenAPIã®ã¹ãã¼ããåºåããããã®ã¯ã©ã¹ããªã®ã§ããããã£ããã§ããã ãããã®æ°æã¡ã§å²ãåã£ãæ¹ãè¯ãã¨æãã¾ã*7ã
ä¸è¨ã«å ãã¦ãè¤æ°ã®@ExampleObject
ã使ãå®è£
ä¾ã示ãã¾ãã
ä¾ãã°ãexampleã®ä¾ç¤ºã¨ãã¦ï¼ãã¿ã¼ã³ã示ããããã¨ãããããããã¾ããã
name
ããã¼ã¨ãã¦å®ç¾©ãããã¿ã¼ã³ã®è¡¨ç¤ºãåãæ¿ãããã¨ãã§ãã¾ãã
Enumã使ã£ãé¸æè¢ã®å®ç¾©ãã§ãã¾ãã
ãããããã¨ã§å®æ°åãã¤ãã£ãå®ç¾©ãã§ããã®ã§å©ç¨å´ã宣è¨çãªå®è£
ãå¯è½ã«ãªãã¾ã*8ã
@GET @Produces(MediaType.APPLICATION_JSON) @Operation( summary = "ã¦ã¼ã¶ã¼æ å ±ãæ¤ç´¢ãã¾ã", operationId = "getUsersByQuery", description = "ã¯ã¨ãªã¼ã§æå®ããæ¡ä»¶ãçµãè¾¼ã¿æ¡ä»¶ã¨ãã¦ä½¿ç¨ãã¾ãã<br>" + "æ¡ä»¶ãæå®ããªãå ´åã¯å ¨ã¬ã³ã¼ããåå¾å¯¾è±¡ã¨ãªãã¾ã") @APIResponse( content = { @Content( mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = UserResponse.UserResponseListBody.class), examples = { @ExampleObject( name = "default", value = "" + "{\n" + " \"body\": [\n" + " {\n" + " \"gender\": \"OTHER\",\n" + " \"name\": \"Name:example\",\n" + " \"id\": \"100\"\n" + " }\n" + " ],\n" + " \"ok\": true\n" + "}"), @ExampleObject( name = "return 2 record", value = "" + "{\n" + " \"body\": [\n" + " {\n" + " \"gender\": \"MALE\",\n" + " \"name\": \"Name:user name1\",\n" + " \"id\": \"1\"\n" + " },\n" + " {\n" + " \"gender\": \"OTHER\",\n" + " \"name\": \"Name:user name2\",\n" + " \"id\": \"2\"\n" + " }\n" + " ],\n" + " \"ok\": true\n" + "}") }) }, responseCode = "200") @Parameters({ @Parameter( name = "gender", description = "æ§å¥", schema = @Schema( enumeration = {"MALE", "FEMALE", "OTHER"}, implementation = String.class)), @Parameter(name = "name", description = "ã¦ã¼ã¶ã¼å", example = "user name") }) public Response getUsersByQuery( @QueryParam("gender") Gender gender, @QueryParam("name") String name) {
ã¡ãã£ã¨åããã«ããã§ãããé
åã«ãªã£ã¦ãã¾ããï¼[
]
ãããã¾ããï¼ã
ã¯ã¨ãªãã¤ãã£ãåãåããï¼@BeanParamï¼
ã¯ã¨ãªã§ï¼ã¤ï¼ã¤ã®å¤æ°ãã¡ã½ããã®å¼æ°ã¨ãã¦å®ç¾©ãããããã@BeanParam
ã§ï¼ã¤ã«ã¾ã¨ãã¦ä½¿ããããããããã®ã§ãã
ã§ããã° BeanParam
å
ã®ã¯ã©ã¹ã§ãã©ã¡ã¼ã¿ã®è¨å®ãã§ããã¨OpenAPIã®è¨è¿°ã¨ãã¦ãå
±éåã§ããã®ã§è¯ãã£ãã®ã§ãããæ®å¿µãªãã @Parameters
ãã¡ã½ããã«ããé©ç¨ã§ããªãã®ã§åé·ã§ã¯ããã¾ãããã¡ã½ããåä½ã§åããã¨ãè¨è¿°ããå¿
è¦ãããã¾ãã
åºæ¬ã¯ãã¿ã§ã¢ããã¼ã·ã§ã³ã§å®ç¾©ããã®ã¨éãã¯ããã¾ããã
@BeanParam
ã®è¦ç´ ããã¹ã¦è¨è¿°ããã ãã§ãã
ãã¤ã³ãã¯ãin = ParameterIn.QUERYãã¨ããããã«ã@BeanParam
ã®ã¢ããã¼ã·ã§ã³ã§æå®ãããã£ã¼ã«ãã«ä»ä¸ããã¢ããã¼ã·ã§ã³ï¼ä¾ãã° @QueryParam
ï¼ã¨åã£ãå®ç¾©ã追è¨ããã¨ããã§ã*9ã
@GET @Path("beamparam") @Produces(MediaType.APPLICATION_JSON) @Operation( summary = "ã¦ã¼ã¶ã¼æ å ±ãæ¤ç´¢ãã¾ãï¼BeanParamã使ç¨ï¼", description = "ã¯ã¨ãªã¼ã§æå®ããæ¡ä»¶ãçµãè¾¼ã¿æ¡ä»¶ã¨ãã¦ä½¿ç¨ãã¾ãã<br>" + "æ¡ä»¶ãæå®ããªãå ´åã¯å ¨ã¬ã³ã¼ããåå¾å¯¾è±¡ã¨ãªãã¾ãã<br>" + "ï¼BeanParamã«ããã¯ã¨ãªã¼æå®ãããã¨OpenAPIã§ã¯æå®ãã§ãã¾ããã<br>") @APIResponse( content = { @Content( mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = UserResponse.UserResponseListBody.class), examples = { @ExampleObject( name = "default", value = "" + "{\n" + " \"body\": [\n" + " {\n" + " \"gender\": \"OTHER\",\n" + " \"name\": \"Name:example\",\n" + " \"id\": \"100\"\n" + " }\n" + " ],\n" + " \"ok\": true\n" + "}") }) }, responseCode = "200") @Parameters({ @Parameter( name = "gender", in = ParameterIn.QUERY, description = "æ§å¥", schema = @Schema(ref = "#/components/schemas/Gender")), @Parameter( name = "name", in = ParameterIn.QUERY, description = "ã¦ã¼ã¶ã¼å", schema = @Schema(example = "User Name", implementation = String.class)) }) public Response getUsersByBeanParam(@BeanParam UserQueryParam userQueryParam) {
@BeanParam
ã¯JAX-RSã®å®è£
ã ãã§Open-APIã®ã¢ããã¼ã·ã§ã³ã¯ããã¾ããã
@lombok.Data public class UserQueryParam { @QueryParam("gender") private Gender gender; @QueryParam("name") private String name; }
POST
ãã¤ã³ãã¯ãªã¯ã¨ã¹ãããã£ã® @ExampleObject
ãå®ç¾©ããã¨ããã§ãã
ããã OpenAPI-UIã®ãªã¯ã¨ã¹ãããã£ã®éå½¢ã«ãªãã¾ãã
ä¾ãã°ãè¤æ°ãã¿ã¼ã³ãæºåãããæã¯è¤æ°è¨è¿°ãããã¨è¯ãã§ãããã
@POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @Operation(summary = "ã¦ã¼ã¶ã¼æ å ±ãç»é²ãã¾ã") @APIResponse( content = @Content( mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = UserResourceId.UserResponseIdBody.class), examples = { @ExampleObject( name = "default", value = "" + "{\n" + " \"body\": {\n" + " \"id\": \"57d1a3b9-bb09-42f4-9913-941de0a7d4cb\"\n" + " },\n" + " \"ok\": true\n" + "}") }), responseCode = "201") @RequestBody( content = @Content( mediaType = MediaType.APPLICATION_JSON, examples = { @ExampleObject( name = "default", value = "" + "{\n" + " \"gender\": \"MALE\",\n" + " \"name\": \"Name:name-1\"\n" + "}") })) public Response postUser(UserRequest userRequest) {
ãªã¯ã¨ã¹ãããã£ã®ã¯ã©ã¹ã« OpenAPIã®å®ç¾©ãè¨è¿°ãã¦ãã¾ãã
ãã¡ãã®Enumï¼ï¼ã¤ã®è¦ç´ ï¼ã¯ãæ¡å¼µãã¦ä½æããã¬ã¹ãã³ã¹ã¯ã©ã¹ã®çµæã¨éã£ã¦ãå®ç¾©éããï¼ã¤ã§ãã
ã¨ãããããã¯ã©ã¤ã¢ã³ãã¨ãã¦ã¯ä»æ§ã¨ãã¦å¦¥å½ãªãã®ãæ示ããã¦ããã¨ãããã¨ã§å第ç¹ããªï¼ã¨ã
@lombok.Data @lombok.NoArgsConstructor @Schema public class UserRequest { @Schema( title = "æ§å¥", example = "OTHER", enumeration = {"MALE", "FEMALE", "OTHER"}, required = true) private String gender; @Schema(title = "ã¦ã¼ã¶ã¼å", example = "User Name", required = true) private String name; User toModel() { return this.toModel(null); } User toModel(String userId) { var model = User.builder() .userId(Objects.isNull(userId) ? null : UserId.of(userId)) .gender(Gender.valueOf(this.gender)) .name(Text.of(name)) .build(); return model; } }
PUT
ç¹ã«ãããã¨ã¯ç¡ãâ¦
@PUT @Path("{id}") @Consumes(MediaType.APPLICATION_JSON) @Operation(summary = "ã¦ã¼ã¶ã¼æ å ±ãæ´æ°ãã¾ã") @APIResponse(responseCode = "204") @Parameters({ @Parameter( name = "id", description = "ã¦ã¼ã¶ã¼ID", example = "57d1a3b9-bb09-42f4-9913-941de0a7d4cb") }) @RequestBody( content = @Content( mediaType = MediaType.APPLICATION_JSON, examples = { @ExampleObject( name = "default", value = "" + "{\n" + " \"gender\": \"MALE\",\n" + " \"name\": \"Name:name-1\"\n" + "}") })) public void putUser(@PathParam("id") String id, UserRequest userRequest) {
DELETE
ãã¡ããç¹çãããã¨ã¯ãªãâ¦
@DELETE @Path("{id}") @Consumes(MediaType.APPLICATION_JSON) @Operation(summary = "ã¦ã¼ã¶ã¼æ å ±ãåé¤ãã¾ã") @APIResponse(responseCode = "204") @Parameters({ @Parameter( name = "id", description = "ã¦ã¼ã¶ã¼ID", example = "57d1a3b9-bb09-42f4-9913-941de0a7d4cb") }) public void deleteUser(@PathParam("id") String id) {
ãã¡ã¤ã«ã¢ãããã¼ã
WebAPIã¨ãã¦ãã¡ã¤ã«ãã¢ãããã¼ããããã¨ããããã£ã¦ããããã¨ããã§ãã
åä¸ãã¡ã¤ã«
ä¾ç¤ºã®ããã«ã¢ããã¼ã·ã§ã³ã§å®ç¾©ãã§ãã¾ãããã¢ãããã¼ãããå ´åã¯å¸¸ã«åãè¨è¿°ãããã®ã§ã³ã¼ãã§ã¹ãã¼ãå®ç¾©ãä½æãã¦å©ç¨ããã¨ããããæ¹ãã§ãã¾ãã
@POST @Path("{id}/file") @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.APPLICATION_JSON) @Operation(summary = "ã¦ã¼ã¶ã¼ã«é¢é£ãããã¡ã¤ã«ãã¢ãããã¼ããã¾ã") @APIResponse( content = { @Content( mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = BaseResponseBody.class), examples = @ExampleObject(name = "default", value = "{\"ok\": true}")) }, responseCode = "200") @Parameters(@Parameter(name = "id", description = "ã¦ã¼ã¶ã¼ID")) @RequestBody( description = "ã¢ãããã¼ããã¡ã¤ã«ãé¸æãã¦ãã ãã", content = @Content( mediaType = MediaType.MULTIPART_FORM_DATA, schema = @Schema( type = SchemaType.OBJECT, properties = { @SchemaProperty(name = "file", type = SchemaType.STRING, format = "binary"), }))) public Response postUploadUserFile(@PathParam("id") String id, EntityPart file) {
ã¹ãã¼ãã®å®ç¾©ç
@POST @Path("{id}/file") @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.APPLICATION_JSON) @Operation(summary = "ã¦ã¼ã¶ã¼ã«é¢é£ãããã¡ã¤ã«ãã¢ãããã¼ããã¾ã") @APIResponse( content = { @Content( mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = BaseResponseBody.class), examples = @ExampleObject(name = "default", value = "{\"ok\": true}")) }, responseCode = "200") @Parameters(@Parameter(name = "id", description = "ã¦ã¼ã¶ã¼ID")) @RequestBody( name = "file", description = "ã¢ãããã¼ããã¡ã¤ã«ãé¸æãã¦ãã ãã", content = @Content( mediaType = MediaType.MULTIPART_FORM_DATA, schema = @Schema(ref = "#/components/schemas/UploadFile"))) public Response postCustomUploadUserFile(@PathParam("id") String id, EntityPart file) {
ã¹ãã¼ããå®ç¾©ãã¦èªã¿è¾¼ã
å®ç¾©ã®èªã¿è¾¼ã¿ã¯ OASModelReader
ã®å®è£
ã§è¡ãã¾ãã
ã¾ãã¯ã¹ãã¼ããçæããã³ã¼ãã
ä»ã®ããã¸ã§ã¯ãã§ãåããã¨ãããã®ã§Utilã«ãã¦ãã¾ãã
/** * è¤æ°ãã¡ã¤ã«ã®ã¢ãããã¼ããå®ç¾©ããã¹ãã¼ããä½æãã¾ã. * * @param propertyName ã¹ãã¼ãã®ããããã£å * @return è¤æ°ãã¡ã¤ã«ã®ã¢ãããã¼ãããã¹ãã¼ã */ public static Schema createUploadFileSchema(String propertyName) { return OASFactory.createSchema() .description("ã¢ãããã¼ããæå®ããããã®ã¹ãã¼ãã§ã.") .type(Schema.SchemaType.OBJECT) .properties( Map.of( propertyName, OASFactory.createSchema().type(Schema.SchemaType.STRING).format("binary"))); }
ããã OASModelReader.buildModel()
ã®ä¸ã§å®ç¾©ãã¾ãã
var components = OASFactory.createComponents() .addSecurityScheme("access_token", securityScheme) .addSchema("Gender", OpenApiSchemaUtil.createEnumSchema(Gender.class)) .addSchema("Genders", OpenApiSchemaUtil.createEnumListSchema(Gender.class)) .addSchema("UploadFile", OpenApiSchemaUtil.createUploadFileSchema()) .addSchema("UploadFiles", OpenApiSchemaUtil.createUploadFileListSchema());
ãããªæãã§ç¬èªã®ã¹ãã¼ãã¯ï¼ã¤ï¼ã¤è¿½å ãã¾ã*10ã
OpenAPI-UIã¨ãã¦ã¯ä»¥ä¸ã®ããã«ãã¡ã¤ã«ãæå®ã§ãã¾ãã
è¤æ°ãã¡ã¤ã«
ã¢ããã¼ã·ã§ã³ã§ãªãã¸ã§ã¯ãã®ãªã¹ãæ§é ãå®ç¾©ãããã¨ã¯åºæ¥ã¾ããã§ããã
ããã¯ã³ã¼ãã§ã®çæä¸æã§ãã
@POST @Path("{id}/files") @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.APPLICATION_JSON) @Operation(summary = "ã¦ã¼ã¶ã¼ã«é¢é£ãããã¡ã¤ã«ãè¤æ°ã¢ãããã¼ããã¾ã") @Parameters(@Parameter(name = "id", description = "ã¦ã¼ã¶ã¼ID")) @RequestBody( name = "files", description = "ã¢ãããã¼ããã¡ã¤ã«ãé¸æãã¦ãã ãã", content = @Content( mediaType = MediaType.MULTIPART_FORM_DATA, schema = @Schema(ref = "#/components/schemas/UploadFiles"))) @APIResponse( content = { @Content( mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = BaseResponseBody.class), examples = @ExampleObject(name = "default", value = "{\"ok\": true}")) }, responseCode = "200") public Response postCustomUploadUserFiles(@PathParam("id") String id, List<EntityPart> files) {
ã¡ãªã¿ã«ããã調ã¹ã§ã¯ã¤ã¤æãã®ãã®ãè¦ã¤ãããªãã£ãã®ã§ãOpenAPIã®å®ç¾©ãè¦ã¤ã¤ãããããæãã«ãªã£ããããããããªãããªï¼ãã¨å½ã¦æ¨éè¾¼ã¿ã§è©¦ãã¦ãã£ã¦è¦ã¤ããæãã§ãã
public static Schema createUploadFileListSchema(String propertyName) { return OASFactory.createSchema() .description("ã¢ãããã¼ããè¤æ°æå®ããããã®ã¹ãã¼ãã§ã.") .type(Schema.SchemaType.OBJECT) .properties( Map.of( propertyName, OASFactory.createSchema() .type(Schema.SchemaType.ARRAY) .items( OASFactory.createSchema() .type(Schema.SchemaType.STRING) .format("binary")))); }
OpenAPI-UIã§ãè¤æ°ãã¡ã¤ã«ãæå®ã§ããããã«ãªã£ã¦ãã¾ããã
Enumã®ãªã¹ããã¯ã¨ãªãã©ã¡ã¼ã¿
Enumã¨ããªãã¸ã§ã¯ãããªã¹ãã¨ã¨ãã¦ã¯ã¨ãªãã©ã¡ã¼ã¿ã«å®ç¾©ããå ´åã¯ãã¢ããã¼ã·ã§ã³ã§ã¯å®ç¾©ã§ãã¾ããã§ããã
ãªã®ã§ãã³ã¼ãã§å¯¾å¿ï¼OASModelReader
ã§è¨å®ã§å¯¾å¿ãã¾ããã
Enumã® name
ããã®ã¾ã¾ä½¿ç¨ããå ´åã§ããã°ããã§è¯ãã¨æãã¾ãã
ããã³ã¼ãå¤ãå¤æãããããªã±ã¼ã¹ãããã°ã¤ã³ã¿ã¼ãã§ã¼ã¹ãEnumã«ã¤ãã¦ï¼ãã¨ãã° getCd
ã¿ãããªï¼èªã¿è¾¼ãããã«ããã°å®ç¾å¯è½ã§ãã
public static Schema createEnumListSchema(Class<? extends Enum<?>> enumClass) { var enums = enumClass.getEnumConstants(); return OASFactory.createSchema() .description(enumClass.getSimpleName() + "ãè¤æ°æå®ããããã®ã¹ãã¼ãã§ã.") .example(enums[0].name()) .type(Schema.SchemaType.ARRAY) .items( OASFactory.createSchema() .type(Schema.SchemaType.STRING) .enumeration(Stream.of(enums).map(Enum::name).collect(Collectors.toList()))); }
ã¡ãªã¿ã«ãªã¹ãã§ã¯ãªãEnumãã¤ãã§ã«ã¤ããã¾ããã
ãããããã° enumeration
ãåæããªãã¦è¯ããªãã¾ã*11ã
public static Schema createEnumSchema(Class<? extends Enum<?>> enumClass) { var enums = enumClass.getEnumConstants(); return OASFactory.createSchema() .description(enumClass.getSimpleName() + "ã®ã¹ãã¼ãã§ã.") .example(enums[0].name()) .type(Schema.SchemaType.STRING) .enumeration(Stream.of(enums).map(Enum::name).collect(Collectors.toList())); }
ãããã£ã¦ã¤ãã£ãã¹ãã¼ãå®ç¾©ã ref
ã§æå®ãã¾ãã
@GET @Path("enumlist") @Produces(MediaType.APPLICATION_JSON) @Operation( summary = "ã¦ã¼ã¶ã¼æ å ±ãæ¤ç´¢ãã¾ã", description = "ã¯ã¨ãªã¼ã§æå®ããæ¡ä»¶ãçµãè¾¼ã¿æ¡ä»¶ã¨ãã¦ä½¿ç¨ãã¾ãã<br>" + "æ¡ä»¶ãæå®ããªãå ´åã¯å ¨ã¬ã³ã¼ããåå¾å¯¾è±¡ã¨ãªãã¾ã") @APIResponse( content = { @Content( mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = UserResponse.UserResponseListBody.class), examples = { @ExampleObject( name = "default", value = "" + "{\n" + " \"body\": [\n" + " {\n" + " \"gender\": \"OTHER\",\n" + " \"name\": \"Name:example\",\n" + " \"id\": \"100\"\n" + " }\n" + " ],\n" + " \"ok\": true\n" + "}"), @ExampleObject( name = "return 2 record", value = "" + "{\n" + " \"body\": [\n" + " {\n" + " \"gender\": \"MALE\",\n" + " \"name\": \"Name:user name1\",\n" + " \"id\": \"1\"\n" + " },\n" + " {\n" + " \"gender\": \"OTHER\",\n" + " \"name\": \"Name:user name2\",\n" + " \"id\": \"2\"\n" + " }\n" + " ],\n" + " \"ok\": true\n" + "}") }) }, responseCode = "200") @Parameters({ @Parameter( name = "genders", description = "æ§å¥ï¼è¤æ°æå®ï¼", schema = @Schema(ref = "#/components/schemas/Genders")), @Parameter( name = "gender", description = "æ§å¥", schema = @Schema(ref = "#/components/schemas/Gender")), @Parameter( name = "name", description = "ã¦ã¼ã¶ã¼å", example = "user name", schema = @Schema(implementation = String.class)) }) public Response getUsersByQueryWithListEnum(
ãæ§å¥ï¼è¤æ°æå®ï¼ãã®é¸æè¢ã ctrlãæ¼ããªããé¸æãããè¤æ°ã®Enumå±æ§å¤ãæ¤ç´¢æ¡ä»¶ã¨ãã¦æå®ã§ãã¾ãã
Exceptionï¼2xx系以å¤ã®ã¹ãã¼ã¿ã¹ï¼
ããæ¹ã¯è²ã ã¨ããã¨æãã¾ãã
- ã¡ã½ããã«ï¼ã¤ï¼ã¤è¨è¿°ãã
OASModelReader
ã使ã£ã¦å ¨ã¡ã½ããã«ã¤ãã- ãã®èª¬æç¨ã®URLãä½ãï¼ä»åã¯ã³ã¬ï¼
ä¸è¬çã«ã¯ä¸ï¼ã¤ã®ããããã ã¨æãã®ã§ãã
- 4xxã5xx ã¯å¤ãã®å ´åãã»ã¼åãäºæ¸ãã ã
- ã¡ã½ããã®ã¬ã¹ãã³ã¹ã¨ãã¦æ³å®ã§ããHTTPã¹ãã¼ã¿ã¹ã ããæ¸ãããæ¹ããããã©ãããããã¨çµå± 5xxãªã©ã®ã·ã¹ãã åºç¤ã¨ãã¦å®ç¾©ãããã®ã®æ±ããã©ãããï¼ã¨ããã®ã¯æ®ã£ã¦ãã¾ã
- æ³å®ã§ããï¼è¨è¨ããï¼ä»¥å¤ã®ãã¿ã¼ã³ã®æç¡ã¯ãµã¼ãã¹ããæ·±ãæ次第ã§å¤ãã£ã¦ãããå ¨é¨ãControlleråä½ã§ææ¡ã§ãããã®ã§ãå®éã®ã¨ãããªãã®ã§ã¯ï¼
ã¨èãã¦ããä¸ã§ã次ã«ãå©ç¨å´ã¨ãã¦ä½ã欲ããï¼ãã¨èããã¨ãã«
- 4xxã5xxã¯å ±éãã¦ããã³ãã¨ãã¦ããã³ããªã³ã°ãããã¨ãå¤ã
- å ±éé¨åã§å¯¾å¿ãããã¨ãã«æ¥ç¶ãã¹ãããããããã®ãããã¨å¬ãã
ã¨ããããã«èãè³ããExceptionController
ãä½ãã¨ããè¨è¨ã«è½ã¡çãã¾ããã
å ¨éã¯é·ãã®ã§ä¸é¨æç²ã§ãã
@Path("/openapi-http-ng-status") @Tag(ref = "ExceptionController") @EntryPoint @SuppressWarnings("checkstyle:MissingJavadocMethod") public class ExceptionController { @SuppressWarnings("resource") @GET @Path("{httpStatus}") @Produces(MediaType.APPLICATION_JSON) @Operation( summary = "Httpã®NGã¹ãã¼ã¿ã¹ã®ã¬ã¹ãã³ã¹ä»æ§", operationId = "getHttpNgStatus", description = "" + "å¤ãã®ã±ã¼ã¹ã§ã¯2xx以å¤ã®ã¹ãã¼ã¿ã¹ã¯ExceptionHandlerãªã©ã§ä¸æ¬ãã¦æä½ãè¡ãã¾ã.<br>\n" + "åAPIæ¯ã«4xxã®ã¹ãã¼ã¿ã¹ä»æ§ãè¨è¼ããããå ±éãã¦åºåãããããªå®è£ ã追å ãããã¨ãã§ãã¾ãã<br>\n" + "åé·ãªè¨è¼ãæ¸ããããæ¬URLã«4xxããã³5xxã®APIä»æ§ã¯æ¬URLã¸éç´ãã¾ã.\n" + "ã¯ã©ã¤ã¢ã³ãã«ã¦4xx,5xxã®Httpã¹ãã¼ã¿ã¹æ¯ã®å¶å¾¡ç¢ºèªã§ã®å©ç¨ãæ³å®ãã¦ãã¾ã.") @APIResponse( content = { @Content( mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = ErrorResponse.ErrorResponseBody.class), examples = { @ExampleObject( name = "default", value = "{\n" + " \"ok\": false,\n" + " \"body\": {\n" + " \"errors\": [\n" + " {\n" + " \"message\": \"æ§æãç¡å¹ã§ã\",\n" + " \"messageCode\": \"HTTP_STATUS_BAD_REQUEST\"\n" + " }\n" + " ]\n" + " }\n" + "}") }) }, responseCode = "400")
Code
experimentation/ee10-01-openapi at openapi · vermeerlab/experimentation · GitHub
ã«ã¼ãã®ããã¸ã§ã¯ãã§
./mvnw package
ããã¦ãå ¨ããã¸ã§ã¯ãããã«ããã¦ãã
ee10-01-openapi
ããã¸ã§ã¯ãã§
./mvnw cargo:run -Ppayara
ããã¦ãã
http://localhost:8081/ee10-01-openapi/api/openapi-ui/index.html
åè
ãã¡ãã«OpenAPIã®ã¡ã¢ã¯åæ
ãããã«
Quarkusç¨ã®OpenAPI-UIã©ã¤ãã©ãªã ã£ãããSpringã ã£ããspringdoc-openapi ã ã£ããã ã¨è²ã
ã¨ããã£ã¦ã¿ã¦æã£ããã¨ããã©ã¤ãã©ãªã解æ¶ãã¦ããã¾ãã
æ¨æºä»æ§ã®ç¯çã ã¨ãã®è¾ºãã¯èªåã§ä½ãè¾¼ã¾ãªãã¨ãããªãã®ã§ããã£ã¡ã ã¨ã§ããã¿ãããªãã ãã©ãªããã¨ã¢ã¤ã¢ã¤ãããã¨ãå¤ãã£ãã§ãã
ã¨ã¯ãããå®ç¾æ¹æ³ãåãã£ã¦ããã¨é¢åãªãã¨ã¯é¢åã§ããJavaDocãæ¸ãã¦ãã代ããã ã¨å²ãåã£ã¦ããã¯å°ãè©ã®åãæããæ°ããã¾ãã
ã¨ã«ããã³ãã³ã試ãã¦ã¿ã¦ãã£ã¦ã¿ãããã¨ã¯ä¸å¿å®ç¾ããã¨ããã¾ã§ãã©ãçããã®ã¯è¯ãã£ãã§ãã
ããã«ãã¦ãEEç³»ã§OpenAPIã®è¨äºãæ¬å½ã«å°ãªããªãã¨æãã¾ããã
ãã¡ãã£ã¨ãã£ã¦ã¿ããã¯ããã®ã§ãããå®ç¨ã¬ãã«ã®ãã®ãå°ãªãã¨ãããâ¦
å人çã«ã¯WebAPIã®ãã¹ãç¨IFã¨ãã¦OpenAPI-UIã¯ä½¿ããããã®ã§ãããã¡ãã£ã¨æ®åãã¦ãã¦ãè¯ãããã«æã£ã¦ããã®ã§ããããã¹ãç³»ã¯è«è² éçºã ã¨ã¨ã¯ã»ã«ã¸ã®ã³ããã§ç´åç©ãä½ãç³»ã®WFéçºã¨ãã¦ã¯ä½è¨ãªä½æ¥ï¼çµæãã£ããã¨ã®ãã人ãå°ãªããã¿ãããªæããªãã§ãããããï¼*12
*1:ãããè¦ã¤ããããã®ããä»åã®ã¢ã¬ã³ã¬ãã£ã¦ã¿ã¦å人çã«ã¯ä¸çªã®åç©«
*2:springdoc-openapi ã¯ãã£ã¦ããã
*3:ãã¾ããªãï¼ï¼ï¼ã¿ãããªæãã§ä½¿ã£ã¦ãã¾ã
*4:å°ãªãã¨ãPayaraã§ã¯ããã®ãããã¯Quarkusã ã¨åºæ¥ããããã¿ãããªã®ã§å®è£ ä¾åãªããã§ã
*5:ãã®è¾ºãã¯ã¡ãã£ã¨èªåã§é å¼µã£ã¦ãè¯ããããããªãã¨æãã¾ãããã¾ã㯠ãªã¬ãªã¬FWçãªãã¨ãããªããã¿ãªããæ¹ã§åºæ¥ããã¨ãæ´çãã¾ã
*6:ãããã¾ããè²ã ãã£ã¦ã¿ã¦è¯ãã£ããã®ã®ï¼ã¤
*7:ããã«ã©ã¤ãã©ãªã®ã¢ãããã¼ãã§ä¸è¦ã«ãªããããããªããããªããªããããããªãããã¹ããã©ã¯ãã£ã¹ã¨ãããããããªãã¨ãè¦ã¤ããTIPSãã§ã
*8:ããã§ã¯ãããã¦ãã¿ã§å®ç¾©ããæ¸ãæ¹ã®ä¾ã¨ãã¦ã¹ãã¼ããä½æãã¦è¨å®ããã¨ããããæ¹ããã¦ãã¾ãã
*9:ã¯ãããinãå±æ§ã¯ä½ã«ä½¿ãã®ãåãã£ã¦ããªãã£ãã®ã§ãããã¹ãæ¸ãã§çæãããã®ã¨è¦æ¯ã¹ã¦ãinãå±æ§ã足ãã¦ããªããã¨ãåãã£ã¦å¯¾å¿æ¹æ³ãåããã¾ããããããè¦ã¤ãã£ã¦æ¬å½ã«è¯ãã£ã
*10:éã«ããã¨è¿½å ããªãã¨ãããªãã®ããã¡ãã£ã¨æ®å¿µã¨ãè¨ãã¾ã
*11:#/components/schemas/ ãæååã§æ¸ããªãã¨ãããªãã¨ããã®ã¯æ®ã£ã¦ãã¾ãã¾ãããã¾ãããã§ãå ±éåã§ããã ããã·ã¨ãããã¨ã§
*12:ã¦ããããã¹ããä½ããªãã®ã¨ä¼¼ã¦ããçç±ãç´åç©ãä½ãå·¥æ°ã¨ãã¦ç©ã¿ã«ãã
JAX-RSï¼Jakarta RESTful Web Servicesï¼ã®ã¡ã¢
Jakarta EE 10 - Jakarta RESTful Web Services 3.1 変更内容まとめ - A Memorandum
JAX-RS(Jakarta RESTful Web Services) 3.1.0で、Contextアノテーションの代わりにCDIが推奨されるようになっていたという話 - CLOVER🍀
JAX-RSを使って動画ファイルをダウンロードする #JAX-RS - Qiita
JAX-RSによるExcel/CSV/PDFファイルダウンロード #Java - Qiita
JAX-RSでファイルダウンロードし、終了したらファイル削除 | GWT Center
JAX-RSで複数ファイルをアップロードするには | KATSUMI KOKUZAWA'S BLOG
JAX-RSを利用して大量データを効率的に配信する方法 - エンタープライズギークス (Enterprise Geeks)