diff --git a/README.MD b/README.MD index 8d714f3..a0cf452 100644 --- a/README.MD +++ b/README.MD @@ -15,7 +15,10 @@ requirements/needs/gaps in your documentation. Developers are writing unit and integration tests on their projects, and a good test could be providing the different input and output values for a component. API developers are writing tons of integration tests for the different use cases and it happens sometimes that they are not giving out these tests, because of course security and privacy rules, but some output of these tests could be used to extend the already existing API. -[Spring's REST Docs](https://github.com/spring-projects/spring-restdocs) does something similar, but it is not heavily involved in the OpenAPI initiative. +## Alternatives + +- [Spring's REST Docs](https://github.com/spring-projects/spring-restdocs) does something similar, but it is not heavily involved in the OpenAPI initiative. +- [Spring REST Docs API specification Integration](https://github.com/ePages-de/restdocs-api-spec) kinda the wanted features, but it is totally test driven. ## Implementation @@ -28,23 +31,46 @@ The project is split up into multiple modules to make sure the end users are not ## Usage +### Generate resources with tests +
- Spring Boot based example + Spring Boot SpringDoc based example ```java @SpringBootApplication -class SpringBootOpenmapiWithTestApplication { +class SpringBootOpenapiWithTestApplication { public static void main(String[] args) { SpringApplication.run(SpringBootOpenmapiWithTestApplication.class, args); } + /** + * Bean to initialize the resources. + */ + @Bean + public ExampleResourceReaderBean exampleResourceReaderBean() { + ExampleResourceReaderBean exampleResourceReaderBean = new ExampleResourceReaderBean(); + exampleResourceReaderBean.initializeResources(); + return exampleResourceReaderBean; + } + + /** + * Operation customizer. + */ @Bean public ApiResponseAndExampleCustomizer customizer() { return new ApiResponseAndExampleCustomizer(); } + /** + * OpenAPI object customizer bean. + */ + @Bean + public OpenApiCustomiser openApiCustomiser() { + return new OpenApiExampleExtenderCustomizer(); + } + } @@ -184,10 +210,14 @@ class UserControllerTest { ``` -After running: `./mvnw package` the following generated classes will pop up: +After running: `./mvnw package` the following generated resources will pop up: ![generated-files.png](docs/generated-files.png) -**requests/postUser/201_Creates a user.json**: +By default, the generated files are going to be created to the following paths: +- `target/classes/openapi-extender/requests/` +- `target/classes/openapi-extender/responses/` + +**requests/postUser_201_Creates a user.json**: ```json { "username": "new-bob", @@ -197,7 +227,7 @@ After running: `./mvnw package` the following generated classes will pop up: } ``` -**responses/postUser/201_Successful operation.json**: +**responses/postUser_201_Successful operation.json**: ```json { "username" : "joe", @@ -205,7 +235,7 @@ After running: `./mvnw package` the following generated classes will pop up: } ``` -**responses/postUser/422_When passwords does not match.json**: +**responses/postUser_422_When passwords does not match.json**: ```json { "message" : "Passwords must match", @@ -231,20 +261,20 @@ info: title: OpenAPI definition version: v0 servers: -- url: http://localhost:8080 - description: Generated server url + - url: http://localhost:8080 + description: Generated server url paths: /user: get: tags: - - user-controller + - user-controller operationId: getUser parameters: - - name: id - in: query - required: false - schema: - type: string + - name: id + in: query + required: false + schema: + type: string responses: "200": description: OK @@ -254,55 +284,41 @@ paths: type: string examples: Standard response: - description: Standard response - value: |- - { - "username" : "joe", - "fullName" : "Joe Big" - } + description: "" + $ref: '#/components/examples/response_getUser_application_json_Standard + response' application/xml: schema: type: string examples: Standard response: - description: Standard response - value: |- - { - "username" : "joe", - "fullName" : "Joe Big" - } - "500": + description: "" + $ref: '#/components/examples/response_getUser_application_json_Standard + response' + "400": content: application/json: examples: - When coupon code does not exist: - description: When coupon code does not exist - value: |- - { - "message" : "Internal Server Error BAD2", - "cause" : "Bad because internal" - } - When server explodes: - description: When server explodes - value: |- - { - "message" : "Internal Server Error BAD2", - "cause" : "Bad because internal" - } - "400": + When bad thing happens: + description: Happens because the id is BAD and it is code to fail + in these cases + $ref: '#/components/examples/response_getUser_application_json_When + bad thing happens' + "500": content: application/json: examples: - When bad thing happens: - description: When bad thing happens - value: |- - { - "message" : "Bad BAD", - "cause" : "Cause it went bad" - } + When coupon code does not exist: + description: "" + $ref: '#/components/examples/response_getUser_application_json_When + coupon code does not exist' + When server explodes: + description: "" + $ref: '#/components/examples/response_getUser_application_json_When + server explodes' post: tags: - - user-controller + - user-controller operationId: postUser requestBody: content: @@ -310,33 +326,18 @@ paths: schema: $ref: '#/components/schemas/UserRequest' examples: - Will throw error - Returns:422: - description: Will throw error - Returns:422 - value: |- - { - "username" : "alex123", - "password" : "password123", - "passwordConfirmation" : "password12", - "fullName" : "Alex King" - } - Will throw error because user already exist - Returns:422: - description: Will throw error because user already exist - Returns:422 - value: |- - { - "username" : "bob", - "password" : "password123", - "passwordConfirmation" : "password123", - "fullName" : "Bob Sug" - } - Creates a user - Returns:201: - description: Creates a user - Returns:201 - value: |- - { - "username" : "new-bob", - "password" : "password123", - "passwordConfirmation" : "password123", - "fullName" : "Bob Sug" - } + Creates a user: + description: "Returns: HTTP 201" + $ref: '#/components/examples/request_postUser_application_json_Creates + a user' + Will throw error because user already exist: + description: "Returns: HTTP 422" + $ref: '#/components/examples/request_postUser_application_json_Will + throw error because user already exist' + Will throw error: + description: "Returns: HTTP 422" + $ref: '#/components/examples/request_postUser_application_json_Will + throw error' required: true responses: "200": @@ -353,39 +354,27 @@ paths: application/json: examples: Successful operation: - description: Successful operation - value: |- - { - "username" : "joe", - "fullName" : "Joe Big" - } + description: "" + $ref: '#/components/examples/response_postUser_application_json_Successful + operation' application/xml: examples: Successful operation: - description: Successful operation - value: | - - joe - Joe Big - + description: "" + $ref: '#/components/examples/response_postUser_application_xml_Successful + operation' "422": content: application/json: examples: When passwords does not match: - description: When passwords does not match - value: |- - { - "message" : "Passwords must match", - "cause" : "Cause it went bad" - } + description: "" + $ref: '#/components/examples/response_postUser_application_json_When + passwords does not match' When username already exist: - description: When username already exist - value: |- - { - "message" : "Username already exists", - "cause" : "Cause it went bad" - } + description: "" + $ref: '#/components/examples/response_postUser_application_json_When + username already exist' components: schemas: UserRequest: @@ -399,5 +388,89 @@ components: type: string fullName: type: string + examples: + request_postUser_application_json_Creates a user: + description: "" + value: |- + { + "username" : "new-bob", + "password" : "password123", + "passwordConfirmation" : "password123", + "fullName" : "Bob Sug" + } + response_postUser_application_json_Successful operation: + description: "" + value: |- + { + "username" : "joe", + "fullName" : "Joe Big" + } + response_getUser_application_json_When coupon code does not exist: + description: "" + value: |- + { + "message" : "Internal Server Error BAD2", + "cause" : "Bad because internal" + } + response_getUser_application_json_When server explodes: + description: "" + value: |- + { + "message" : "Internal Server Error BAD2", + "cause" : "Bad because internal" + } + response_getUser_application_json_When bad thing happens: + description: Happens because the id is BAD and it is code to fail in these cases + value: |- + { + "message" : "Bad BAD", + "cause" : "Cause it went bad" + } + request_postUser_application_json_Will throw error: + description: "" + value: |- + { + "username" : "alex123", + "password" : "password123", + "passwordConfirmation" : "password12", + "fullName" : "Alex King" + } + response_postUser_application_xml_Successful operation: + description: "" + value: | + + joe + Joe Big + + response_postUser_application_json_When username already exist: + description: "" + value: |- + { + "message" : "Username already exists", + "cause" : "Cause it went bad" + } + request_postUser_application_json_Will throw error because user already exist: + description: "" + value: |- + { + "username" : "bob", + "password" : "password123", + "passwordConfirmation" : "password123", + "fullName" : "Bob Sug" + } + response_postUser_application_json_When passwords does not match: + description: "" + value: |- + { + "message" : "Passwords must match", + "cause" : "Cause it went bad" + } + response_getUser_application_json_Standard response: + description: "" + value: |- + { + "username" : "joe", + "fullName" : "Joe Big" + } ```
diff --git a/docs/generated-files.png b/docs/generated-files.png index b84d168..22bc484 100644 Binary files a/docs/generated-files.png and b/docs/generated-files.png differ diff --git a/docs/index.adoc b/docs/index.adoc new file mode 100644 index 0000000..128eb68 --- /dev/null +++ b/docs/index.adoc @@ -0,0 +1,27 @@ += OpenAPI Extender Documentation +:stylesheet: rodnansol.css +:doctype: book +:toc: left + +include::project-overview.adoc[] + +== Motivation + +== Goals + +== Flows and logic +[plantuml, target=output-file-name, format=png] +.... +@startuml +class BlockProcessor +class DiagramBlock +class DitaaBlock +class PlantUmlBlock + +BlockProcessor <|-- DiagramBlock +DiagramBlock <|-- DitaaBlock +DiagramBlock <|-- PlantUmlBlock +@enduml +.... + + diff --git a/docs/my-theme.yml b/docs/my-theme.yml new file mode 100644 index 0000000..ea41a65 --- /dev/null +++ b/docs/my-theme.yml @@ -0,0 +1,26 @@ +extends: base +page: + layout: portrait + margin: [0.75in, 1in, 0.75in, 1in] + size: Letter +base: + font-color: #333333 + font-family: Times-Roman + font-size: 12 + line-height-length: 17 + line-height: $base-line-height-length / $base-font-size +role: + removed: + font-style: italic + text-decoration: line-through + text-decoration-color: #FF0000 +heading: + font-color: #262626 + font-size: 17 + font-style: bold + line-height: 1.2 + margin-bottom: 10 +link: + font-color: #002FA7 +list: + indent: $base-font-size * 1.5 diff --git a/docs/project-overview.adoc b/docs/project-overview.adoc new file mode 100644 index 0000000..71a3b16 --- /dev/null +++ b/docs/project-overview.adoc @@ -0,0 +1 @@ +== Project overview diff --git a/docs/rodnansol.css b/docs/rodnansol.css new file mode 100644 index 0000000..a4ebd5e --- /dev/null +++ b/docs/rodnansol.css @@ -0,0 +1,108 @@ +@import url(//fonts.googleapis.com/css?family=Montserrat|Open+Sans); +@import url(https://cdn.jsdelivr.net/gh/asciidoctor/asciidoctor@2.0/data/stylesheets/asciidoctor-default.css); /* Default asciidoc style framework - important */ + +:root { + --rodnansol-color: #a275f4; + --maincolor: #FFFFFF; + --primarycolor: #a275f4; + --secondarycolor: #7d75f4; + --tertiarycolor: #CCCCCC; + --highlightcolor: #e59728; + --sidebarbackground: #12151f; + --linkcolor: #0D47A1; + --linkcoloralternate: #db3279; + --white: #FFFFFF; + --black: #000000; +} + +/* Text styles */ + +body { + font-family: "Open Sans", sans-serif; + background-color: var(--maincolor); + color: var(--black); +} + +h1 { + color: var(--primarycolor) !important; + font-family: "Montserrat", sans-serif; +} + +h2, h3, h4, h5, h6 { + color: var(--secondarycolor) !important; + font-family: "Montserrat", sans-serif; +} + +.title { + color: var(--black) !important; + font-family: "Open Sans", sans-serif; + font-style: normal; + font-weight: normal; +} + +a { + text-decoration: none; +} + +p { + font-family: "Open Sans", sans-serif ! important +} + +#toc.toc2 a:link { + color: var(--linkcolor); +} + +blockquote { + color: var(--linkcoloralternate) !important +} + +.quoteblock blockquote:before { + color: var(--linkcoloralternate) +} + +code { + color: var(--white); + background-color: var(--highlightcolor) !important +} + +mark { + background-color: var(--highlightcolor) +} + +th { + background-color: var(--maincolor); + color: var(--black) !important; +} + +td { + background-color: var(--maincolor); + color: var(--black) !important +} + + +#toc.toc2 { + background-color: var(--sidebarbackground) !important; +} + +#toc.toc2 a { + color: var(--white) +} + +#toctitle { + color: var(--rodnansol-color); +} + +video { + max-width: 100%; +} + +@media all and (max-width: 600px) { + table { + width: 55vw !important; + font-size: 3vw; + } +} + +.sect1 { + color: var(--rodnansol-color) +} diff --git a/samples/spring-boot-openapi-with-test/src/main/java/com/example/springbootopenmapiwithtest/SpringBootOpenmapiWithTestApplication.java b/samples/spring-boot-openapi-with-test/src/main/java/com/example/springbootopenmapiwithtest/SpringBootOpenapiWithTestApplication.java similarity index 78% rename from samples/spring-boot-openapi-with-test/src/main/java/com/example/springbootopenmapiwithtest/SpringBootOpenmapiWithTestApplication.java rename to samples/spring-boot-openapi-with-test/src/main/java/com/example/springbootopenmapiwithtest/SpringBootOpenapiWithTestApplication.java index 23fbfdb..7b4a964 100644 --- a/samples/spring-boot-openapi-with-test/src/main/java/com/example/springbootopenmapiwithtest/SpringBootOpenmapiWithTestApplication.java +++ b/samples/spring-boot-openapi-with-test/src/main/java/com/example/springbootopenmapiwithtest/SpringBootOpenapiWithTestApplication.java @@ -9,12 +9,15 @@ import org.springframework.context.annotation.Bean; @SpringBootApplication -public class SpringBootOpenmapiWithTestApplication { +public class SpringBootOpenapiWithTestApplication { public static void main(String[] args) { - SpringApplication.run(SpringBootOpenmapiWithTestApplication.class, args); + SpringApplication.run(SpringBootOpenapiWithTestApplication.class, args); } + /** + * Bean to initialize the resources. + */ @Bean public ExampleResourceReaderBean exampleResourceReaderBean() { ExampleResourceReaderBean exampleResourceReaderBean = new ExampleResourceReaderBean(); @@ -22,11 +25,17 @@ public ExampleResourceReaderBean exampleResourceReaderBean() { return exampleResourceReaderBean; } + /** + * Operation customizer. + */ @Bean public ApiResponseAndExampleCustomizer customizer() { return new ApiResponseAndExampleCustomizer(); } + /** + * OpenAPI object customizer bean. + */ @Bean public OpenApiCustomiser openApiCustomiser() { return new OpenApiExampleExtenderCustomizer();