diff --git a/readme.md b/readme.md index ec1d830e..79417fb0 100644 --- a/readme.md +++ b/readme.md @@ -24,11 +24,11 @@ Appointment represents a meeting set for a specific place and time |endTime|ZonedDateTime|no|ISO8601 full DateTime. Default value 30 minutes after startTime| |arrivalTime|String|no|Free text but can contain a ISO8601 DateTime. Example: Please arrive 15 minutes early| |place|String|no|The name of the place. Example: Oslo City Røntgen| -|address|[Address](#address)|no|| +|address|[Address](#appointmentaddress)|no|| |subTitle|String|no|Example: MR-undersøkelse av høyre kne| |info|List|no|Additional sections of information (max 2) with a title and text| -### Address +### Appointment.Address |Name|Type|Required|Description| |----|----|--------|-----------| @@ -68,7 +68,7 @@ Details about a Residence, and may be joined with Residence to retrieve the core |Name|Type|Required|Description| |----|----|--------|-----------| -|residence|[Residence](#residence)|yes|| +|residence|[Residence](#boligdetaljerresidence)|yes|| |hjemmelshavere|List|no|List of people with legal rights associated with the residence| |bruksareal|Integer|no|BRA for bolig| |antallOppholdsrom|Integer|no|Number of rooms, bathroom, kitchen and storage rooms excluded| @@ -79,16 +79,16 @@ Details about a Residence, and may be joined with Residence to retrieve the core |andelsnummer|String|no|| |heftelser|List|no|| -### Residence +### Boligdetaljer.Residence |Name|Type|Required|Description| |----|----|--------|-----------| -|address|[ResidenceAddress](#residenceaddress)|yes|| -|matrikkel|[Matrikkel](#matrikkel)|no|| +|address|[ResidenceAddress](#boligdetaljerresidenceaddress)|yes|| +|matrikkel|[Matrikkel](#boligdetaljermatrikkel)|no|| |source|String|no|| |externalId|String|no|| -### ResidenceAddress +### Boligdetaljer.ResidenceAddress |Name|Type|Required|Description| |----|----|--------|-----------| @@ -98,7 +98,7 @@ Details about a Residence, and may be joined with Residence to retrieve the core |postalCode|String|no|| |city|String|no|| -### Matrikkel +### Boligdetaljer.Matrikkel |Name|Type|Required|Description| |----|----|--------|-----------| @@ -360,26 +360,35 @@ Receipt represents a document containing details about a purchase |merchantChain|String|no|Optional name of the chain that the merchant is a part of| |merchantName|String|yes|Name of the store or merchant. Example: Grünerløkka Hip Coffee| |merchantPhoneNumber|String|no|| -|merchantAddress|Address|no|Address of the store or merchant| +|merchantAddress|[Address](#receiptaddress)|no|Address of the store or merchant| |organizationNumber|String|no|Organization number of the sales point| -|barcode|[Barcode](#barcode)|no|| +|barcode|[Barcode](#receiptbarcode)|no|| |payments|List|no|List of payments done during this purchase| |items|List|no|The individual items sold| -|taxiDetails|[TaxiDetails](#taxidetails)|no|Details for taxi receipts| -|customer|[Customer](#customer)|no|Name and address of customer| -|delivery|[Delivery](#delivery)|no|Name and address of delivery| +|taxiDetails|[TaxiDetails](#receipttaxidetails)|no|Details for taxi receipts| +|customer|[Customer](#receiptcustomer)|no|Name and address of customer| +|delivery|[Delivery](#receiptdelivery)|no|Name and address of delivery| |orderNumber|String|no|| |membershipNumber|String|no|| |comment|String|no|| -### Barcode +### Receipt.Address + +|Name|Type|Required|Description| +|----|----|--------|-----------| +|streetAddress|String|no|E.g. Storgata 11| +|postalCode|String|no|| +|city|String|no|| +|country|String|no|| + +### Receipt.Barcode |Name|Type|Required|Description| |----|----|--------|-----------| |barcodeValue|String|no|The barcode on this receipt| |barcodeType|String|no|| -### TaxiDetails +### Receipt.TaxiDetails |Name|Type|Required|Description| |----|----|--------|-----------| @@ -396,29 +405,29 @@ Receipt represents a document containing details about a purchase |totalTimeBeforeBoardingInSeconds|Integer|no|| |totalTimeInSeconds|Integer|no|| |totalTimeWithPassengerInSeconds|Integer|no|| -|vat|[VatDetails](#vatdetails)|no|| +|vat|[VatDetails](#receiptvatdetails)|no|| -### VatDetails +### Receipt.VatDetails |Name|Type|Required|Description| |----|----|--------|-----------| |levels|List|no|| |sum|BigDecimal|no|| -### Customer +### Receipt.Customer |Name|Type|Required|Description| |----|----|--------|-----------| |name|String|no|| -|address|Address|no|| +|address|[Address](#receiptaddress)|no|| |phoneNumber|String|no|| -### Delivery +### Receipt.Delivery |Name|Type|Required|Description| |----|----|--------|-----------| |name|String|no|| -|address|Address|no|| +|address|[Address](#receiptaddress)|no|| |terms|String|no|| ### XML @@ -532,12 +541,12 @@ Residence is a way of linking separate data for the same residence |Name|Type|Required|Description| |----|----|--------|-----------| -|address|[ResidenceAddress](#residenceaddress)|yes|| -|matrikkel|[Matrikkel](#matrikkel)|no|| +|address|[ResidenceAddress](#residenceresidenceaddress)|yes|| +|matrikkel|[Matrikkel](#residencematrikkel)|no|| |source|String|no|| |externalId|String|no|| -### ResidenceAddress +### Residence.ResidenceAddress |Name|Type|Required|Description| |----|----|--------|-----------| @@ -547,7 +556,7 @@ Residence is a way of linking separate data for the same residence |postalCode|String|no|| |city|String|no|| -### Matrikkel +### Residence.Matrikkel |Name|Type|Required|Description| |----|----|--------|-----------| diff --git a/src/main/java/no/digipost/api/datatypes/documentation/ComplexType.java b/src/main/java/no/digipost/api/datatypes/documentation/ComplexType.java index 698f75c7..82fb1b5e 100644 --- a/src/main/java/no/digipost/api/datatypes/documentation/ComplexType.java +++ b/src/main/java/no/digipost/api/datatypes/documentation/ComplexType.java @@ -25,4 +25,8 @@ public boolean isComplex() { public int compareTo(ComplexType o) { return getTypeName().compareTo(o.getTypeName()); } + + public boolean hasSameNameAs(ComplexType ct){ + return this.getTypeName().equals(ct.getTypeName()); + } } diff --git a/src/main/java/no/digipost/api/datatypes/documentation/DataTypePackage.java b/src/main/java/no/digipost/api/datatypes/documentation/DataTypePackage.java new file mode 100644 index 00000000..b7973fef --- /dev/null +++ b/src/main/java/no/digipost/api/datatypes/documentation/DataTypePackage.java @@ -0,0 +1,11 @@ +package no.digipost.api.datatypes.documentation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.PACKAGE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Retention(RUNTIME) @Target({PACKAGE}) +public @interface DataTypePackage { +} diff --git a/src/main/java/no/digipost/api/datatypes/documentation/DocumentationStructureBuilder.java b/src/main/java/no/digipost/api/datatypes/documentation/DocumentationStructureBuilder.java index d32a6126..bbff127e 100644 --- a/src/main/java/no/digipost/api/datatypes/documentation/DocumentationStructureBuilder.java +++ b/src/main/java/no/digipost/api/datatypes/documentation/DocumentationStructureBuilder.java @@ -28,11 +28,11 @@ private static Function, ComplexType> getTypeInfo } private static List getFieldInfos(Class dataType) { - return Stream.of(dataType.getDeclaredFields()).filter(f -> !isStatic(f.getModifiers())).map(getFieldInfo(dataType)).collect(toList()); + return Stream.of(dataType.getDeclaredFields()).filter(f -> !isStatic(f.getModifiers())).map(getFieldInfo()).collect(toList()); } - private static Function getFieldInfo(final Class parentType) { - return field -> new FieldInfo(field.getName(), resolveTypeOfField(parentType, field.getType()), isMandatory(field), getDescription(field)); + private static Function getFieldInfo() { + return field -> new FieldInfo(field.getName(), resolveTypeOfField(field.getType()), isMandatory(field), getDescription(field)); } private static String getDescription(AnnotatedElement element) { @@ -44,9 +44,9 @@ private static boolean isMandatory(Field field) { final boolean notNull = field.getAnnotationsByType(NotNull.class).length > 0; return requiredXmlElement || notNull; } - - private static FieldType resolveTypeOfField(Class parentType, Class fieldType) { - if (fieldType.getPackage().equals(parentType.getPackage())) { + + private static FieldType resolveTypeOfField(Class fieldType) { + if (fieldType.getPackage().isAnnotationPresent(DataTypePackage.class)) { return new ComplexType(fieldType, getDescription(fieldType), getFieldInfos(fieldType), null); } else { return new SimpleType(fieldType); diff --git a/src/main/java/no/digipost/api/datatypes/documentation/MarkdownPrinter.java b/src/main/java/no/digipost/api/datatypes/documentation/MarkdownPrinter.java index 6f5321b3..78cbdd46 100644 --- a/src/main/java/no/digipost/api/datatypes/documentation/MarkdownPrinter.java +++ b/src/main/java/no/digipost/api/datatypes/documentation/MarkdownPrinter.java @@ -8,7 +8,9 @@ import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import java.io.StringWriter; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.stream.Stream; import static java.lang.System.lineSeparator; @@ -30,7 +32,7 @@ public MarkdownPrinter(JAXBContext jaxbContext, boolean printJsonExamples) { public String print(List typeInfos) { - return printHeader(typeInfos) + LLF + + return printHeader(typeInfos) + LLF + printTypes(typeInfos) + LF; } @@ -39,19 +41,19 @@ private String printTypes(List typeInfos) { } private String printHeader(List typeInfos) { - return heading(2, "Data types") + LLF + + return heading(2, "Data types") + LLF + "|Type|Description|" + LF + "|----|-----------|" + LF + typeInfos.stream().map(t -> - "|" + printLink(t) + "|" + t.getDescription() + "|" + "|" + printLink(t, t) + "|" + t.getDescription() + "|" ).collect(joining(LF)); } private String printTypeOverview(ComplexType typeInfo) { - return heading(2, typeInfo.getTypeName()) + LLF + + return heading(2, typeInfo.getTypeName()) + LLF + typeInfo.getDescription() + LLF + heading(3, "Fields") + LLF + - printFields(typeInfo.getFields()) + LLF + + printFields(typeInfo, typeInfo.getFields(), new HashSet<>()) + LLF + (this.printJsonExamples ? printJsonExample(typeInfo.getExample()) + LLF : "") + printXmlExample(typeInfo.getExample()); } @@ -62,7 +64,7 @@ private String printXmlExample(Object example) { final Marshaller marshaller = jaxb.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); marshaller.marshal(example, writer); - return heading(3, "XML") + LLF + + return heading(3, "XML") + LLF + code("xml", writer.toString().trim()); } catch (JAXBException e) { throw new RuntimeException(e); @@ -75,36 +77,46 @@ private String code(String type, String s) { private String printJsonExample(Object example) { try { - return heading(3, "JSON") + LLF + + return heading(3, "JSON") + LLF + code("json", - DataTypesJsonMapper.getMapper() - .configure(SerializationFeature.INDENT_OUTPUT, true) - .writeValueAsString(example).trim()); + DataTypesJsonMapper.getMapper() + .configure(SerializationFeature.INDENT_OUTPUT, true) + .writeValueAsString(example).trim()); } catch (JsonProcessingException e) { throw new RuntimeException(e); } } - private String printTypeInfo(ComplexType type) { - return heading(3, type.getTypeName()) + LLF + - printFields(type.getFields()); + private String printTypeInfo(ComplexType parent, ComplexType type, Set printed) { + printed.add(type); + return heading(3, parent.getTypeName() + "." + type.getTypeName()) + LLF + + printFields(parent, type.getFields(), printed); } - private String printFields(List fields) { - final String subTypes = fields.stream().map(FieldInfo::getType).filter(FieldType::isComplex).map(t -> (ComplexType) t).map(this::printTypeInfo).collect(joining(LLF)); - return "|Name|Type|Required|Description|" + LF + + private String printFields(ComplexType parent, List fields, Set printed) { + + final String subTypes = fields.stream() + .map(FieldInfo::getType) + .filter(FieldType::isComplex) + .map(t -> (ComplexType) t) + .filter(o -> !printed.contains(o)) + .map(type -> printTypeInfo(parent, type, printed)) + .collect(joining(LLF)); + return "|Name|Type|Required|Description|" + LF + "|----|----|--------|-----------|" + LF + - fields.stream().map(this::printField).collect(joining(LF)) + + fields.stream().map(f -> printField(parent, f)).collect(joining(LF)) + (subTypes.isEmpty() ? subTypes : LLF + subTypes); } - private String printField(FieldInfo f) { - final String type = f.getType().isComplex() ? printLink(f.getType()) : f.getType().getTypeName(); + private String printField(ComplexType parent, FieldInfo f) { + final String type = f.getType().isComplex() ? printLink(parent, (ComplexType) f.getType()) : f.getType().getTypeName(); return "|" + f.getName() + "|" + type + "|" + (f.isMandatory() ? "yes" : "no") + "|" + f.getDescription() + "|"; } - private String printLink(FieldType f) { - return "[" + f.getTypeName() + "](#" + f.getTypeName().toLowerCase().replaceAll(" ", "-") + ")"; + private String printLink(ComplexType parent, ComplexType field) { + String prefix = (!parent.hasSameNameAs(field)) ? parent.getTypeName().toLowerCase().replaceAll(" ", "-") : ""; + + return "[" + field.getTypeName() + "](#" + prefix + field.getTypeName().toLowerCase().replaceAll(" ", "-") + ")"; } private String heading(int level, String heading) { diff --git a/src/main/java/no/digipost/api/datatypes/types/package-info.java b/src/main/java/no/digipost/api/datatypes/types/package-info.java index 78f0dec5..3d263ee9 100644 --- a/src/main/java/no/digipost/api/datatypes/types/package-info.java +++ b/src/main/java/no/digipost/api/datatypes/types/package-info.java @@ -1,8 +1,10 @@ @XmlSchema(namespace = DIGIPOST_DATATYPES_NAMESPACE, elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED) @XmlAccessorType(XmlAccessType.FIELD) @XmlJavaTypeAdapter(ZonedDateTimeXmlAdapter.class) +@DataTypePackage package no.digipost.api.datatypes.types; +import no.digipost.api.datatypes.documentation.DataTypePackage; import no.digipost.api.datatypes.marshalling.ZonedDateTimeXmlAdapter; import javax.xml.bind.annotation.XmlAccessType; diff --git a/src/main/java/no/digipost/api/datatypes/types/receipt/package-info.java b/src/main/java/no/digipost/api/datatypes/types/receipt/package-info.java index 9fbd7d8d..90c53d63 100644 --- a/src/main/java/no/digipost/api/datatypes/types/receipt/package-info.java +++ b/src/main/java/no/digipost/api/datatypes/types/receipt/package-info.java @@ -4,8 +4,10 @@ @XmlJavaTypeAdapter(ZonedDateTimeXmlAdapter.class), @XmlJavaTypeAdapter(MoneyBigDecimalXmlAdapter.class) }) +@DataTypePackage package no.digipost.api.datatypes.types.receipt; +import no.digipost.api.datatypes.documentation.DataTypePackage; import no.digipost.api.datatypes.marshalling.MoneyBigDecimalXmlAdapter; import no.digipost.api.datatypes.marshalling.ZonedDateTimeXmlAdapter; @@ -16,3 +18,4 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters; import static no.digipost.api.datatypes.marshalling.DataTypesJAXBContext.DIGIPOST_DATATYPES_NAMESPACE; + diff --git a/src/test/java/no/digipost/api/datatypes/documentation/DocumentationTest.java b/src/test/java/no/digipost/api/datatypes/documentation/DocumentationTest.java index 3c24caaf..803599b8 100644 --- a/src/test/java/no/digipost/api/datatypes/documentation/DocumentationTest.java +++ b/src/test/java/no/digipost/api/datatypes/documentation/DocumentationTest.java @@ -1,12 +1,14 @@ package no.digipost.api.datatypes.documentation; import no.digipost.api.datatypes.DataType; +import no.digipost.api.datatypes.types.MetaData; import no.digipost.api.datatypes.types.ShortTextMessage; import org.junit.Test; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import java.util.stream.Stream; @@ -24,17 +26,26 @@ public class DocumentationTest { @Test public void should_build_type_structure_for_test_data_type() { final Stream types = DocumentationStructureBuilder.buildTypeStructure( - singleton(ShortTextMessage.class), t -> ShortTextMessage.EXAMPLE); + singleton(ShortTextMessage.class), t -> ShortTextMessage.EXAMPLE); + System.out.println(MetaData.EXAMPLE); assertThat(types.collect(toSet()), contains( - new ComplexType(ShortTextMessage.class, "150 character short message", - Collections.singletonList(new FieldInfo("message", new SimpleType(String.class), true, "Your short message goes here")), - ShortTextMessage.EXAMPLE))); + new ComplexType(ShortTextMessage.class, "150 character short message", + Arrays.asList( + new FieldInfo("message", new SimpleType(String.class), true, "Your short message goes here"), + new FieldInfo("metaData", + new ComplexType( + MetaData.class + , "Metainformation" + , Collections.singletonList(new FieldInfo("value", new SimpleType(String.class), true, "Your extra information")) + , null), false, "Some metadata for shortTextMessage") + ), + ShortTextMessage.EXAMPLE))); } @Test public void should_print_docs_for_test_data_type() throws IOException, JAXBException { final String docs = new MarkdownPrinter(JAXBContext.newInstance(ShortTextMessage.class), true).print(DocumentationStructureBuilder.buildTypeStructure( - singleton(ShortTextMessage.class), t -> ShortTextMessage.EXAMPLE).collect(toList())); + singleton(ShortTextMessage.class), t -> ShortTextMessage.EXAMPLE).collect(toList())); assertThat(docs, is(new String(toByteArray(getClass().getResource("testdoc.md")), UTF_8))); } diff --git a/src/test/java/no/digipost/api/datatypes/types/MetaData.java b/src/test/java/no/digipost/api/datatypes/types/MetaData.java new file mode 100644 index 00000000..0afe4d70 --- /dev/null +++ b/src/test/java/no/digipost/api/datatypes/types/MetaData.java @@ -0,0 +1,24 @@ +package no.digipost.api.datatypes.types; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import lombok.Value; +import no.digipost.api.datatypes.documentation.Description; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +@Description("Metainformation") +@XmlRootElement +@Value +@AllArgsConstructor +@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE) +public class MetaData { + + @XmlElement(required = true) + @Description("Your extra information") + String value; + + public static final MetaData EXAMPLE = new MetaData("Some text"); +} diff --git a/src/test/java/no/digipost/api/datatypes/types/ShortTextMessage.java b/src/test/java/no/digipost/api/datatypes/types/ShortTextMessage.java index f96fed92..9b7f59da 100644 --- a/src/test/java/no/digipost/api/datatypes/types/ShortTextMessage.java +++ b/src/test/java/no/digipost/api/datatypes/types/ShortTextMessage.java @@ -25,5 +25,8 @@ public class ShortTextMessage implements DataType { @Description("Your short message goes here") String message; - public static final ShortTextMessage EXAMPLE = new ShortTextMessage("Dette er en kort melding til deg"); + @Description("Some metadata for shortTextMessage") + MetaData metaData; + + public static final ShortTextMessage EXAMPLE = new ShortTextMessage("Dette er en kort melding til deg", MetaData.EXAMPLE); } diff --git a/src/test/resources/no/digipost/api/datatypes/documentation/testdoc.md b/src/test/resources/no/digipost/api/datatypes/documentation/testdoc.md index 8b9f3e83..26951f88 100644 --- a/src/test/resources/no/digipost/api/datatypes/documentation/testdoc.md +++ b/src/test/resources/no/digipost/api/datatypes/documentation/testdoc.md @@ -13,12 +13,22 @@ |Name|Type|Required|Description| |----|----|--------|-----------| |message|String|yes|Your short message goes here| +|metaData|[MetaData](#shorttextmessagemetadata)|no|Some metadata for shortTextMessage| + +### ShortTextMessage.MetaData + +|Name|Type|Required|Description| +|----|----|--------|-----------| +|value|String|yes|Your extra information| ### JSON ```json { "message" : "Dette er en kort melding til deg", + "metaData" : { + "value" : "Some text" + }, "type" : "ShortTextMessage" } ``` @@ -29,5 +39,8 @@ Dette er en kort melding til deg + + Some text + ```