Skip to content

Commit

Permalink
Add YAML support for WebMVC
Browse files Browse the repository at this point in the history
  • Loading branch information
neiser committed Dec 6, 2020
1 parent dc89687 commit 8d47430
Show file tree
Hide file tree
Showing 22 changed files with 246 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@

package de.qaware.openapigeneratorforspring.common.supplier;

import com.fasterxml.jackson.core.JsonProcessingException;
import de.qaware.openapigeneratorforspring.model.OpenApi;


@FunctionalInterface
public interface OpenApiYamlMapper {
String map(OpenApi openApi);
String map(OpenApi openApi) throws JsonProcessingException;
}
5 changes: 5 additions & 0 deletions openapi-generator-for-spring-autoconfigure/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
<artifactId>validation-api</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,22 @@

package de.qaware.openapigeneratorforspring.autoconfigure;

import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import de.qaware.openapigeneratorforspring.common.annotation.AnnotationsSupplierFactory;
import de.qaware.openapigeneratorforspring.common.supplier.DefaultOpenAPIDefinitionAnnotationSupplier;
import de.qaware.openapigeneratorforspring.common.supplier.DefaultOpenApiObjectMapperSupplier;
import de.qaware.openapigeneratorforspring.common.supplier.DefaultOpenApiServersSupplier;
import de.qaware.openapigeneratorforspring.common.supplier.DefaultOpenApiSpringBootApplicationAnnotationsSupplier;
import de.qaware.openapigeneratorforspring.common.supplier.DefaultOpenApiSpringBootApplicationClassSupplier;
import de.qaware.openapigeneratorforspring.common.supplier.DefaultOpenApiYamlMapper;
import de.qaware.openapigeneratorforspring.common.supplier.OpenAPIDefinitionAnnotationSupplier;
import de.qaware.openapigeneratorforspring.common.supplier.OpenApiBaseUriSupplier;
import de.qaware.openapigeneratorforspring.common.supplier.OpenApiObjectMapperSupplier;
import de.qaware.openapigeneratorforspring.common.supplier.OpenApiServersSupplier;
import de.qaware.openapigeneratorforspring.common.supplier.OpenApiSpringBootApplicationAnnotationsSupplier;
import de.qaware.openapigeneratorforspring.common.supplier.OpenApiSpringBootApplicationClassSupplier;
import de.qaware.openapigeneratorforspring.common.supplier.OpenApiYamlMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;

Expand Down Expand Up @@ -72,4 +76,10 @@ public OpenApiServersSupplier defaultOpenApiServersSupplier(
) {
return new DefaultOpenApiServersSupplier(openApiBaseUriSupplier);
}

@ConditionalOnClass(YAMLFactory.class)
@Bean
public OpenApiYamlMapper defaultOpenApiYamlMapper() {
return new DefaultOpenApiYamlMapper();
}
}
5 changes: 5 additions & 0 deletions openapi-generator-for-spring-common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>javax.validation</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*-
* #%L
* OpenAPI Generator for Spring Boot :: Common
* %%
* Copyright (C) 2020 QAware GmbH
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/

package de.qaware.openapigeneratorforspring.common.supplier;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import de.qaware.openapigeneratorforspring.model.OpenApi;

import static com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature.MINIMIZE_QUOTES;
import static com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature.WRITE_DOC_START_MARKER;

public class DefaultOpenApiYamlMapper implements OpenApiYamlMapper {

private static final ObjectMapper YAML_MAPPER = new YAMLMapper(new YAMLFactory())
.configure(MINIMIZE_QUOTES, true)
.configure(WRITE_DOC_START_MARKER, false)
.setSerializationInclusion(JsonInclude.Include.NON_NULL);


@Override
public String map(OpenApi openApi) throws JsonProcessingException {
return YAML_MAPPER.writeValueAsString(openApi);
}
}
5 changes: 5 additions & 0 deletions openapi-generator-for-spring-starter/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
<groupId>de.qaware.tools.openapi-generator-for-spring</groupId>
<artifactId>openapi-generator-for-spring-ui</artifactId>
</dependency>
<!-- By default, offer YAML support. Library users can exclude this dependency if desired -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
5 changes: 5 additions & 0 deletions openapi-generator-for-spring-test/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@
<artifactId>validation-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<scope>test</scope>
</dependency>
</dependencies>


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;

