#1038 - Update documentation and testing for ALPS.

Also make the ALPS types support deserialization using @JsonCreator-based private constructor calls.
This commit is contained in:
Greg Turnquist
2019-08-13 13:01:49 -05:00
parent 30ddf1873c
commit f15740fb25
9 changed files with 425 additions and 9 deletions

View File

@@ -0,0 +1,109 @@
/*
* Copyright 2019 the original author or authors.
*
* 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
*
* https://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.
*/
package org.springframework.hateoas.mediatype.alps;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType.*;
import reactor.test.StepVerifier;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.config.EnableHypermediaSupport;
import org.springframework.hateoas.config.WebClientConfigurer;
import org.springframework.hateoas.support.WebFluxEmployeeController;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.config.EnableWebFlux;
/**
* @author Greg Turnquist
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration
public class AlpsWebFluxIntegrationTest {
@Autowired WebTestClient testClient;
@Test
void profileEndpointReturnsAlps() {
this.testClient.get().uri("/profile") //
.accept(MediaTypes.ALPS_JSON) //
.exchange() //
.expectStatus().isOk() //
.returnResult(Alps.class).getResponseBody() //
.as(StepVerifier::create) //
.expectNextMatches(alps -> {
assertThat(alps.getVersion()).isEqualTo("1.0");
assertThat(alps.getDoc().getFormat()).isEqualByComparingTo(Format.TEXT);
assertThat(alps.getDoc().getHref()).isEqualTo("https://example.org/samples/full/doc.html");
assertThat(alps.getDoc().getValue()).isEqualTo("value goes here");
assertThat(alps.getDescriptor()).hasSize(2);
assertThat(alps.getDescriptor().get(0).getId()).isEqualTo("class field [name]");
assertThat(alps.getDescriptor().get(0).getName()).isEqualTo("name");
assertThat(alps.getDescriptor().get(0).getType()).isEqualByComparingTo(Type.SEMANTIC);
assertThat(alps.getDescriptor().get(0).getDescriptor()).hasSize(1);
assertThat(alps.getDescriptor().get(0).getDescriptor().get(0).getId()).isEqualTo("embedded");
assertThat(alps.getDescriptor().get(0).getExt().getId()).isEqualTo("ext [name]");
assertThat(alps.getDescriptor().get(0).getExt().getHref()).isEqualTo("https://example.org/samples/ext/name");
assertThat(alps.getDescriptor().get(0).getExt().getValue()).isEqualTo("value goes here");
assertThat(alps.getDescriptor().get(0).getRt()).isEqualTo("rt for [name]");
assertThat(alps.getDescriptor().get(1).getId()).isEqualTo("class field [role]");
assertThat(alps.getDescriptor().get(1).getName()).isEqualTo("role");
assertThat(alps.getDescriptor().get(1).getType()).isEqualByComparingTo(Type.SEMANTIC);
assertThat(alps.getDescriptor().get(1).getDescriptor()).hasSize(1);
assertThat(alps.getDescriptor().get(1).getDescriptor().get(0).getId()).isEqualTo("embedded");
assertThat(alps.getDescriptor().get(1).getExt().getId()).isEqualTo("ext [role]");
assertThat(alps.getDescriptor().get(1).getExt().getHref()).isEqualTo("https://example.org/samples/ext/role");
assertThat(alps.getDescriptor().get(1).getExt().getValue()).isEqualTo("value goes here");
assertThat(alps.getDescriptor().get(1).getRt()).isEqualTo("rt for [role]");
return true;
}) //
.verifyComplete();
}
@Configuration
@EnableWebFlux
@EnableHypermediaSupport(type = HAL)
static class TestConfig {
@Bean
WebFluxEmployeeController employeeController() {
return new WebFluxEmployeeController();
}
@Bean
WebTestClient webTestClient(WebClientConfigurer webClientConfigurer, ApplicationContext ctx) {
return WebTestClient.bindToApplicationContext(ctx).build() //
.mutate() //
.exchangeStrategies(webClientConfigurer.hypermediaExchangeStrategies()) //
.build();
}
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2019 the original author or authors.
*
* 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
*
* https://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.
*/
package org.springframework.hateoas.mediatype.alps;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.*;
import static org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.config.EnableHypermediaSupport;
import org.springframework.hateoas.support.WebMvcEmployeeController;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
/**
* @author Greg Turnquist
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
public class AlpsWebMvcIntegrationTest {
@Autowired WebApplicationContext context;
MockMvc mockMvc;
@BeforeEach
void setUp() {
this.mockMvc = webAppContextSetup(this.context).build();
}
@Test
void profileEndpointReturnsAlps() throws Exception {
this.mockMvc.perform(get("/profile").accept(MediaTypes.ALPS_JSON_VALUE)) //
.andDo(print()) //
.andExpect(status().isOk()) //
.andExpect(jsonPath("$.version", is("1.0"))) //
.andExpect(jsonPath("$.doc.format", is("TEXT")))
.andExpect(jsonPath("$.doc.href", is("https://example.org/samples/full/doc.html")))
.andExpect(jsonPath("$.doc.value", is("value goes here"))).andExpect(jsonPath("$.descriptor", hasSize(2)))
.andExpect(jsonPath("$.descriptor[0].id", is("class field [name]")))
.andExpect(jsonPath("$.descriptor[0].name", is("name")))
.andExpect(jsonPath("$.descriptor[0].type", is("SEMANTIC")))
.andExpect(jsonPath("$.descriptor[0].descriptor", hasSize(1)))
.andExpect(jsonPath("$.descriptor[0].descriptor[0].id", is("embedded")))
.andExpect(jsonPath("$.descriptor[0].ext.id", is("ext [name]")))
.andExpect(jsonPath("$.descriptor[0].ext.href", is("https://example.org/samples/ext/name")))
.andExpect(jsonPath("$.descriptor[0].ext.value", is("value goes here")))
.andExpect(jsonPath("$.descriptor[0].rt", is("rt for [name]")))
.andExpect(jsonPath("$.descriptor[1].id", is("class field [role]")))
.andExpect(jsonPath("$.descriptor[1].name", is("role")))
.andExpect(jsonPath("$.descriptor[1].type", is("SEMANTIC")))
.andExpect(jsonPath("$.descriptor[1].descriptor", hasSize(1)))
.andExpect(jsonPath("$.descriptor[1].descriptor[0].id", is("embedded")))
.andExpect(jsonPath("$.descriptor[1].ext.id", is("ext [role]")))
.andExpect(jsonPath("$.descriptor[1].ext.href", is("https://example.org/samples/ext/role")))
.andExpect(jsonPath("$.descriptor[1].ext.value", is("value goes here")))
.andExpect(jsonPath("$.descriptor[1].rt", is("rt for [role]")));
}
@Configuration
@EnableWebMvc
@EnableHypermediaSupport(type = HAL)
static class TestConfig {
@Bean
WebMvcEmployeeController employeeController() {
return new WebMvcEmployeeController();
}
}
}

View File

@@ -15,12 +15,17 @@
*/
package org.springframework.hateoas.support;
import static org.springframework.hateoas.MediaTypes.*;
import static org.springframework.hateoas.mediatype.PropertyUtils.*;
import static org.springframework.hateoas.mediatype.alps.Alps.*;
import static org.springframework.hateoas.server.reactive.WebFluxLinkBuilder.*;
import static reactor.function.TupleUtils.*;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.stream.Collectors;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -30,6 +35,11 @@ import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Links;
import org.springframework.hateoas.mediatype.alps.Alps;
import org.springframework.hateoas.mediatype.alps.Descriptor;
import org.springframework.hateoas.mediatype.alps.Ext;
import org.springframework.hateoas.mediatype.alps.Format;
import org.springframework.hateoas.mediatype.alps.Type;
import org.springframework.hateoas.server.reactive.WebFluxLinkBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
@@ -174,4 +184,30 @@ public class WebFluxEmployeeController {
.build() //
);
}
@GetMapping(value = "/profile", produces = ALPS_JSON_VALUE)
Alps profile() {
return Alps.alps() //
.doc(doc() //
.href("https://example.org/samples/full/doc.html") //
.value("value goes here") //
.format(Format.TEXT) //
.build()) //
.descriptor(getExposedProperties(Employee.class).stream() //
.map(property -> Descriptor.builder() //
.id("class field [" + property.getName() + "]") //
.name(property.getName()) //
.type(Type.SEMANTIC) //
.ext(Ext.builder() //
.id("ext [" + property.getName() + "]") //
.href("https://example.org/samples/ext/" + property.getName()) //
.value("value goes here") //
.build()) //
.rt("rt for [" + property.getName() + "]") //
.descriptor(Collections.singletonList(Descriptor.builder().id("embedded").build())) //
.build()) //
.collect(Collectors.toList()))
.build();
}
}

