Skip to content

Commit

Permalink
Expose prompt and title in schemas
Browse files Browse the repository at this point in the history
  • Loading branch information
rschev committed Jan 9, 2024
1 parent 1808ef7 commit 968d354
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,29 @@
import com.contentgrid.spring.querydsl.mapping.CollectionFilter;
import com.contentgrid.spring.querydsl.mapping.CollectionFiltersMapping;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import lombok.Value;
import lombok.With;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.hateoas.AffordanceModel.InputPayloadMetadata;
import org.springframework.hateoas.AffordanceModel.Named;
import org.springframework.hateoas.AffordanceModel.PayloadMetadata;
import org.springframework.hateoas.AffordanceModel.PropertyMetadata;
import org.springframework.hateoas.mediatype.InputTypeFactory;
import org.springframework.hateoas.mediatype.html.HtmlInputType;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;

@RequiredArgsConstructor
Expand All @@ -38,17 +43,7 @@ public PayloadMetadata convertToCreatePayloadMetadata(Class<?> domainType) {
List<PropertyMetadata> properties = new ArrayList<>();
extractPropertyMetadataForForms(formMapping.forDomainType(domainType))
.forEachOrdered(properties::add);
return new PayloadMetadata() {
@Override
public Stream<PropertyMetadata> stream() {
return properties.stream();
}

@Override
public Class<?> getType() {
return domainType;
}
};
return new ClassnameI18nedPayloadMetadata(domainType, properties);
}

@Override
Expand All @@ -57,17 +52,7 @@ public PayloadMetadata convertToUpdatePayloadMetadata(Class<?> domainType) {
extractPropertyMetadataForForms(formMapping.forDomainType(domainType))
.filter(property -> !Objects.equals(property.getInputType(), HtmlInputType.URL_VALUE))
.forEachOrdered(properties::add);
return new PayloadMetadata() {
@Override
public Stream<PropertyMetadata> stream() {
return properties.stream();
}

@Override
public Class<?> getType() {
return domainType;
}
};
return new ClassnameI18nedPayloadMetadata(domainType, properties);
}

@Override
Expand All @@ -82,7 +67,7 @@ public PayloadMetadata convertToSearchPayloadMetadata(Class<?> domainType) {
.withReadOnly(false)
).toList();

return properties::stream;
return new ClassnameI18nedPayloadMetadata(domainType, properties);
}

private Stream<PropertyMetadata> extractPropertyMetadataForForms(Container entity) {
Expand Down Expand Up @@ -202,4 +187,43 @@ public Optional<String> getPattern() {
}
}


@AllArgsConstructor
@RequiredArgsConstructor
static class ClassnameI18nedPayloadMetadata implements InputPayloadMetadata {
private final Class<?> domainType;
private final Collection<PropertyMetadata> properties;
@With
private List<MediaType> mediaTypes = List.of(MediaType.APPLICATION_JSON);

@Override
public <T extends Named> T customize(T target, Function<PropertyMetadata, T> customizer) {
return properties.stream()
.filter(propMeta -> propMeta.getName().equals(target.getName()))
.findAny()
.map(customizer)
.orElse(target);
}

@Override
public List<String> getI18nCodes() {
return List.of(domainType.getName());
}

@Override
public List<MediaType> getMediaTypes() {
return this.mediaTypes;
}

@Override
public Stream<PropertyMetadata> stream() {
return properties.stream();
}

@Override
public Class<?> getType() {
return this.domainType;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package com.contentgrid.spring.data.rest.messages;

import com.contentgrid.spring.test.fixture.invoicing.InvoicingApplication;
import com.contentgrid.spring.test.fixture.invoicing.model.Customer;
import com.contentgrid.spring.test.fixture.invoicing.repository.CustomerRepository;
import java.util.Set;
import java.util.UUID;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.hateoas.MediaTypes;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

@SpringBootTest
@ContextConfiguration(classes = {
InvoicingApplication.class,
})
@AutoConfigureMockMvc(printOnlyOnFailure = false)
class TemplatePromptTest {
@Autowired
MockMvc mockMvc;

@Autowired
CustomerRepository customerRepository;

Customer customer;

@BeforeEach
void setup() {
customer = customerRepository.save(new Customer(UUID.randomUUID(), "Abc", "ABC", null, null, null, Set.of(), Set.of()));
}

@AfterEach
void cleanup() {
customerRepository.delete(customer);
customer = null;
}

@Test
void promptOnVatAndNameInHalForms() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/profile/customers")
.accept(MediaTypes.HAL_FORMS_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().json("""
{
_templates: {
search: {
method: "GET",
contentType: "application/json",
properties: [
{
prompt: "VAT number",
name: "vat",
type: "text"
},
{ name: "content.size", type: "number" }, { name: "content.mimetype", type: "text" }, { name: "content.filename", type: "text" },
{ name: "invoices.number", type: "text" }, { name: "invoices.paid", type: "checkbox" }, { name: "invoices.orders.id" }, { name: "invoices.content.length", type: "number" }
]
},
create-form: {
method: "POST",
contentType: "application/json",
properties: [
{
prompt: "Customer name",
name: "name",
type: "text"
},
{
prompt: "VAT number",
name: "vat",
type: "text"
},
{
name : "birthday",
type : "datetime"
},
{
name : "total_spend",
type : "number"
},
{ name: "content.mimetype", type: "text" }, { name: "content.filename", type: "text" },
{ name : "orders", type : "url" }, { name : "invoices", type : "url" }
]
}
}
}
"""))
;
}

@Test
void titleOnVatAndNameInJsonSchema() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/profile/customers")
.accept("application/schema+json"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().json("""
{
title : "Customer",
properties : {
name : {
title : "Customer name",
readOnly : false,
type : "string"
},
vat : {
title : "VAT number",
readOnly : false,
type : "string"
},
birthday : {
title : "Birthday",
readOnly : false,
type : "string",
format : "date-time"
},
total_spend : {
title : "Total spend",
readOnly : false,
type : "integer"
}
}
}
"""))
;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
com.contentgrid.spring.test.fixture.invoicing.model.Customer.vat._prompt=VAT number
com.contentgrid.spring.test.fixture.invoicing.model.Customer.name._prompt=Customer name
com.contentgrid.spring.test.fixture.invoicing.model.Customer.vat._title=VAT number
com.contentgrid.spring.test.fixture.invoicing.model.Customer.name._title=Customer name

0 comments on commit 968d354

Please sign in to comment.