Skip to content

Commit 9607869

Browse files
committed
feat(postman): add support for postman collection
You can now use `POSTMAN_COLLECTION` as a specification type to generate a [Postman Collection][1] compliant spec. [1]: https://schema.getpostman.com/json/collection/v2.1.0/docs/index.html closes #17
1 parent 127630f commit 9607869

File tree

22 files changed

+571
-32
lines changed

22 files changed

+571
-32
lines changed

README.md

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ If you would prefer that the OpenAPI 2.0 document is in JSON format you can spec
5151
</plugin>
5252
```
5353

54-
If you would prefer that the document is created to match the OpenAPI 3.0 specification you can specify it like this:
54+
You can also generate OpenAPI 3.0 or Postman Collection specification. For instance, this would generate OpenAPI 3.0:
5555
```xml
5656
<plugin>
5757
<groupId>com.github.berkleytechnologyservices.restdocs-spec</groupId>
@@ -63,13 +63,45 @@ If you would prefer that the document is created to match the OpenAPI 3.0 specif
6363
<goal>generate</goal>
6464
</goals>
6565
<configuration>
66-
<specification>OPENAPI_V3</specification>
66+
<specification>OPENAPI_V3</specification><!-- switch this to POSTMAN_COLLECTION for Postman Collection specs -->
6767
</configuration>
6868
</execution>
6969
</executions>
7070
</plugin>
7171
```
7272

73+
Finally, you can also generate multiple formats at once using the more verbose syntax:
74+
```xml
75+
<plugin>
76+
<groupId>com.github.berkleytechnologyservices.restdocs-spec</groupId>
77+
<artifactId>restdocs-spec-maven-plugin</artifactId>
78+
<version>${restdocs-spec.version}</version>
79+
<executions>
80+
<execution>
81+
<goals>
82+
<goal>generate</goal>
83+
</goals>
84+
<configuration>
85+
<specifications>
86+
<specification>
87+
<type>OPENAPI_V2</type>
88+
</specification>
89+
<specification>
90+
<type>OPENAPI_V3</type>
91+
<format>JSON</format>
92+
</specification>
93+
<specification>
94+
<type>POSTMAN_COLLECTION</type>
95+
<filename>my-api-collection</filename>
96+
</specification>
97+
</specifications>
98+
</configuration>
99+
</execution>
100+
</executions>
101+
</plugin>
102+
```
103+
104+
73105
There are several other aspects you can optionally configure. Here is the full set of options with their default values:
74106

75107
```xml

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@
120120
<artifactId>restdocs-api-spec-openapi3-generator</artifactId>
121121
<version>${restdocs-api-spec.version}</version>
122122
</dependency>
123+
<dependency>
124+
<groupId>com.epages</groupId>
125+
<artifactId>restdocs-api-spec-postman-generator</artifactId>
126+
<version>${restdocs-api-spec.version}</version>
127+
</dependency>
123128
<dependency>
124129
<groupId>com.epages</groupId>
125130
<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
@@ -23,6 +23,10 @@
2323
<groupId>com.epages</groupId>
2424
<artifactId>restdocs-api-spec-openapi3-generator</artifactId>
2525
</dependency>
26+
<dependency>
27+
<groupId>com.epages</groupId>
28+
<artifactId>restdocs-api-spec-postman-generator</artifactId>
29+
</dependency>
2630
<dependency>
2731
<groupId>com.epages</groupId>
2832
<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
@@ -7,7 +7,8 @@
77

