Skip to content

Commit ea33f08

Browse files
committed
add support for generating OpenAPI 3.0 specifications
1 parent 41ced03 commit ea33f08

File tree

8 files changed

+224
-22
lines changed

8 files changed

+224
-22
lines changed

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@
119119
<artifactId>restdocs-api-spec-openapi-generator</artifactId>
120120
<version>${restdocs-api-spec.version}</version>
121121
</dependency>
122+
<dependency>
123+
<groupId>com.epages</groupId>
124+
<artifactId>restdocs-api-spec-openapi3-generator</artifactId>
125+
<version>${restdocs-api-spec.version}</version>
126+
</dependency>
122127
<dependency>
123128
<groupId>com.epages</groupId>
124129
<artifactId>restdocs-api-spec-model</artifactId>

restdocs-spec-generator/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
<groupId>com.epages</groupId>
2020
<artifactId>restdocs-api-spec-openapi-generator</artifactId>
2121
</dependency>
22+
<dependency>
23+
<groupId>com.epages</groupId>
24+
<artifactId>restdocs-api-spec-openapi3-generator</artifactId>
25+
</dependency>
2226
<dependency>
2327
<groupId>com.epages</groupId>
2428
<artifactId>restdocs-api-spec-model</artifactId>

restdocs-spec-generator/src/main/java/com/berkleytechnologyservices/restdocs/spec/Specification.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
import java.util.Set;
77

88
public enum Specification {
9-
OPENAPI_V2("openapi-2.0", SpecificationFormat.YAML, SpecificationFormat.JSON);
9+
OPENAPI_V2("openapi-2.0", SpecificationFormat.YAML, SpecificationFormat.JSON),
10+
OPENAPI_V3("openapi-3.0", SpecificationFormat.YAML, SpecificationFormat.JSON);
1011

1112
private final String defaultFilename;
1213
private final SpecificationFormat defaultFormat;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.berkleytechnologyservices.restdocs.spec.generator;
2+
3+
import com.berkleytechnologyservices.restdocs.spec.AuthConfig;
4+
import com.berkleytechnologyservices.restdocs.spec.Scope;
5+
import com.epages.restdocs.apispec.model.Oauth2Configuration;
6+
7+
import java.util.stream.Collectors;
8+
9+
public class SpecificationGeneratorUtils {
10+
11+
private SpecificationGeneratorUtils() {
12+
}
13+
14+
public static Oauth2Configuration createOauth2Configuration(AuthConfig authConfig) {
15+
16+
Oauth2Configuration oauth2Configuration = null;
17+
18+
if (authConfig.getTokenUrl() != null && authConfig.getAuthorizationUrl() != null) {
19+
oauth2Configuration = new Oauth2Configuration();
20+
oauth2Configuration.setTokenUrl(authConfig.getTokenUrl());
21+
oauth2Configuration.setAuthorizationUrl(authConfig.getAuthorizationUrl());
22+
oauth2Configuration.setFlows(authConfig.getFlows().toArray(new String[0]));
23+
24+
if (!authConfig.getScopes().isEmpty()) {
25+
oauth2Configuration.setScopes(
26+
authConfig.getScopes().stream()
27+
.collect(Collectors.toMap(Scope::getName, Scope::getDescription))
28+
);
29+
}
30+
}
31+
32+
return oauth2Configuration;
33+
}
34+
}

restdocs-spec-generator/src/main/java/com/berkleytechnologyservices/restdocs/spec/generator/openapi_v2/OpenApi20SpecificationGenerator.java

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.berkleytechnologyservices.restdocs.spec.Scope;
66
import com.berkleytechnologyservices.restdocs.spec.Specification;
77
import com.berkleytechnologyservices.restdocs.spec.generator.SpecificationGenerator;
8+
import com.berkleytechnologyservices.restdocs.spec.generator.SpecificationGeneratorUtils;
89
import com.epages.restdocs.apispec.model.Oauth2Configuration;
910
import com.epages.restdocs.apispec.model.ResourceModel;
1011
import com.epages.restdocs.apispec.openapi2.OpenApi20Generator;
@@ -40,29 +41,9 @@ public String generate(ApiDetails details, List<ResourceModel> models) {
4041
details.getSchemes(),
4142
details.getName(),
4243
details.getVersion(),
43-
createOauth2Configuration(details.getAuthConfig()),
44+
SpecificationGeneratorUtils.createOauth2Configuration(details.getAuthConfig()),
4445
details.getFormat().name().toLowerCase()
4546
);
4647
}
4748