import java.util.function.Function;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
Expand All @@ -19,10 +23,18 @@ public abstract class AbstractOpenApiGeneratorWebMvcBaseIntTest {
@Autowired
protected MockMvc mockMvc;

protected ResultActions performApiDocsRequest(Function<MockHttpServletRequestBuilder, MockHttpServletRequestBuilder> requestModifier) throws Exception {
return mockMvc.perform(requestModifier.apply(get("/v3/api-docs")));
}

protected static void assertResponseBodyMatchesOpenApiJson(String expectedJsonFile, ResultActions performResult) throws Exception {
OpenApiJsonIntegrationTestUtils.assertMatchesOpenApiJson(expectedJsonFile, () -> getResponseBodyAsString(performResult));
}

protected static void assertResponseBodyMatchesOpenApiYaml(String expectedYamlFile, ResultActions performResult) throws Exception {
OpenApiJsonIntegrationTestUtils.assertMatchesOpenApiYaml(expectedYamlFile, () -> getResponseBodyAsString(performResult));
}

protected static String getResponseBodyAsString(ResultActions performResult) throws Exception {
MvcResult mvcResult = performResult
.andExpect(status().isOk())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import org.junit.Test;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;

public abstract class AbstractOpenApiGeneratorWebMvcIntTest extends AbstractOpenApiGeneratorWebMvcBaseIntTest {

private final String expectedJsonFile;
Expand All @@ -14,6 +12,6 @@ protected AbstractOpenApiGeneratorWebMvcIntTest(String expectedJsonFile) {

@Test
public void getOpenApiAsJson() throws Exception {
assertResponseBodyMatchesOpenApiJson(expectedJsonFile, mockMvc.perform(get("/v3/api-docs")));
assertResponseBodyMatchesOpenApiJson(expectedJsonFile, performApiDocsRequest(x -> x));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,41 @@
import lombok.NoArgsConstructor;
import org.apache.commons.io.IOUtils;
import org.skyscreamer.jsonassert.JSONAssert;
import org.yaml.snakeyaml.Yaml;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.Callable;

import static org.assertj.core.api.Assertions.assertThat;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class OpenApiJsonIntegrationTestUtils {
private static final String RESOURCE_PATH_PREFIX = "/openApiJson/";
private static final String JSON_PATH_PREFIX = "/openApiJson/";
private static final String YAML_PATH_PREFIX = "/openApiYaml/";

public static void assertMatchesOpenApiJson(String expectedJsonFile, Callable<String> jsonSupplier) throws Exception {
String expectedJson = readOpenApiJsonFile(expectedJsonFile);
String expectedJson = readFileAsString(JSON_PATH_PREFIX + expectedJsonFile);
String actualJson = jsonSupplier.call();
try {
JSONAssert.assertEquals(expectedJson, actualJson, true);
} catch (AssertionError e) {
throw new AssertionError(e.getMessage() + "\n\n Actual JSON: " + actualJson, e);
throw new AssertionError(e.getMessage() + "\n\n Actual JSON:\n" + actualJson, e);
}
}

public static String readOpenApiJsonFile(String jsonFile) throws IOException {
return readFileAsString(RESOURCE_PATH_PREFIX + jsonFile);
public static void assertMatchesOpenApiYaml(String expectedJsonFile, Callable<String> yamlSupplier) throws Exception {
Yaml yaml = new Yaml();
Map<String, Object> expectedYaml = yaml.load(readFileAsString(YAML_PATH_PREFIX + expectedJsonFile));
String actualYamlString = yamlSupplier.call();
Map<String, Object> actualYaml = yaml.load(actualYamlString);
try {
assertThat(actualYaml).isEqualTo(expectedYaml);
} catch (AssertionError e) {
throw new AssertionError(e.getMessage() + "\n\n Actual YAML:\n" + actualYamlString, e);
}
}

private static String readFileAsString(String path) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,19 @@
import de.qaware.openapigeneratorforspring.test.AbstractOpenApiGeneratorWebMvcBaseIntTest;
import org.junit.Test;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;

public class App10Test extends AbstractOpenApiGeneratorWebMvcBaseIntTest {

@Test
public void testWithQueryParam() throws Exception {
assertResponseBodyMatchesOpenApiJson("app10_admin.json",
mockMvc.perform(get("/v3/api-docs").queryParam("pathPrefix", "/admin"))
performApiDocsRequest(x -> x.queryParam("pathPrefix", "/admin"))
);
}

@Test
public void testWithHeaderParam() throws Exception {
assertResponseBodyMatchesOpenApiJson("app10_user.json",
mockMvc.perform(get("/v3/api-docs").header("X-Path-Prefix", "/user"))
performApiDocsRequest(x -> x.header("X-Path-Prefix", "/user"))
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
import org.junit.Test;
import org.springframework.test.context.TestPropertySource;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@TestPropertySource(properties = "openapi-generator-for-spring.enabled=false")
public class App24Test extends AbstractOpenApiGeneratorWebMvcBaseIntTest {

@Test
public void testOpenApiIsNotFoundWhenDisabled() throws Exception {
mockMvc.perform(get("/v3/api-docs"))
performApiDocsRequest(x -> x)
.andExpect(status().isNotFound());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class App26Test extends AbstractOpenApiGeneratorWebMvcBaseIntTest {

@Test
public void testOpenApiIsNotFoundWhenDisabled() throws Exception {
mockMvc.perform(get("/v3/api-docs"))
performApiDocsRequest(x -> x)
.andExpect(status().isOk());
mockMvc.perform(get("/swagger-ui/index.html"))
.andExpect(status().isNotFound());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package de.qaware.openapigeneratorforspring.test.app35;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App35 {
public static void main(String[] args) {
SpringApplication.run(App35.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package de.qaware.openapigeneratorforspring.test.app35;

import de.qaware.openapigeneratorforspring.test.AbstractOpenApiGeneratorWebMvcBaseIntTest;
import org.junit.Test;

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

public class App35Test extends AbstractOpenApiGeneratorWebMvcBaseIntTest {

@Test
public void getOpenApiAsJson_withAcceptTextHtml() throws Exception {
performApiDocsRequest(x -> x.accept("text/html"))
.andExpect(status().isNotAcceptable());
}

@Test
public void getOpenApiAsJson_withAcceptApplicationYaml() throws Exception {
assertResponseBodyMatchesOpenApiYaml("app35.yaml",
performApiDocsRequest(x -> x.accept("application/yaml"))
);
}

@Test
public void getOpenApiAsJson_withAcceptApplicationXYaml() throws Exception {
assertResponseBodyMatchesOpenApiYaml("app35.yaml",
performApiDocsRequest(x -> x.accept("application/x-yaml"))
);
}

@Test
public void getOpenApiAsJson_withAcceptTextYaml() throws Exception {
assertResponseBodyMatchesOpenApiYaml("app35.yaml",
performApiDocsRequest(x -> x.accept("text/yaml"))
);
}

@Test
public void getOpenApiAsJson_withAcceptTextXYaml() throws Exception {
assertResponseBodyMatchesOpenApiYaml("app35.yaml",
performApiDocsRequest(x -> x.accept("text/x-yaml"))
);
}

@Test
public void getOpenApiAsJson_withAcceptTextVndYaml() throws Exception {
assertResponseBodyMatchesOpenApiYaml("app35.yaml",
performApiDocsRequest(x -> x.accept("text/vnd.yaml"))
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package de.qaware.openapigeneratorforspring.test.app36;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App36 {
public static void main(String[] args) {
SpringApplication.run(App36.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package de.qaware.openapigeneratorforspring.test.app36;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class App36Controller {
@GetMapping("mapping1")
public String mapping1(@RequestBody String request, @RequestParam String param1) {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package de.qaware.openapigeneratorforspring.test.app36;

import de.qaware.openapigeneratorforspring.test.AbstractOpenApiGeneratorWebMvcBaseIntTest;
import org.junit.Test;

public class App36Test extends AbstractOpenApiGeneratorWebMvcBaseIntTest {

@Test
public void getOpenApiAsJson() throws Exception {
assertResponseBodyMatchesOpenApiYaml("app36.yaml",
performApiDocsRequest(x -> x.accept("application/yaml"))
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
openapi: 3.0.1
info:
title: API for App35
version: unknown
paths: { }
Loading

0 comments on commit 8d47430

Please sign in to comment.