88
public enum Specification {
99
OPENAPI_V2("openapi-2.0", SpecificationFormat.YAML, SpecificationFormat.JSON),
10-
OPENAPI_V3("openapi-3.0", SpecificationFormat.YAML, SpecificationFormat.JSON);
10+
OPENAPI_V3("openapi-3.0", SpecificationFormat.YAML, SpecificationFormat.JSON),
11+
POSTMAN_COLLECTION("postman-collection", SpecificationFormat.JSON);
1112

1213
private final String defaultFilename;
1314
private final SpecificationFormat defaultFormat;

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
public class SpecificationGeneratorException extends Exception {
44

5+
public SpecificationGeneratorException(String message) {
6+
super(message);
7+
}
8+
59
public SpecificationGeneratorException(String message, Throwable cause) {
610
super(message, cause);
711
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
import com.berkleytechnologyservices.restdocs.spec.Scope;
55
import com.berkleytechnologyservices.restdocs.spec.Tag;
66
import com.epages.restdocs.apispec.model.Oauth2Configuration;
7+
import com.google.common.base.Strings;
78

9+
import java.net.MalformedURLException;
10+
import java.net.URL;
811
import java.util.List;
912
import java.util.Map;
1013
import java.util.stream.Collectors;
@@ -39,4 +42,15 @@ public static Map<String, String> createTagDescriptionsMap(List<Tag> tags) {
3942
return tags.stream()
4043
.collect(Collectors.toMap(Tag::getName, Tag::getDescription));
4144
}
45+
46+
public static URL createBaseUrl(String scheme, String host, String basePath) throws MalformedURLException {
47+
URL url;
48+
int indexOfColon = host.indexOf(':');
49+
if (indexOfColon > -1 && indexOfColon + 1 < host.length()) {
50+
url = new URL(scheme, host.substring(0, indexOfColon), Integer.parseInt(host.substring(indexOfColon + 1)), Strings.nullToEmpty(basePath));
51+
} else {
52+
url = new URL(scheme, host, Strings.nullToEmpty(basePath));
53+
}
54+
return url;
55+
}
4256
}

restdocs-spec-generator/src/main/java/com/berkleytechnologyservices/restdocs/spec/generator/openapi_v3/OpenApi30SpecificationGenerator.java

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.berkleytechnologyservices.restdocs.spec.ApiDetails;
44
import com.berkleytechnologyservices.restdocs.spec.Specification;
55
import com.berkleytechnologyservices.restdocs.spec.generator.SpecificationGenerator;
6+
import com.berkleytechnologyservices.restdocs.spec.generator.SpecificationGeneratorException;
67
import com.berkleytechnologyservices.restdocs.spec.generator.SpecificationGeneratorUtils;
78
import com.epages.restdocs.apispec.model.ResourceModel;
89
import com.epages.restdocs.apispec.openapi3.OpenApi3Generator;
@@ -33,19 +34,10 @@ public Specification getSpecification() {
3334
}
3435

3536
@Override
36-
public String generate(ApiDetails details, List<ResourceModel> models) {
37-
List<Server> servers = new ArrayList<>();
38-
for (String scheme : details.getSchemes()) {
39-
try {
40-
URL url = buildUrl(scheme, details.getHost(), details.getBasePath() == null ? "" : details.getBasePath());
41-
servers.add(new Server().url(url.toString()));
42-
} catch (MalformedURLException e) {
43-
throw new IllegalArgumentException("Invalid server URL", e);
44-
}
45-
}
37+
public String generate(ApiDetails details, List<ResourceModel> models) throws SpecificationGeneratorException {
4638
return generator.generateAndSerialize(
4739
models,
48-
servers,
40+
createServerList(details),
4941
details.getName(),
5042
details.getDescription(),
5143
SpecificationGeneratorUtils.createTagDescriptionsMap(details.getTags()),
@@ -55,15 +47,16 @@ public String generate(ApiDetails details, List<ResourceModel> models) {
5547
);
5648
}
5749

58-
private URL buildUrl(String scheme, String host, String basePath) throws MalformedURLException {
59-
URL url;
60-
int indexOfColon = host.indexOf(':');
61-
if (indexOfColon > -1 && indexOfColon + 1 < host.length()) {
62-
url = new URL(scheme, host.substring(0, indexOfColon), Integer.parseInt(host.substring(indexOfColon + 1)), basePath);
63-
} else {
64-
url = new URL(scheme, host, basePath);
50+
private List<Server> createServerList(ApiDetails details) throws SpecificationGeneratorException {
51+
List<Server> servers = new ArrayList<>();
52+
for (String scheme : details.getSchemes()) {
53+
try {
54+
URL url = SpecificationGeneratorUtils.createBaseUrl(scheme, details.getHost(), details.getBasePath() == null ? "" : details.getBasePath());
55+
servers.add(new Server().url(url.toString()));
56+
} catch (MalformedURLException e) {
57+
throw new SpecificationGeneratorException("Unable to build server url.", e);
58+
}
6559
}
66-
return url;
60+
return servers;
6761
}
68-
6962
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.berkleytechnologyservices.restdocs.spec.generator.postman;
2+
3+
import com.berkleytechnologyservices.restdocs.spec.ApiDetails;
4+
import com.berkleytechnologyservices.restdocs.spec.Specification;
5+
import com.berkleytechnologyservices.restdocs.spec.generator.SpecificationGenerator;
6+
import com.berkleytechnologyservices.restdocs.spec.generator.SpecificationGeneratorException;
7+
import com.berkleytechnologyservices.restdocs.spec.generator.SpecificationGeneratorUtils;
8+
import com.epages.restdocs.apispec.model.ResourceModel;
9+
import com.epages.restdocs.apispec.postman.PostmanCollectionGenerator;
10+
import com.epages.restdocs.apispec.postman.model.Collection;
11+
import com.fasterxml.jackson.core.JsonProcessingException;
12+
import com.fasterxml.jackson.databind.DeserializationFeature;
13+
import com.fasterxml.jackson.databind.ObjectMapper;
14+
import com.fasterxml.jackson.databind.SerializationFeature;
15+
import com.fasterxml.jackson.module.kotlin.KotlinModule;
16+
17+
import javax.inject.Named;
18+
import java.net.MalformedURLException;
19+
import java.util.List;
20+
21+
@Named
22+
public class PostmanCollectionSpecificationGenerator implements SpecificationGenerator {
23+
24+
private final PostmanCollectionGenerator generator;
25+
private final ObjectMapper objectMapper;
26+
27+
public PostmanCollectionSpecificationGenerator() {
28+
this(PostmanCollectionGenerator.INSTANCE, createObjectMapper());
29+
}
30+
31+
public PostmanCollectionSpecificationGenerator(PostmanCollectionGenerator generator, ObjectMapper objectMapper) {
32+
this.generator = generator;
33+
this.objectMapper = objectMapper;
34+
}
35+
36+
@Override
37+
public Specification getSpecification() {
38+
return Specification.POSTMAN_COLLECTION;
39+
}
40+
41+
@Override
42+
public String generate(ApiDetails details, List<ResourceModel> models) throws SpecificationGeneratorException {
43+
44+
Collection collection = generator.generate(
45+
models,
46+
details.getName(),
47+
details.getVersion(),
48+
createBaseUrl(details)
49+
);
50+
51+
try {
52+
return objectMapper.writeValueAsString(collection);
53+
} catch (JsonProcessingException e) {
54+
throw new SpecificationGeneratorException("Unable to generate Postman Collection specification.", e);
55+
}
56+
}
57+
58+
private String createBaseUrl(ApiDetails details) throws SpecificationGeneratorException {
59+
if (details.getSchemes().isEmpty()) {
60+
throw new SpecificationGeneratorException("You must define a scheme in order to generate a Postman Collection specification.");
61+
}
62+
63+
try {
64+
return SpecificationGeneratorUtils.createBaseUrl(details.getSchemes().get(0), details.getHost(), details.getBasePath()).toString();
65+
} catch (MalformedURLException e) {
66+
throw new SpecificationGeneratorException("Unable to build base url.", e);
67+
}
68+
}
69+
70+
private static ObjectMapper createObjectMapper() {
71+
return new ObjectMapper()
72+
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
73+
.enable(SerializationFeature.INDENT_OUTPUT)
74+
.registerModule(new KotlinModule());
75+
}
76+
77+
}

restdocs-spec-generator/src/test/java/com/berkleytechnologyservices/restdocs/spec/generator/openapi_v2/OpenApi20SpecificationGeneratorTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public void testGenerateWithDefaults() {
5959
String rawOutput = generator.generate(apiDetails, list(model));
6060

6161
assertThat(rawOutput)
62-
.isEqualToNormalizingNewlines(contentOfResource("/mock-specs/default-settings.yml"));
62+
.isEqualToNormalizingNewlines(contentOfResource("/mock-specs/openapi2/default-settings.yml"));
6363
}
6464

6565
private static String contentOfResource(String resourceName) {

restdocs-spec-generator/src/test/java/com/berkleytechnologyservices/restdocs/spec/generator/openapi_v3/OpenApi30SpecificationGeneratorTest.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,14 @@
22

33
import com.berkleytechnologyservices.restdocs.spec.ApiDetails;
44
import com.berkleytechnologyservices.restdocs.spec.Specification;
5+
import com.berkleytechnologyservices.restdocs.spec.generator.SpecificationGeneratorException;
56
import com.epages.restdocs.apispec.model.HTTPMethod;
67
import com.epages.restdocs.apispec.model.ResourceModel;
78
import com.epages.restdocs.apispec.model.Schema;
89
import org.junit.jupiter.api.Test;
910
import org.junit.jupiter.api.extension.ExtendWith;
1011
import org.mockito.junit.jupiter.MockitoExtension;
1112

12-
import java.io.IOException;
13-
import java.nio.file.Files;
14-
import java.nio.file.Paths;
15-
1613
import static com.berkleytechnologyservices.restdocs.spec.generator.test.ResourceModels.*;
1714
import static org.assertj.core.api.Assertions.assertThat;
1815
import static org.assertj.core.api.Assertions.contentOf;
@@ -30,26 +27,26 @@ public void testGetSpecification() {
3027
}
3128

3229
@Test
33-
public void testGenerateWithDefaults() {
30+
public void testGenerateWithDefaults() throws SpecificationGeneratorException {
3431

3532
ApiDetails apiDetails = new ApiDetails();
3633

3734

3835
String rawOutput = generator.generate(apiDetails, list(getMockResource()));
3936

4037
assertThat(rawOutput)
41-
.isEqualToNormalizingNewlines(contentOfResource("/mock-specs/default-settings-openapi3.yml"));
38+
.isEqualToNormalizingNewlines(contentOfResource("/mock-specs/openapi3/default-settings.yml"));
4239
}
4340

4441
@Test
45-
public void testGenerateHostWithPort() {
42+
public void testGenerateHostWithPort() throws SpecificationGeneratorException {
4643

4744
ApiDetails apiDetails = new ApiDetails().host("example.com:8080");
4845

4946
String rawOutput = generator.generate(apiDetails, list(getMockResource()));
5047

5148
assertThat(rawOutput)
52-
.isEqualToNormalizingNewlines(contentOfResource("/mock-specs/host-with-port-openapi3.yml"));
49+
.isEqualToNormalizingNewlines(contentOfResource("/mock-specs/openapi3/host-with-port.yml"));
5350
}
5451

5552
private ResourceModel getMockResource() {

0 commit comments

Comments
 (0)