From 4a240589e975aac9e42187459fd5b6fc5e9eb14a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20N=C3=B6hles?= Date: Wed, 16 Oct 2024 07:55:38 +0200 Subject: [PATCH 1/8] Add new artefact version Rename health endpoint part to just validator and application --- README.md | 2 +- ...Check.java => ApplicationHealthCheck.java} | 18 ++++++- .../HealthCheck/ValidatorHealthCheck.java | 18 ++++++- .../easybill/Services/ValidationService.java | 15 ++++-- ...-CII-1.3.12.sch => EN16931-CII-1.3.13.sch} | 49 +++++++++++-------- ...-UBL-1.3.12.sch => EN16931-UBL-1.3.13.sch} | 21 ++++---- src/main/resources/application.properties | 4 ++ .../github/easybill/IndexControllerTest.java | 43 +++++++++------- 8 files changed, 112 insertions(+), 58 deletions(-) rename src/main/java/io/github/easybill/Services/HealthCheck/{StatsHealthCheck.java => ApplicationHealthCheck.java} (72%) rename src/main/resources/{EN16931-CII-1.3.12.sch => EN16931-CII-1.3.13.sch} (97%) mode change 100644 => 100755 rename src/main/resources/{EN16931-UBL-1.3.12.sch => EN16931-UBL-1.3.13.sch} (98%) mode change 100644 => 100755 diff --git a/README.md b/README.md index a6f64b6..5d905bd 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ EN16931 schematron rules. It exposes a validation endpoint which takes the to be validated XML and returns the schematron report. The HTTP status code indicates if the provided XML is valid (200) or has issues (400). UBL and CII is supported. -### Currently supported validation artifacts: [v1.3.12](https://github.com/ConnectingEurope/eInvoicing-EN16931/releases/tag/validation-1.3.12) +### Currently supported validation artifacts: [v1.3.13](https://github.com/ConnectingEurope/eInvoicing-EN16931/releases/tag/validation-1.3.13) ## Usage This service was mainly designed with containerization in mind. So general idea is to use the following diff --git a/src/main/java/io/github/easybill/Services/HealthCheck/StatsHealthCheck.java b/src/main/java/io/github/easybill/Services/HealthCheck/ApplicationHealthCheck.java similarity index 72% rename from src/main/java/io/github/easybill/Services/HealthCheck/StatsHealthCheck.java rename to src/main/java/io/github/easybill/Services/HealthCheck/ApplicationHealthCheck.java index fd2ad78..69e93f5 100644 --- a/src/main/java/io/github/easybill/Services/HealthCheck/StatsHealthCheck.java +++ b/src/main/java/io/github/easybill/Services/HealthCheck/ApplicationHealthCheck.java @@ -2,6 +2,8 @@ import jakarta.enterprise.context.ApplicationScoped; import java.lang.management.ManagementFactory; +import java.util.Objects; +import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.health.HealthCheck; import org.eclipse.microprofile.health.HealthCheckResponse; import org.eclipse.microprofile.health.HealthCheckResponseBuilder; @@ -9,12 +11,18 @@ @Liveness @ApplicationScoped -public final class StatsHealthCheck implements HealthCheck { +public final class ApplicationHealthCheck implements HealthCheck { + + final Config config; + + public ApplicationHealthCheck(Config config) { + this.config = config; + } @Override public HealthCheckResponse call() { HealthCheckResponseBuilder response = HealthCheckResponse.named( - "stats" + "Application" ); var osBean = ManagementFactory.getOperatingSystemMXBean(); @@ -22,6 +30,12 @@ public HealthCheckResponse call() { return response .up() + .withData( + "version", + Objects.requireNonNull( + config.getConfigValue("application.version").getValue() + ) + ) .withData("osName", osBean.getName()) .withData("osArch", osBean.getArch()) .withData( diff --git a/src/main/java/io/github/easybill/Services/HealthCheck/ValidatorHealthCheck.java b/src/main/java/io/github/easybill/Services/HealthCheck/ValidatorHealthCheck.java index 5440139..c859350 100644 --- a/src/main/java/io/github/easybill/Services/HealthCheck/ValidatorHealthCheck.java +++ b/src/main/java/io/github/easybill/Services/HealthCheck/ValidatorHealthCheck.java @@ -2,6 +2,8 @@ import io.github.easybill.Contracts.IValidationService; import jakarta.enterprise.context.ApplicationScoped; +import java.util.Objects; +import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.health.HealthCheck; import org.eclipse.microprofile.health.HealthCheckResponse; import org.eclipse.microprofile.health.HealthCheckResponseBuilder; @@ -11,16 +13,28 @@ @ApplicationScoped public final class ValidatorHealthCheck implements HealthCheck { + final Config config; final IValidationService validationService; - public ValidatorHealthCheck(IValidationService validationService) { + public ValidatorHealthCheck( + IValidationService validationService, + Config config + ) { + this.config = config; this.validationService = validationService; } @Override public HealthCheckResponse call() { HealthCheckResponseBuilder response = HealthCheckResponse.named( - "schematron ready and ruleset is valid" + "Validator" + ); + + response.withData( + "artefactsVersion", + Objects.requireNonNull( + config.getConfigValue("en16931.artefacts.version").getValue() + ) ); if (this.validationService.isLoadedSchematronValid()) { diff --git a/src/main/java/io/github/easybill/Services/ValidationService.java b/src/main/java/io/github/easybill/Services/ValidationService.java index 2dc2b8e..c5785f1 100644 --- a/src/main/java/io/github/easybill/Services/ValidationService.java +++ b/src/main/java/io/github/easybill/Services/ValidationService.java @@ -18,6 +18,7 @@ import java.util.Optional; import java.util.regex.Pattern; import org.checkerframework.checker.nullness.qual.NonNull; +import org.eclipse.microprofile.config.Config; import org.mozilla.universalchardet.UniversalDetector; @Singleton @@ -26,15 +27,23 @@ public final class ValidationService implements IValidationService { private final SchematronResourceSCH ciiSchematron; private final SchematronResourceSCH ublSchematron; - ValidationService() { + ValidationService(Config config) { + var artefactsVersion = Objects.requireNonNull( + config.getConfigValue("en16931.artefacts.version").getValue() + ); + ciiSchematron = new SchematronResourceSCH( - new ClassPathResource("EN16931-CII-1.3.12.sch") + new ClassPathResource( + String.format("EN16931-CII-%s.sch", artefactsVersion) + ) ); ublSchematron = new SchematronResourceSCH( - new ClassPathResource("EN16931-UBL-1.3.12.sch") + new ClassPathResource( + String.format("EN16931-UBL-%s.sch", artefactsVersion) + ) ); if (!ciiSchematron.isValidSchematron()) { diff --git a/src/main/resources/EN16931-CII-1.3.12.sch b/src/main/resources/EN16931-CII-1.3.13.sch old mode 100644 new mode 100755 similarity index 97% rename from src/main/resources/EN16931-CII-1.3.12.sch rename to src/main/resources/EN16931-CII-1.3.13.sch index 2efd856..91a34f5 --- a/src/main/resources/EN16931-CII-1.3.12.sch +++ b/src/main/resources/EN16931-CII-1.3.13.sch @@ -4,7 +4,8 @@ Licensed under European Union Public Licence (EUPL) version 1.2. --> - + + EN16931 model bound to CII @@ -121,7 +122,7 @@ [BR-64]-The Item standard identifier (BT-157) shall have a Scheme identifier. [BR-CO-04]-Each Invoice line (BG-25) shall be categorized with an Invoiced item VAT category code (BT-151). [BR-CO-18]-An Invoice shall at least have one VAT breakdown group (BG-23). - [BR-DEC-23]-The allowed maximum number of decimals for the Invoice line net amount (BT-131) is 2. + [BR-DEC-23]-The allowed maximum number of decimals for the Invoice line net amount (BT-131) is 2. [BR-41]-Each Invoice line allowance (BG-27) shall have an Invoice line allowance amount (BT-136). @@ -216,7 +217,7 @@ [BR-AG-02]-An Invoice that contains an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "IPSI" shall contain the Seller VAT Identifier (BT-31), the Seller tax registration identifier (BT-32) and/or the Seller tax representative VAT identifier (BT-63). - [BR-AG-05]-In an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "IPSI" the Invoiced item VAT rate (BT-152) shall be 0 (zero) or greater than zero. + [BR-AG-05]-In an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "IPSI" the Invoiced item VAT rate (BT-152) shall be 0 (zero) or greater than zero. [BR-AG-03]-An Invoice that contains a Document level allowance (BG-20) where the Document level allowance VAT category code (BT-95) is "IPSI" shall contain the Seller VAT Identifier (BT-31), the Seller Tax registration identifier (BT-32) and/or the Seller tax representative VAT identifier (BT-63). @@ -489,7 +490,6 @@ [CII-SR-129] - TypeCode should not be present [CII-SR-130] - CategoryTradeTax should not be present [CII-SR-131] - ActualTradeCurrencyExchange should not be present - [CII-SR-440] - ActualAmount should exist maximum once [CII-SR-445] - IncludedTradeTax should not be present [CII-SR-132] - ValiditySpecifiedPeriod should not be present [CII-SR-133] - DeliveryTradeLocation should not be present @@ -513,6 +513,9 @@ [CII-SR-150] - IncludedSpecifiedMarketplace should not be present [CII-SR-447] - UltimateCustomerOrderReferencedDocument should not be present + + [CII-SR-440] - ActualAmount should exist maximum once + [CII-SR-151] - RequestedQuantity should not be present [CII-SR-152] - ReceivedQuantity should not be present @@ -680,8 +683,8 @@ [CII-SR-456] - DefinedTradeContact of BuyerTradeParty shall exist maximum once [CII-SR-457] - IssuerAssignedID with TypeCode 50 should exist maximum once [CII-SR-458] - IssuerAssignedID with TypeCode 130 should exist maximum once - [CII-SR-459] - SellerTradeParty URIUniversalCommunication should exist maximum once - [CII-SR-460] - BuyerTradeParty URIUniversalCommunication should exist maximum once + [CII-SR-459] - SellerTradeParty URIUniversalCommunication should exist maximum once + [CII-SR-460] - BuyerTradeParty URIUniversalCommunication should exist maximum once [CII-SR-308] - RelatedSupplyChainConsignment should not be present @@ -807,7 +810,7 @@ [CII-SR-452] - Only one SpecifiedTradePaymentTerms should be present [CII-SR-453] - Only one SpecifiedTradePaymentTerms Description should be present [CII-SR-461] - Only one TaxPointDate shall be present - [CII-SR-462] - Only one DueDateTypeCode shall be present + [CII-SR-462] - Only one DueDateTypeCode shall be present [CII-SR-411] - InformationAmount should not be present @@ -830,16 +833,20 @@ [CII-SR-004] - Value should not be present [CII-SR-005] - SpecifiedDocumentVersion should not be present - + + [CII-DT-001] - schemeName should not be present + [CII-DT-002] - schemeAgencyName should not be present + [CII-DT-003] - schemeDataURI should not be present + [CII-DT-004] - schemeURI should not be present [CII-DT-005] - schemeID should not be present [CII-DT-006] - schemeAgencyID should not be present [CII-DT-007] - schemeVersionID should not be present - [CII-DT-001] - schemeName should not be present - [CII-DT-002] - schemeAgencyName should not be present - [CII-DT-003] - schemeDataURI should not be present - [CII-DT-004] - schemeURI should not be present + [CII-DT-001] - schemeName should not be present + [CII-DT-002] - schemeAgencyName should not be present + [CII-DT-003] - schemeDataURI should not be present + [CII-DT-004] - schemeURI should not be present [CII-DT-008] - name should not be present @@ -955,13 +962,13 @@ [BR-CL-01]-The document type code MUST be coded by the invoice and credit note related code lists of UNTDID 1001. - [BR-CL-03]-currencyID MUST be coded using ISO code list 4217 alpha-3 + [BR-CL-03]-currencyID MUST be coded using ISO code list 4217 alpha-3 - [BR-CL-04]-Invoice currency code MUST be coded using ISO code list 4217 alpha-3 + [BR-CL-04]-Invoice currency code MUST be coded using ISO code list 4217 alpha-3 - [BR-CL-05]-Tax currency code MUST be coded using ISO code list 4217 alpha-3 + [BR-CL-05]-Tax currency code MUST be coded using ISO code list 4217 alpha-3 [BR-CL-06]-Value added tax point date code MUST be coded using a restriction of UNTDID 2475. @@ -973,10 +980,10 @@ [BR-CL-08]-Subject Code MUST be coded using a restriction of UNTDID 4451. - [BR-CL-10]-Any identifier identification scheme identifier MUST be coded using one of the ISO 6523 ICD list. + [BR-CL-10]-Any identifier identification scheme identifier MUST be coded using one of the ISO 6523 ICD list. - [BR-CL-11]-Any registration identifier identification scheme identifier MUST be coded using one of the ISO 6523 ICD list. + [BR-CL-11]-Any registration identifier identification scheme identifier MUST be coded using one of the ISO 6523 ICD list. [BR-CL-13]-Item classification identifier identification scheme identifier MUST be coded using one of the UNTDID 7143 list. @@ -1003,11 +1010,11 @@ [BR-CL-20]-Coded charge reasons MUST belong to the UNCL 7161 code list - [BR-CL-21]-Item standard identifier scheme identifier MUST belong to the ISO 6523 ICD + [BR-CL-21]-Item standard identifier scheme identifier MUST belong to the ISO 6523 ICD code list - [BR-CL-22]-Tax exemption reason code identifier scheme identifier MUST belong to the CEF VATEX code list + [BR-CL-22]-Tax exemption reason code identifier scheme identifier MUST belong to the CEF VATEX code list [BR-CL-23]-Unit code MUST be coded according to the UN/ECE Recommendation 20 with Rec 21 extension @@ -1016,10 +1023,10 @@ [BR-CL-24]-For Mime code in attribute use MIMEMediaType. - [BR-CL-25]-Endpoint identifier scheme identifier MUST belong to the CEF EAS code list + [BR-CL-25]-Endpoint identifier scheme identifier MUST belong to the CEF EAS code list - [BR-CL-26]-Delivery location identifier scheme identifier MUST belong to the ISO 6523 ICD + [BR-CL-26]-Delivery location identifier scheme identifier MUST belong to the ISO 6523 ICD code list diff --git a/src/main/resources/EN16931-UBL-1.3.12.sch b/src/main/resources/EN16931-UBL-1.3.13.sch old mode 100644 new mode 100755 similarity index 98% rename from src/main/resources/EN16931-UBL-1.3.12.sch rename to src/main/resources/EN16931-UBL-1.3.13.sch index 43b85cf..33a7bc7 --- a/src/main/resources/EN16931-UBL-1.3.12.sch +++ b/src/main/resources/EN16931-UBL-1.3.13.sch @@ -4,7 +4,8 @@ Licensed under European Union Public Licence (EUPL) version 1.2. --> - + + EN16931 model bound to UBL @@ -1149,13 +1150,13 @@ [BR-CL-01]-The document type code MUST be coded by the invoice and credit note related code lists of UNTDID 1001. - [BR-CL-03]-currencyID MUST be coded using ISO code list 4217 alpha-3 + [BR-CL-03]-currencyID MUST be coded using ISO code list 4217 alpha-3 - [BR-CL-04]-Invoice currency code MUST be coded using ISO code list 4217 alpha-3 + [BR-CL-04]-Invoice currency code MUST be coded using ISO code list 4217 alpha-3 - [BR-CL-05]-Tax currency code MUST be coded using ISO code list 4217 alpha-3 + [BR-CL-05]-Tax currency code MUST be coded using ISO code list 4217 alpha-3 [BR-CL-06]-Value added tax point date code MUST be coded using a restriction of UNTDID 2005. @@ -1164,10 +1165,10 @@ [BR-CL-07]-Object identifier identification scheme identifier MUST be coded using a restriction of UNTDID 1153. - [BR-CL-10]-Any identifier identification scheme identifier MUST be coded using one of the ISO 6523 ICD list. + [BR-CL-10]-Any identifier identification scheme identifier MUST be coded using one of the ISO 6523 ICD list. - [BR-CL-11]-Any registration identifier identification scheme identifier MUST be coded using one of the ISO 6523 ICD list. + [BR-CL-11]-Any registration identifier identification scheme identifier MUST be coded using one of the ISO 6523 ICD list. [BR-CL-13]-Item classification identifier identification scheme identifier MUST be @@ -1195,10 +1196,10 @@ [BR-CL-20]-Coded charge reasons MUST belong to the UNCL 7161 code list - [BR-CL-21]-Item standard identifier scheme identifier MUST belong to the ISO 6523 ICD code list + [BR-CL-21]-Item standard identifier scheme identifier MUST belong to the ISO 6523 ICD code list - [BR-CL-22]-Tax exemption reason code identifier scheme identifier MUST belong to the CEF VATEX code list + [BR-CL-22]-Tax exemption reason code identifier scheme identifier MUST belong to the CEF VATEX code list [BR-CL-23]-Unit code MUST be coded according to the UN/ECE Recommendation 20 with @@ -1208,10 +1209,10 @@ [BR-CL-24]-For Mime code in attribute use MIMEMediaType. - [BR-CL-25]-Endpoint identifier scheme identifier MUST belong to the CEF EAS code list + [BR-CL-25]-Endpoint identifier scheme identifier MUST belong to the CEF EAS code list - [BR-CL-26]-Delivery location identifier scheme identifier MUST belong to the ISO 6523 ICD code list + [BR-CL-26]-Delivery location identifier scheme identifier MUST belong to the ISO 6523 ICD code list diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 2227b16..e72f1ae 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,7 @@ +application.version=0.2.0 + +en16931.artefacts.version=1.3.13 + quarkus.banner.enabled=false quarkus.swagger-ui.always-include=true quarkus.swagger-ui.path=/swagger diff --git a/src/test/java/io/github/easybill/IndexControllerTest.java b/src/test/java/io/github/easybill/IndexControllerTest.java index 395012e..deb4ce7 100644 --- a/src/test/java/io/github/easybill/IndexControllerTest.java +++ b/src/test/java/io/github/easybill/IndexControllerTest.java @@ -42,7 +42,6 @@ void testValidationEndpointWhenInvokedWithAnEmptyPayload() { "UBL/guide-example3.xml", "UBL/issue116.xml", "UBL/sales-order-example.xml", - "UBL/sample-discount-price.xml", "UBL/ubl-tc434-creditnote1.xml", "UBL/ubl-tc434-example1.xml", "UBL/ubl-tc434-example2.xml", @@ -103,6 +102,7 @@ void testValidationEndpointWithValidUblDocuments( "CII/CII_ZUGFeRD_23_XRECHNUNG_Elektron.xml", "CII/CII_ZUGFeRD_23_XRECHNUNG_Reisekostenabrechnung.xml", "CII/XRechnung-O.xml", + "CII/CII_ZUGFeRD_23_EXTENDED_Rechnungskorrektur.xml", } ) void testValidationEndpointWithValidCIIDocuments( @@ -118,24 +118,7 @@ void testValidationEndpointWithValidCIIDocuments( } @ParameterizedTest - @ValueSource( - strings = { - "Invalid/CII_MissingExchangeDocumentContext.xml", - "Invalid/Empty.xml", - // Profile BASIC WL is not EN16931 conform. WL = Without Lines. EN16931 requires at least 1 line. - "CII/CII_ZUGFeRD_23_BASIC-WL_Einfach.xml", - // Profile MINIMUM is not EN16931 conform. - "CII/CII_ZUGFeRD_Minimum.xml", - "CII/CII_ZUGFeRD_23_MINIMUM_Buchungshilfe.xml", - "CII/CII_ZUGFeRD_23_MINIMUM_Rechnung.xml", - // Profile EXTENDED is EN16931 conform. However, those examples do have rounding issues. Which is valid - // in EXTENDED Profile - "CII/CII_ZUGFeRD_23_EXTENDED_Kostenrechnung.xml", - "CII/CII_ZUGFeRD_23_EXTENDED_Projektabschlussrechnung.xml", - "CII/CII_ZUGFeRD_23_EXTENDED_Rechnungskorrektur.xml", - "CII/CII_ZUGFeRD_23_EXTENDED_Warenrechnung.xml", - } - ) + @MethodSource("providerValuesValidationEndpointWithInvalidPayload") void testValidationEndpointWithInvalidPayload( @NonNull String fixtureFileName ) throws IOException { @@ -148,6 +131,28 @@ void testValidationEndpointWithInvalidPayload( .statusCode(400); } + static Stream providerValuesValidationEndpointWithInvalidPayload() { + return Stream.of( + Arguments.of("Invalid/CII_MissingExchangeDocumentContext.xml"), + Arguments.of("Invalid/Empty.xml"), + // Uses HRK as currency which is no longer supported in EN16931 + Arguments.of("UBL/sample-discount-price.xml"), + // Profile BASIC WL is not EN16931 conform. WL = Without Lines. EN16931 requires at least 1 line. + Arguments.of("CII/CII_ZUGFeRD_23_BASIC-WL_Einfach.xml"), + // Profile MINIMUM is not EN16931 conform. + Arguments.of("CII/CII_ZUGFeRD_Minimum.xml"), + Arguments.of("CII/CII_ZUGFeRD_23_MINIMUM_Buchungshilfe.xml"), + Arguments.of("CII/CII_ZUGFeRD_23_MINIMUM_Rechnung.xml"), + // Profile EXTENDED is EN16931 conform. However, those examples do have rounding issues. Which is valid + // in EXTENDED Profile + Arguments.of("CII/CII_ZUGFeRD_23_EXTENDED_Kostenrechnung.xml"), + Arguments.of( + "CII/CII_ZUGFeRD_23_EXTENDED_Projektabschlussrechnung.xml" + ), + Arguments.of("CII/CII_ZUGFeRD_23_EXTENDED_Warenrechnung.xml") + ); + } + @ParameterizedTest @MethodSource("providerValuesForDifferentEncodings") void testValidationEndpointWithDifferentEncodings( From e1018c18a6eaeec89f03f93a0875be365ef105e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20N=C3=B6hles?= Date: Wed, 16 Oct 2024 08:04:09 +0200 Subject: [PATCH 2/8] Set more application.properties config values --- .../easybill/EN16931ValidatorApplication.java | 21 ------------------- src/main/resources/application.properties | 9 ++++++++ 2 files changed, 9 insertions(+), 21 deletions(-) delete mode 100644 src/main/java/io/github/easybill/EN16931ValidatorApplication.java diff --git a/src/main/java/io/github/easybill/EN16931ValidatorApplication.java b/src/main/java/io/github/easybill/EN16931ValidatorApplication.java deleted file mode 100644 index ff5f2ce..0000000 --- a/src/main/java/io/github/easybill/EN16931ValidatorApplication.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.github.easybill; - -import jakarta.ws.rs.core.Application; -import org.eclipse.microprofile.openapi.annotations.OpenAPIDefinition; -import org.eclipse.microprofile.openapi.annotations.info.Contact; -import org.eclipse.microprofile.openapi.annotations.info.Info; -import org.eclipse.microprofile.openapi.annotations.info.License; - -@OpenAPIDefinition( - info = @Info( - title = "EN16931 Validator API", - version = "0.1.0", - contact = @Contact( - name = "easybill GmbH", - url = "https://github.com/easybill", - email = "dev@easybill.de" - ), - license = @License(name = "MIT", url = "https://mit-license.org") - ) -) -public final class EN16931ValidatorApplication extends Application {} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e72f1ae..f18b334 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,6 +2,15 @@ application.version=0.2.0 en16931.artefacts.version=1.3.13 +quarkus.smallrye-openapi.info-title=EN16931 Validator API +quarkus.smallrye-openapi.info-version=${application.version} +quarkus.smallrye-openapi.info-description=A small service to validate XML against EN16931 rules +quarkus.smallrye-openapi.info-contact-email=dev@easybill.de +quarkus.smallrye-openapi.info-contact-name=easybill GmbH +quarkus.smallrye-openapi.info-contact-url=https://github.com/easybill +quarkus.smallrye-openapi.info-license-name=MIT +quarkus.smallrye-openapi.info-license-url=https://mit-license.org + quarkus.banner.enabled=false quarkus.swagger-ui.always-include=true quarkus.swagger-ui.path=/swagger From 6382ee585b82380ad23aa424b088f8ecf8801a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20N=C3=B6hles?= Date: Wed, 16 Oct 2024 08:05:46 +0200 Subject: [PATCH 3/8] Rename IndexController to ValidationController --- .../{IndexController.java => ValidationController.java} | 4 ++-- .../{IndexResourceIT.java => ValidationResourceIT.java} | 2 +- ...IndexControllerTest.java => ValidationControllerTest.java} | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/main/java/io/github/easybill/Controllers/{IndexController.java => ValidationController.java} (94%) rename src/native-test/java/io/github/easybill/{IndexResourceIT.java => ValidationResourceIT.java} (72%) rename src/test/java/io/github/easybill/{IndexControllerTest.java => ValidationControllerTest.java} (99%) diff --git a/src/main/java/io/github/easybill/Controllers/IndexController.java b/src/main/java/io/github/easybill/Controllers/ValidationController.java similarity index 94% rename from src/main/java/io/github/easybill/Controllers/IndexController.java rename to src/main/java/io/github/easybill/Controllers/ValidationController.java index 020cd2a..824ab34 100644 --- a/src/main/java/io/github/easybill/Controllers/IndexController.java +++ b/src/main/java/io/github/easybill/Controllers/ValidationController.java @@ -15,11 +15,11 @@ import org.jboss.resteasy.reactive.RestResponse; @Path("/") -public final class IndexController { +public final class ValidationController { private final IValidationService validationService; - public IndexController(IValidationService validationService) { + public ValidationController(IValidationService validationService) { this.validationService = validationService; } diff --git a/src/native-test/java/io/github/easybill/IndexResourceIT.java b/src/native-test/java/io/github/easybill/ValidationResourceIT.java similarity index 72% rename from src/native-test/java/io/github/easybill/IndexResourceIT.java rename to src/native-test/java/io/github/easybill/ValidationResourceIT.java index 198ec24..8ea339b 100644 --- a/src/native-test/java/io/github/easybill/IndexResourceIT.java +++ b/src/native-test/java/io/github/easybill/ValidationResourceIT.java @@ -3,6 +3,6 @@ import io.quarkus.test.junit.QuarkusIntegrationTest; @QuarkusIntegrationTest -class IndexResourceIT extends IndexControllerTest { +class ValidationResourceIT extends ValidationControllerTest { // Execute the same tests but in packaged mode. } diff --git a/src/test/java/io/github/easybill/IndexControllerTest.java b/src/test/java/io/github/easybill/ValidationControllerTest.java similarity index 99% rename from src/test/java/io/github/easybill/IndexControllerTest.java rename to src/test/java/io/github/easybill/ValidationControllerTest.java index deb4ce7..fe9eb3b 100644 --- a/src/test/java/io/github/easybill/IndexControllerTest.java +++ b/src/test/java/io/github/easybill/ValidationControllerTest.java @@ -16,7 +16,7 @@ import org.junit.jupiter.params.provider.ValueSource; @QuarkusTest -class IndexControllerTest { +class ValidationControllerTest { @Test void testValidationEndpointWhenInvokedWithWrongMethod() { From c0f683a6268e15c0001ee33868df522ccbe5947e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20N=C3=B6hles?= Date: Wed, 16 Oct 2024 09:18:22 +0200 Subject: [PATCH 4/8] Add GlobalExceptionHandler --- .../GlobalExceptionInterceptor.java | 21 +++++++++ .../RequestResponseLoggingInterceptor.java | 47 +++++++++++++++++++ src/main/resources/application.properties | 1 + 3 files changed, 69 insertions(+) create mode 100644 src/main/java/io/github/easybill/Interceptors/GlobalExceptionInterceptor.java create mode 100644 src/main/java/io/github/easybill/Interceptors/RequestResponseLoggingInterceptor.java diff --git a/src/main/java/io/github/easybill/Interceptors/GlobalExceptionInterceptor.java b/src/main/java/io/github/easybill/Interceptors/GlobalExceptionInterceptor.java new file mode 100644 index 0000000..127389f --- /dev/null +++ b/src/main/java/io/github/easybill/Interceptors/GlobalExceptionInterceptor.java @@ -0,0 +1,21 @@ +package io.github.easybill.Interceptors; + +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; +import org.jboss.logging.Logger; + +@Provider +public class GlobalExceptionInterceptor implements ExceptionMapper { + + private static final Logger LOGGER = Logger.getLogger( + GlobalExceptionInterceptor.class + ); + + @Override + public Response toResponse(Throwable exception) { + LOGGER.error("Encountered an exception:", exception); + + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } +} diff --git a/src/main/java/io/github/easybill/Interceptors/RequestResponseLoggingInterceptor.java b/src/main/java/io/github/easybill/Interceptors/RequestResponseLoggingInterceptor.java new file mode 100644 index 0000000..771ec7f --- /dev/null +++ b/src/main/java/io/github/easybill/Interceptors/RequestResponseLoggingInterceptor.java @@ -0,0 +1,47 @@ +package io.github.easybill.Interceptors; + +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.ContainerResponseContext; +import jakarta.ws.rs.container.ContainerResponseFilter; +import jakarta.ws.rs.ext.Provider; +import java.io.IOException; +import org.jboss.logging.Logger; + +@Provider +public class RequestResponseLoggingInterceptor + implements ContainerRequestFilter, ContainerResponseFilter { + + private static final Logger logger = Logger.getLogger( + RequestResponseLoggingInterceptor.class + ); + + @Override + public void filter(ContainerRequestContext containerRequestContext) + throws IOException { + String method = containerRequestContext.getMethod(); + String uri = containerRequestContext + .getUriInfo() + .getRequestUri() + .toString(); + String queryParams = containerRequestContext + .getUriInfo() + .getQueryParameters() + .toString(); + + logger.infof("Request received: %s %s, %s", method, uri, queryParams); + } + + @Override + public void filter( + ContainerRequestContext containerRequestContext, + ContainerResponseContext containerResponseContext + ) throws IOException { + int statusCode = containerResponseContext.getStatus(); + String status = containerResponseContext + .getStatusInfo() + .getReasonPhrase(); + + logger.infof("Response sent: %s - %s", statusCode, status); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f18b334..8ae7f7f 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -12,6 +12,7 @@ quarkus.smallrye-openapi.info-license-name=MIT quarkus.smallrye-openapi.info-license-url=https://mit-license.org quarkus.banner.enabled=false +quarkus.log.console.level=INFO quarkus.swagger-ui.always-include=true quarkus.swagger-ui.path=/swagger quarkus.smallrye-health.root-path=/health \ No newline at end of file From 93275ac3873dbda650cff3a81870c44206bb562b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20N=C3=B6hles?= Date: Wed, 16 Oct 2024 10:51:49 +0200 Subject: [PATCH 5/8] Return json payload --- .../Controllers/ValidationController.java | 13 ++--- .../easybill/Dtos/ValidationResult.java | 51 +++---------------- .../easybill/Dtos/ValidationResultField.java | 46 +++++++++++++++++ .../Dtos/ValidationResultMetaData.java | 3 ++ .../easybill/Services/ValidationService.java | 18 +++---- 5 files changed, 69 insertions(+), 62 deletions(-) create mode 100644 src/main/java/io/github/easybill/Dtos/ValidationResultField.java create mode 100644 src/main/java/io/github/easybill/Dtos/ValidationResultMetaData.java diff --git a/src/main/java/io/github/easybill/Controllers/ValidationController.java b/src/main/java/io/github/easybill/Controllers/ValidationController.java index 824ab34..c0448ae 100644 --- a/src/main/java/io/github/easybill/Controllers/ValidationController.java +++ b/src/main/java/io/github/easybill/Controllers/ValidationController.java @@ -26,7 +26,7 @@ public ValidationController(IValidationService validationService) { @POST @Path("/validation") @Consumes(MediaType.APPLICATION_XML) - @Produces(MediaType.APPLICATION_XML) + @Produces(MediaType.APPLICATION_JSON) @APIResponses( { @APIResponse( @@ -39,8 +39,9 @@ public ValidationController(IValidationService validationService) { ), } ) - public RestResponse<@NonNull String> validation(InputStream xmlInputStream) - throws Exception { + public RestResponse<@NonNull ValidationResult> validation( + InputStream xmlInputStream + ) throws Exception { try { ValidationResult result = validationService.validateXml( xmlInputStream @@ -48,13 +49,13 @@ public ValidationController(IValidationService validationService) { if (result.isValid()) { return RestResponse.ResponseBuilder - .ok(result.getXmlReport(), MediaType.APPLICATION_XML) + .ok(result, MediaType.APPLICATION_JSON) .build(); } return RestResponse.ResponseBuilder - .create(RestResponse.Status.BAD_REQUEST, result.getXmlReport()) - .type(MediaType.APPLICATION_XML) + .create(RestResponse.Status.BAD_REQUEST, result) + .type(MediaType.APPLICATION_JSON) .build(); } catch (InvalidXmlException exception) { return RestResponse.status(RestResponse.Status.BAD_REQUEST); diff --git a/src/main/java/io/github/easybill/Dtos/ValidationResult.java b/src/main/java/io/github/easybill/Dtos/ValidationResult.java index 4031046..c6073d2 100644 --- a/src/main/java/io/github/easybill/Dtos/ValidationResult.java +++ b/src/main/java/io/github/easybill/Dtos/ValidationResult.java @@ -1,53 +1,14 @@ package io.github.easybill.Dtos; -import com.helger.schematron.svrl.jaxb.FailedAssert; -import com.helger.schematron.svrl.jaxb.SchematronOutputType; import java.util.List; import org.checkerframework.checker.nullness.qual.NonNull; -public final class ValidationResult { - - @NonNull - private final SchematronOutputType report; - - @NonNull - private final String xmlReport; - - @NonNull - private final List errors; - - @NonNull - private final List warnings; - - public ValidationResult( - @NonNull SchematronOutputType report, - @NonNull String xmlReport, - @NonNull List errors, - @NonNull List warnings - ) { - this.report = report.clone(); - this.xmlReport = xmlReport; - this.errors = errors.stream().toList(); - this.warnings = warnings.stream().toList(); - } - +public record ValidationResult( + @NonNull ValidationResultMetaData meta, + @NonNull List<@NonNull ValidationResultField> errors, + @NonNull List<@NonNull ValidationResultField> warnings +) { public boolean isValid() { - return (long) errors.size() == 0; - } - - public @NonNull SchematronOutputType getReport() { - return report.clone(); - } - - public @NonNull String getXmlReport() { - return xmlReport; - } - - public @NonNull List getErrors() { - return errors.stream().toList(); - } - - public @NonNull List getWarnings() { - return warnings.stream().toList(); + return errors.isEmpty(); } } diff --git a/src/main/java/io/github/easybill/Dtos/ValidationResultField.java b/src/main/java/io/github/easybill/Dtos/ValidationResultField.java new file mode 100644 index 0000000..a1d52d6 --- /dev/null +++ b/src/main/java/io/github/easybill/Dtos/ValidationResultField.java @@ -0,0 +1,46 @@ +package io.github.easybill.Dtos; + +import com.helger.schematron.svrl.jaxb.FailedAssert; +import com.helger.schematron.svrl.jaxb.Text; +import java.util.List; +import java.util.Objects; +import org.checkerframework.checker.nullness.qual.NonNull; + +enum Severity { + FATAL, + WARNING, +} + +public record ValidationResultField( + @NonNull String ruleId, + @NonNull String ruleLocation, + @NonNull Severity severity, + @NonNull List<@NonNull String> messages +) { + public static ValidationResultField fromFailedAssert( + @NonNull FailedAssert failedAssert + ) { + var messages = failedAssert + .getDiagnosticReferenceOrPropertyReferenceOrText() + .stream() + .filter(element -> element instanceof Text) + .map(element -> + ((Text) element).getContent() + .stream() + .filter(innerElement -> innerElement instanceof String) + .map(innerElement -> (String) innerElement) + .toList() + ) + .flatMap(List::stream) + .toList(); + + return new ValidationResultField( + Objects.requireNonNullElse(failedAssert.getId(), ""), + Objects.requireNonNullElse(failedAssert.getLocation(), ""), + Objects.equals(failedAssert.getFlag(), "fatal") + ? Severity.FATAL + : Severity.WARNING, + messages + ); + } +} diff --git a/src/main/java/io/github/easybill/Dtos/ValidationResultMetaData.java b/src/main/java/io/github/easybill/Dtos/ValidationResultMetaData.java new file mode 100644 index 0000000..0c976a2 --- /dev/null +++ b/src/main/java/io/github/easybill/Dtos/ValidationResultMetaData.java @@ -0,0 +1,3 @@ +package io.github.easybill.Dtos; + +public record ValidationResultMetaData() {} diff --git a/src/main/java/io/github/easybill/Services/ValidationService.java b/src/main/java/io/github/easybill/Services/ValidationService.java index c5785f1..56c6d4b 100644 --- a/src/main/java/io/github/easybill/Services/ValidationService.java +++ b/src/main/java/io/github/easybill/Services/ValidationService.java @@ -3,11 +3,12 @@ import com.helger.commons.io.ByteArrayWrapper; import com.helger.commons.io.resource.ClassPathResource; import com.helger.schematron.sch.SchematronResourceSCH; -import com.helger.schematron.svrl.SVRLMarshaller; import com.helger.schematron.svrl.jaxb.FailedAssert; import com.helger.schematron.svrl.jaxb.SchematronOutputType; import io.github.easybill.Contracts.IValidationService; import io.github.easybill.Dtos.ValidationResult; +import io.github.easybill.Dtos.ValidationResultField; +import io.github.easybill.Dtos.ValidationResultMetaData; import io.github.easybill.Enums.XMLSyntaxType; import io.github.easybill.Exceptions.InvalidXmlException; import jakarta.inject.Singleton; @@ -75,15 +76,8 @@ public final class ValidationService implements IValidationService { var report = innerValidateSchematron(xmlSyntaxType, bytesFromSteam) .orElseThrow(RuntimeException::new); - String reportXML = new SVRLMarshaller().getAsString(report); - - if (reportXML == null) { - throw new RuntimeException("validation failed unexpectedly"); - } - return new ValidationResult( - report, - reportXML, + new ValidationResultMetaData(), getErrorsFromSchematronOutput(report), getWarningsFromSchematronOutput(report) ); @@ -97,7 +91,7 @@ public boolean isLoadedSchematronValid() { ); } - private List getErrorsFromSchematronOutput( + private List<@NonNull ValidationResultField> getErrorsFromSchematronOutput( @NonNull SchematronOutputType outputType ) { return outputType @@ -108,10 +102,11 @@ private List getErrorsFromSchematronOutput( Objects.equals(((FailedAssert) element).getFlag(), "fatal") ) .map(element -> (FailedAssert) element) + .map(ValidationResultField::fromFailedAssert) .toList(); } - private List getWarningsFromSchematronOutput( + private List<@NonNull ValidationResultField> getWarningsFromSchematronOutput( @NonNull SchematronOutputType outputType ) { return outputType @@ -122,6 +117,7 @@ private List getWarningsFromSchematronOutput( Objects.equals(((FailedAssert) element).getFlag(), "warning") ) .map(element -> (FailedAssert) element) + .map(ValidationResultField::fromFailedAssert) .toList(); } From 4ddd7b8c39f850a9de9e4f2190dc266a144e9e66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20N=C3=B6hles?= Date: Wed, 16 Oct 2024 10:57:30 +0200 Subject: [PATCH 6/8] Return json payload --- .../io/github/easybill/Dtos/ValidationResultField.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/github/easybill/Dtos/ValidationResultField.java b/src/main/java/io/github/easybill/Dtos/ValidationResultField.java index a1d52d6..a5a6ce6 100644 --- a/src/main/java/io/github/easybill/Dtos/ValidationResultField.java +++ b/src/main/java/io/github/easybill/Dtos/ValidationResultField.java @@ -1,5 +1,6 @@ package io.github.easybill.Dtos; +import com.fasterxml.jackson.annotation.JsonProperty; import com.helger.schematron.svrl.jaxb.FailedAssert; import com.helger.schematron.svrl.jaxb.Text; import java.util.List; @@ -12,10 +13,10 @@ enum Severity { } public record ValidationResultField( - @NonNull String ruleId, - @NonNull String ruleLocation, - @NonNull Severity severity, - @NonNull List<@NonNull String> messages + @JsonProperty("rule_id") @NonNull String id, + @JsonProperty("rule_location") @NonNull String location, + @JsonProperty("rule_severity") @NonNull Severity severity, + @JsonProperty("rule_messages") @NonNull List<@NonNull String> messages ) { public static ValidationResultField fromFailedAssert( @NonNull FailedAssert failedAssert From c78d51caa2383e13cd8306bff57214617a5553d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20N=C3=B6hles?= Date: Wed, 16 Oct 2024 12:02:56 +0200 Subject: [PATCH 7/8] Fix Tests --- README.md | 90 +++++-------------- .../easybill/Dtos/ValidationResult.java | 2 + .../Dtos/ValidationResultMetaData.java | 13 ++- .../GlobalExceptionInterceptor.java | 5 ++ .../easybill/Services/ValidationService.java | 10 ++- .../easybill/ValidationControllerTest.java | 73 ++++++++++----- src/test/resources/Invalid/Empty.xml | 0 src/test/resources/Invalid/Invalid.xml | 2 + 8 files changed, 101 insertions(+), 94 deletions(-) delete mode 100644 src/test/resources/Invalid/Empty.xml create mode 100644 src/test/resources/Invalid/Invalid.xml diff --git a/README.md b/README.md index 5d905bd..961b94e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ## Introduction `en16931-validator` is a small service for validating XML against the official EN16931 schematron rules. It exposes a validation endpoint which takes the -to be validated XML and returns the schematron report. The HTTP status code indicates if the +to be validated XML and returns a JSON payload which contains possible warnings or errors. The HTTP status code indicates if the provided XML is valid (200) or has issues (400). UBL and CII is supported. ### Currently supported validation artifacts: [v1.3.13](https://github.com/ConnectingEurope/eInvoicing-EN16931/releases/tag/validation-1.3.13) @@ -51,7 +51,7 @@ final class EN16931Validator $response = $httpClient->request('POST', 'http://localhost:8081/validation', [ RequestOptions::HEADERS => [ - 'Content-Type' => 'application/xml', + 'Content-Type' => 'application/json', ], RequestOptions::BODY => $xml, RequestOptions::TIMEOUT => 10, @@ -66,73 +66,25 @@ final class EN16931Validator - Example response in case the XML is invalid -> The `svrl:failed-assert` elements are relevant to be inspected. They contain the error message or warnings. -```xml - - - - - - - - - - - - [BR-10]-An Invoice shall contain the Buyer postal address (BG-8). - - - [BR-11]-The Buyer postal address shall contain a Buyer country code (BT-55). - - - [BR-16]-An Invoice shall have at least one Invoice line (BG-25). - - - [BR-CO-25]-In case the Amount due for payment (BT-115) is positive, either the Payment due date (BT-9) or the Payment terms (BT-20) shall be present. - - - - - - - [BR-12]-An Invoice shall have the Sum of Invoice line net amount (BT-106). - - - [BR-CO-10]-Sum of Invoice line net amount (BT-106) = Σ Invoice line net amount (BT-131). - - - [BR-CO-13]-Invoice total amount without VAT (BT-109) = Σ Invoice line net amount (BT-131) - Sum of allowances on document level (BT-107) + Sum of charges on document level (BT-108). - - - - [BR-CO-14]-Invoice total VAT amount (BT-110) = Σ VAT category tax amount (BT-117). - - - - - - - - - - - - - - - - - - - - - - - - - - - +```JSON +{ + "meta": { + "validation_profile": "UBL", + "validation_profile_version": "1.3.13" + }, + "errors": [ + { + "rule_id": "BR-03", + "rule_location": "/*:Invoice[namespace-uri()='urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'][1]", + "rule_severity": "FATAL", + "rule_messages": [ + "[BR-03]-An Invoice shall have an Invoice issue date (BT-2)." + ] + } + ], + "warnings": [], + "is_valid": false +} ``` ## Issues & Contribution diff --git a/src/main/java/io/github/easybill/Dtos/ValidationResult.java b/src/main/java/io/github/easybill/Dtos/ValidationResult.java index c6073d2..512e6ed 100644 --- a/src/main/java/io/github/easybill/Dtos/ValidationResult.java +++ b/src/main/java/io/github/easybill/Dtos/ValidationResult.java @@ -1,5 +1,6 @@ package io.github.easybill.Dtos; +import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; import org.checkerframework.checker.nullness.qual.NonNull; @@ -8,6 +9,7 @@ public record ValidationResult( @NonNull List<@NonNull ValidationResultField> errors, @NonNull List<@NonNull ValidationResultField> warnings ) { + @JsonProperty("is_valid") public boolean isValid() { return errors.isEmpty(); } diff --git a/src/main/java/io/github/easybill/Dtos/ValidationResultMetaData.java b/src/main/java/io/github/easybill/Dtos/ValidationResultMetaData.java index 0c976a2..1ff9af1 100644 --- a/src/main/java/io/github/easybill/Dtos/ValidationResultMetaData.java +++ b/src/main/java/io/github/easybill/Dtos/ValidationResultMetaData.java @@ -1,3 +1,14 @@ package io.github.easybill.Dtos; -public record ValidationResultMetaData() {} +import com.fasterxml.jackson.annotation.JsonProperty; +import io.github.easybill.Enums.XMLSyntaxType; +import org.checkerframework.checker.nullness.qual.NonNull; + +public record ValidationResultMetaData( + @NonNull + @JsonProperty("validation_profile") + XMLSyntaxType validationProfile, + @NonNull + @JsonProperty("validation_profile_version") + String validation_profile_version +) {} diff --git a/src/main/java/io/github/easybill/Interceptors/GlobalExceptionInterceptor.java b/src/main/java/io/github/easybill/Interceptors/GlobalExceptionInterceptor.java index 127389f..620b4df 100644 --- a/src/main/java/io/github/easybill/Interceptors/GlobalExceptionInterceptor.java +++ b/src/main/java/io/github/easybill/Interceptors/GlobalExceptionInterceptor.java @@ -1,5 +1,6 @@ package io.github.easybill.Interceptors; +import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.ExceptionMapper; import jakarta.ws.rs.ext.Provider; @@ -14,6 +15,10 @@ public class GlobalExceptionInterceptor implements ExceptionMapper { @Override public Response toResponse(Throwable exception) { + if (exception instanceof WebApplicationException) { + return ((WebApplicationException) exception).getResponse(); + } + LOGGER.error("Encountered an exception:", exception); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); diff --git a/src/main/java/io/github/easybill/Services/ValidationService.java b/src/main/java/io/github/easybill/Services/ValidationService.java index 56c6d4b..2fa7845 100644 --- a/src/main/java/io/github/easybill/Services/ValidationService.java +++ b/src/main/java/io/github/easybill/Services/ValidationService.java @@ -25,13 +25,15 @@ @Singleton public final class ValidationService implements IValidationService { + private final String artefactsVersion; private final SchematronResourceSCH ciiSchematron; private final SchematronResourceSCH ublSchematron; ValidationService(Config config) { - var artefactsVersion = Objects.requireNonNull( - config.getConfigValue("en16931.artefacts.version").getValue() - ); + artefactsVersion = + Objects.requireNonNull( + config.getConfigValue("en16931.artefacts.version").getValue() + ); ciiSchematron = new SchematronResourceSCH( @@ -77,7 +79,7 @@ public final class ValidationService implements IValidationService { .orElseThrow(RuntimeException::new); return new ValidationResult( - new ValidationResultMetaData(), + new ValidationResultMetaData(xmlSyntaxType, artefactsVersion), getErrorsFromSchematronOutput(report), getWarningsFromSchematronOutput(report) ); diff --git a/src/test/java/io/github/easybill/ValidationControllerTest.java b/src/test/java/io/github/easybill/ValidationControllerTest.java index fe9eb3b..0ff5ab8 100644 --- a/src/test/java/io/github/easybill/ValidationControllerTest.java +++ b/src/test/java/io/github/easybill/ValidationControllerTest.java @@ -1,7 +1,9 @@ package io.github.easybill; import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.*; +import io.github.easybill.Enums.XMLSyntaxType; import io.quarkus.test.junit.QuarkusTest; import io.restassured.http.ContentType; import java.io.IOException; @@ -28,6 +30,17 @@ void testValidationEndpointWhenInvokedWithAnEmptyPayload() { given().when().post("/validation").then().statusCode(415); } + @Test + void testValidationEndpointWithEmptyPayload() throws IOException { + given() + .body(loadFixtureFileAsStream("Invalid/Invalid.xml")) + .contentType(ContentType.XML) + .when() + .post("/validation") + .then() + .statusCode(400); + } + @ParameterizedTest @ValueSource( strings = { @@ -67,7 +80,14 @@ void testValidationEndpointWithValidUblDocuments( .when() .post("/validation") .then() - .statusCode(200); + .statusCode(200) + .contentType(ContentType.JSON) + .body("is_valid", equalTo(true)) + .body( + "meta.validation_profile", + equalTo(XMLSyntaxType.UBL.toString()) + ) + .body("errors", empty()); } @ParameterizedTest @@ -114,27 +134,20 @@ void testValidationEndpointWithValidCIIDocuments( .when() .post("/validation") .then() - .statusCode(200); - } - - @ParameterizedTest - @MethodSource("providerValuesValidationEndpointWithInvalidPayload") - void testValidationEndpointWithInvalidPayload( - @NonNull String fixtureFileName - ) throws IOException { - given() - .body(loadFixtureFileAsStream(fixtureFileName)) - .contentType(ContentType.XML) - .when() - .post("/validation") - .then() - .statusCode(400); + .statusCode(200) + .contentType(ContentType.JSON) + .body("is_valid", equalTo(true)) + .body( + "meta.validation_profile", + equalTo(XMLSyntaxType.CII.toString()) + ) + .body("errors", empty()); } static Stream providerValuesValidationEndpointWithInvalidPayload() { return Stream.of( Arguments.of("Invalid/CII_MissingExchangeDocumentContext.xml"), - Arguments.of("Invalid/Empty.xml"), + //Arguments.of("Invalid/Empty.xml"), // Uses HRK as currency which is no longer supported in EN16931 Arguments.of("UBL/sample-discount-price.xml"), // Profile BASIC WL is not EN16931 conform. WL = Without Lines. EN16931 requires at least 1 line. @@ -154,8 +167,8 @@ static Stream providerValuesValidationEndpointWithInvalidPayload() { } @ParameterizedTest - @MethodSource("providerValuesForDifferentEncodings") - void testValidationEndpointWithDifferentEncodings( + @MethodSource("providerValuesValidationEndpointWithInvalidPayload") + void testValidationEndpointWithInvalidPayload( @NonNull String fixtureFileName ) throws IOException { given() @@ -164,7 +177,10 @@ void testValidationEndpointWithDifferentEncodings( .when() .post("/validation") .then() - .statusCode(200); + .statusCode(400) + .contentType(ContentType.JSON) + .body("is_valid", equalTo(false)) + .body("errors", not(empty())); } static Stream providerValuesForDifferentEncodings() { @@ -174,6 +190,23 @@ static Stream providerValuesForDifferentEncodings() { ); } + @ParameterizedTest + @MethodSource("providerValuesForDifferentEncodings") + void testValidationEndpointWithDifferentEncodings( + @NonNull String fixtureFileName + ) throws IOException { + given() + .body(loadFixtureFileAsStream(fixtureFileName)) + .contentType(ContentType.XML) + .when() + .post("/validation") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("is_valid", equalTo(true)) + .body("errors", empty()); + } + InputStream loadFixtureFileAsStream(@NonNull String fixtureFileName) throws IOException { return Objects diff --git a/src/test/resources/Invalid/Empty.xml b/src/test/resources/Invalid/Empty.xml deleted file mode 100644 index e69de29..0000000 diff --git a/src/test/resources/Invalid/Invalid.xml b/src/test/resources/Invalid/Invalid.xml new file mode 100644 index 0000000..edccff3 --- /dev/null +++ b/src/test/resources/Invalid/Invalid.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 756b364986ea953581318f36ab963d6ec8508f31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20N=C3=B6hles?= Date: Wed, 16 Oct 2024 12:16:23 +0200 Subject: [PATCH 8/8] Fix Spotbugs --- src/main/java/io/github/easybill/Dtos/ValidationResult.java | 6 ++++++ .../java/io/github/easybill/Dtos/ValidationResultField.java | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/src/main/java/io/github/easybill/Dtos/ValidationResult.java b/src/main/java/io/github/easybill/Dtos/ValidationResult.java index 512e6ed..03e9fd7 100644 --- a/src/main/java/io/github/easybill/Dtos/ValidationResult.java +++ b/src/main/java/io/github/easybill/Dtos/ValidationResult.java @@ -1,6 +1,7 @@ package io.github.easybill.Dtos; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Collections; import java.util.List; import org.checkerframework.checker.nullness.qual.NonNull; @@ -9,6 +10,11 @@ public record ValidationResult( @NonNull List<@NonNull ValidationResultField> errors, @NonNull List<@NonNull ValidationResultField> warnings ) { + public ValidationResult { + errors = Collections.unmodifiableList(errors); + warnings = Collections.unmodifiableList(warnings); + } + @JsonProperty("is_valid") public boolean isValid() { return errors.isEmpty(); diff --git a/src/main/java/io/github/easybill/Dtos/ValidationResultField.java b/src/main/java/io/github/easybill/Dtos/ValidationResultField.java index a5a6ce6..4b0964d 100644 --- a/src/main/java/io/github/easybill/Dtos/ValidationResultField.java +++ b/src/main/java/io/github/easybill/Dtos/ValidationResultField.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.helger.schematron.svrl.jaxb.FailedAssert; import com.helger.schematron.svrl.jaxb.Text; +import java.util.Collections; import java.util.List; import java.util.Objects; import org.checkerframework.checker.nullness.qual.NonNull; @@ -18,6 +19,10 @@ public record ValidationResultField( @JsonProperty("rule_severity") @NonNull Severity severity, @JsonProperty("rule_messages") @NonNull List<@NonNull String> messages ) { + public ValidationResultField { + messages = Collections.unmodifiableList(messages); + } + public static ValidationResultField fromFailedAssert( @NonNull FailedAssert failedAssert ) {