48-
private static Oauth2Configuration createOauth2Configuration(AuthConfig authConfig) {
49-
50-
Oauth2Configuration oauth2Configuration = null;
51-
52-
if (authConfig.getTokenUrl() != null && authConfig.getAuthorizationUrl() != null) {
53-
oauth2Configuration = new Oauth2Configuration();
54-
oauth2Configuration.setTokenUrl(authConfig.getTokenUrl());
55-
oauth2Configuration.setAuthorizationUrl(authConfig.getAuthorizationUrl());
56-
oauth2Configuration.setFlows(authConfig.getFlows().toArray(new String[0]));
57-
58-
if (!authConfig.getScopes().isEmpty()) {
59-
oauth2Configuration.setScopes(
60-
authConfig.getScopes().stream()
61-
.collect(Collectors.toMap(Scope::getName, Scope::getDescription))
62-
);
63-
}
64-
}
65-
66-
return oauth2Configuration;
67-
}
6849
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.berkleytechnologyservices.restdocs.spec.generator.openapi_v3;
2+
3+
import com.berkleytechnologyservices.restdocs.spec.ApiDetails;
4+
import com.berkleytechnologyservices.restdocs.spec.AuthConfig;
5+
import com.berkleytechnologyservices.restdocs.spec.Scope;
6+
import com.berkleytechnologyservices.restdocs.spec.Specification;
7+
import com.berkleytechnologyservices.restdocs.spec.generator.SpecificationGenerator;
8+
import com.berkleytechnologyservices.restdocs.spec.generator.SpecificationGeneratorUtils;
9+
import com.epages.restdocs.apispec.model.Oauth2Configuration;
10+
import com.epages.restdocs.apispec.model.ResourceModel;
11+
import com.epages.restdocs.apispec.openapi3.OpenApi3Generator;
12+
import io.swagger.v3.oas.models.servers.Server;
13+
14+
import javax.inject.Named;
15+
import java.net.MalformedURLException;
16+
import java.net.URL;
17+
import java.util.ArrayList;
18+
import java.util.Arrays;
19+
import java.util.List;
20+
import java.util.stream.Collectors;
21+
22+
@Named
23+
public class OpenApi30SpecificationGenerator implements SpecificationGenerator {
24+
25+
private final OpenApi3Generator generator;
26+
27+
public OpenApi30SpecificationGenerator() {
28+
this(OpenApi3Generator.INSTANCE);
29+
}
30+
31+
public OpenApi30SpecificationGenerator(OpenApi3Generator generator) {
32+
this.generator = generator;
33+
}
34+
35+
@Override
36+
public Specification getSpecification() {
37+
return Specification.OPENAPI_V3;
38+
}
39+
40+
@Override
41+
public String generate(ApiDetails details, List<ResourceModel> models) {
42+
List<Server> servers = new ArrayList<>();
43+
for (String scheme : details.getSchemes()) {
44+
try {
45+
URL url = new URL(scheme, details.getHost(), details.getBasePath() == null ? "" : details.getBasePath());
46+
servers.add(new Server().url(url.toString()));
47+
} catch (MalformedURLException e) {
48+
throw new IllegalArgumentException("Invalid server URL", e);
49+
}
50+
}
51+
return generator.generateAndSerialize(
52+
models,
53+
servers,
54+
details.getName(),
55+
details.getVersion(),
56+
SpecificationGeneratorUtils.createOauth2Configuration(details.getAuthConfig()),
57+
details.getFormat().name().toLowerCase()
58+
);
59+
}
60+
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.berkleytechnologyservices.restdocs.spec.generator.openapi_v3;
2+
3+
import com.berkleytechnologyservices.restdocs.spec.ApiDetails;
4+
import com.berkleytechnologyservices.restdocs.spec.Specification;
5+
import com.berkleytechnologyservices.restdocs.spec.generator.openapi_v2.OpenApi20SpecificationGenerator;
6+
import com.epages.restdocs.apispec.model.HTTPMethod;
7+
import com.epages.restdocs.apispec.model.ResourceModel;
8+
import org.junit.jupiter.api.Test;
9+
import org.junit.jupiter.api.extension.ExtendWith;
10+
import org.mockito.junit.jupiter.MockitoExtension;
11+
12+
import java.io.IOException;
13+
import java.nio.file.Files;
14+
import java.nio.file.Paths;
15+
16+
import static com.berkleytechnologyservices.restdocs.spec.generator.test.ResourceModels.*;
17+
import static org.assertj.core.api.Assertions.assertThat;
18+
import static org.assertj.core.api.Assertions.contentOf;
19+
import static org.assertj.core.util.Lists.emptyList;
20+
import static org.assertj.core.util.Lists.list;
21+
22+
@ExtendWith(MockitoExtension.class)
23+
public class OpenApi30SpecificationGeneratorTest {
24+
25+
private final OpenApi30SpecificationGenerator generator = new OpenApi30SpecificationGenerator();
26+
27+
@Test
28+
public void testGetSpecification() {
29+
assertThat(generator.getSpecification()).isEqualTo(Specification.OPENAPI_V3);
30+
}
31+
32+
@Test
33+
public void testGenerateWithDefaults() {
34+
35+
ApiDetails apiDetails = new ApiDetails();
36+
37+
ResourceModel model = resource(
38+
"book-get",
39+
"Get a book by id",
40+
"book",
41+
request(
42+
"/book/{id}",
43+
HTTPMethod.GET,
44+
list(
45+
requiredParam("id", "The unique identifier for the book.", "NUMBER")
46+
),
47+
emptyList()
48+
),
49+
response(
50+
200,
51+
"application/hal+json",
52+
list(),
53+
list(
54+
field("title", "Title of the book", "STRING"),
55+
field("author", "Author of the book", "STRING"),
56+
field("pages", "Number of pages in the book", "NUMBER")
57+
),
58+
"The example response.",
59+
"type: string"
60+
61+
)
62+
);
63+
64+
String rawOutput = generator.generate(apiDetails, list(model));
65+
66+
assertThat(rawOutput)
67+
.isEqualToNormalizingNewlines(contentOfResource("/mock-specs/default-settings-openapi3.yml"));
68+
}
69+
70+
private static String contentOfResource(String resourceName) {
71+
return contentOf(OpenApi30SpecificationGeneratorTest.class.getResource(resourceName));
72+
}
73+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
openapi: 3.0.1
2+
info:
3+
title: API Documentation
4+
version: 1.0.0
5+
servers:
6+
- url: http://localhost
7+
paths:
8+
/book/{id}:
9+
get:
10+
tags:
11+
- book
12+
summary: Get a book by id
13+
description: Get a book by id
14+
operationId: book-get
15+
parameters:
16+
- name: id
17+
in: path
18+
description: The unique identifier for the book.
19+
required: true
20+
responses:
21+
200:
22+
description: "200"
23+
content:
24+
application/hal+json:
25+
schema:
26+
$ref: '#/components/schemas/book-id328345809'
27+
examples:
28+
book-get:
29+
value: The example response.
30+
components:
31+
schemas:
32+
book-id328345809:
33+
type: object
34+
properties:
35+
pages:
36+
type: number
37+
description: Number of pages in the book
38+
author:
39+
type: string
40+
description: Author of the book
41+
title:
42+
type: string
43+
description: Title of the book

0 commit comments

Comments
 (0)