View File

@@ -15,9 +15,13 @@
*/
package org.springframework.hateoas.support;
import static org.springframework.hateoas.MediaTypes.*;
import static org.springframework.hateoas.mediatype.PropertyUtils.*;
import static org.springframework.hateoas.mediatype.alps.Alps.*;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -29,6 +33,11 @@ import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.mediatype.alps.Alps;
import org.springframework.hateoas.mediatype.alps.Descriptor;
import org.springframework.hateoas.mediatype.alps.Ext;
import org.springframework.hateoas.mediatype.alps.Format;
import org.springframework.hateoas.mediatype.alps.Type;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
@@ -174,4 +183,32 @@ public class WebMvcEmployeeController {
.toUri()) //
.build();
}
// tag::alps-profile[]
@GetMapping(value = "/profile", produces = ALPS_JSON_VALUE)
Alps profile() {
return Alps.alps() //
.doc(doc() //
.href("https://example.org/samples/full/doc.html") //
.value("value goes here") //
.format(Format.TEXT) //
.build()) //
.descriptor(getExposedProperties(Employee.class).stream() //
.map(property -> Descriptor.builder() //
.id("class field [" + property.getName() + "]") //
.name(property.getName()) //
.type(Type.SEMANTIC) //
.ext(Ext.builder() //
.id("ext [" + property.getName() + "]") //
.href("https://example.org/samples/ext/" + property.getName()) //
.value("value goes here") //
.build()) //
.rt("rt for [" + property.getName() + "]") //
.descriptor(Collections.singletonList(Descriptor.builder().id("embedded").build())) //
.build()) //
.collect(Collectors.toList()))
.build();
}
// end::alps-profile[]
}