From 6dc7a0941b2cba7ca4f2269756b5175fe98c04f8 Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Mon, 2 Dec 2024 21:43:58 +0100 Subject: [PATCH] add format assertion for dates,times and duration --- pom.xml | 3 +- vocabulary/format-assertion/pom.xml | 78 +++++++++++++++ .../FormatAssertionKeyWordType.java | 49 ++++++++++ .../FormatAssertionVocabulary.java | 69 +++++++++++++ .../formatassertion/FormatKeyword.java | 74 ++++++++++++++ .../vocabulary/formatassertion/Formats.java | 96 +++++++++++++++++++ .../formatassertion/rfc/RegExRule.java | 41 ++++++++ .../vocabulary/formatassertion/rfc/Rfc.java | 30 ++++++ .../formatassertion/rfc/Rfc3339.java | 59 ++++++++++++ .../vocabulary/formatassertion/rfc/Rule.java | 28 ++++++ .../src/main/java/module-info.java | 33 +++++++ .../src/main/resources/rfc/rfc3339 | 30 ++++++ .../FormatAssertionKeyWordTypeTest.java | 50 ++++++++++ .../FormatAssertionVocabularyTest.java | 64 +++++++++++++ .../formatassertion/FormatKeywordTest.java | 89 +++++++++++++++++ .../formatassertion/FormatsTest.java | 62 ++++++++++++ .../src/test/java/module-info.java | 38 ++++++++ vocabulary/pom.xml | 22 +++++ 18 files changed, 914 insertions(+), 1 deletion(-) create mode 100644 vocabulary/format-assertion/pom.xml create mode 100644 vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatAssertionKeyWordType.java create mode 100644 vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatAssertionVocabulary.java create mode 100644 vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatKeyword.java create mode 100644 vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/Formats.java create mode 100644 vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/rfc/RegExRule.java create mode 100644 vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/rfc/Rfc.java create mode 100644 vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/rfc/Rfc3339.java create mode 100644 vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/rfc/Rule.java create mode 100644 vocabulary/format-assertion/src/main/java/module-info.java create mode 100644 vocabulary/format-assertion/src/main/resources/rfc/rfc3339 create mode 100644 vocabulary/format-assertion/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatAssertionKeyWordTypeTest.java create mode 100644 vocabulary/format-assertion/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatAssertionVocabularyTest.java create mode 100644 vocabulary/format-assertion/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatKeywordTest.java create mode 100644 vocabulary/format-assertion/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatsTest.java create mode 100644 vocabulary/format-assertion/src/test/java/module-info.java create mode 100644 vocabulary/pom.xml diff --git a/pom.xml b/pom.xml index 81edb15..b0eb8fe 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,8 @@ api vocabulary-spi - core + core + vocabulary diff --git a/vocabulary/format-assertion/pom.xml b/vocabulary/format-assertion/pom.xml new file mode 100644 index 0000000..2b2b387 --- /dev/null +++ b/vocabulary/format-assertion/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + + io.github.sebastian-toepfer.json-schema.vocabulary + json-schema-vocabulary + 0.4.0-SNAPSHOT + + + json-schema-vocabulary-format-assertion + Json Schema :: vocabulary :: format-assertion + + + + io.github.sebastian-toepfer.json-schema + json-schema-vocabulary-spi + ${project.version} + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.hamcrest + hamcrest + test + + + com.github.npathai + hamcrest-optional + test + + + io.github.sebastian-toepfer.ddd + media-core + test + + + + jakarta.json + jakarta.json-api + provided + + + com.github.spotbugs + spotbugs-annotations + true + provided + + + + org.eclipse.parsson + parsson + test + + + io.github.sebastian-toepfer.json-schema + json-schema-core + ${project.version} + test + + + diff --git a/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatAssertionKeyWordType.java b/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatAssertionKeyWordType.java new file mode 100644 index 0000000..5a79c5a --- /dev/null +++ b/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatAssertionKeyWordType.java @@ -0,0 +1,49 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema.vocabulary.formatassertion; + +import static io.github.sebastiantoepfer.jsonschema.vocabulary.formatassertion.FormatKeyword.NAME; + +import io.github.sebastiantoepfer.jsonschema.JsonSchema; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; + +final class FormatAssertionKeyWordType implements KeywordType { + + private final Formats formats; + + public FormatAssertionKeyWordType(final Formats formats) { + this.formats = formats; + } + + @Override + public String name() { + return NAME; + } + + @Override + public Keyword createKeyword(final JsonSchema schema) { + return new FormatKeyword(formats.findByName(schema.asJsonObject().getString(NAME))); + } +} diff --git a/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatAssertionVocabulary.java b/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatAssertionVocabulary.java new file mode 100644 index 0000000..ac66e47 --- /dev/null +++ b/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatAssertionVocabulary.java @@ -0,0 +1,69 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema.vocabulary.formatassertion; + +import static io.github.sebastiantoepfer.jsonschema.vocabulary.formatassertion.FormatKeyword.NAME; + +import io.github.sebastiantoepfer.jsonschema.Vocabulary; +import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.LazyVocabularies; +import java.net.URI; +import java.util.Objects; +import java.util.Optional; + +public final class FormatAssertionVocabulary implements LazyVocabularies, Vocabulary { + + private final URI id; + + public FormatAssertionVocabulary() { + id = URI.create("https://json-schema.org/draft/2020-12/vocab/format-assertion"); + } + + @Override + public Optional loadVocabularyWithId(final URI id) { + final Optional result; + if (id().equals(id)) { + result = Optional.of(this); + } else { + result = Optional.empty(); + } + return result; + } + + @Override + public URI id() { + return id; + } + + @Override + public Optional findKeywordTypeByName(final String name) { + final Optional result; + if (Objects.equals(NAME, name)) { + result = Optional.of(new FormatAssertionKeyWordType(new Formats())); + } else { + result = Optional.empty(); + } + return result; + } +} diff --git a/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatKeyword.java b/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatKeyword.java new file mode 100644 index 0000000..2c2a601 --- /dev/null +++ b/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatKeyword.java @@ -0,0 +1,74 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema.vocabulary.formatassertion; + +import io.github.sebastiantoepfer.ddd.common.Media; +import io.github.sebastiantoepfer.jsonschema.InstanceType; +import io.github.sebastiantoepfer.jsonschema.keyword.Annotation; +import io.github.sebastiantoepfer.jsonschema.keyword.Assertion; +import jakarta.json.Json; +import jakarta.json.JsonString; +import jakarta.json.JsonValue; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +final class FormatKeyword implements Assertion, Annotation { + + static final String NAME = "format"; + private final Formats.Format format; + + FormatKeyword(final Formats.Format format) { + this.format = Objects.requireNonNull(format); + } + + @Override + public Collection categories() { + return List.of(KeywordCategory.ASSERTION, KeywordCategory.ANNOTATION); + } + + @Override + public boolean isValidFor(final JsonValue instance) { + return !InstanceType.STRING.isInstance(instance) || hasValidValue((JsonString) instance); + } + + private boolean hasValidValue(final JsonString value) { + return format.applyTo(value.getString()); + } + + @Override + public boolean hasName(final String name) { + return NAME.equals(name); + } + + @Override + public JsonValue valueFor(final JsonValue value) { + return Json.createValue(format.name()); + } + + @Override + public > T printOn(final T media) { + return media.withValue(NAME, format.name()); + } +} diff --git a/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/Formats.java b/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/Formats.java new file mode 100644 index 0000000..7cc6542 --- /dev/null +++ b/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/Formats.java @@ -0,0 +1,96 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema.vocabulary.formatassertion; + +import io.github.sebastiantoepfer.jsonschema.vocabulary.formatassertion.rfc.Rfc; +import io.github.sebastiantoepfer.jsonschema.vocabulary.formatassertion.rfc.Rfc3339; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +final class Formats { + + private static final List RFCS = Map.ofEntries( + Map.entry("date-time", "date-time"), + Map.entry("date", "full-date"), + Map.entry("time", "full-time"), + Map.entry("duration", "duration") + ) + .entrySet() + .stream() + .map(entry -> new RfcBasedFormat(entry.getKey(), new Rfc3339(), entry.getValue())) + .map(Format.class::cast) + .toList(); + + Format findByName(final String name) { + return RFCS.stream().filter(f -> f.name().equals(name)).findFirst().orElseGet(() -> new UnknownFormat(name)); + } + + private static class RfcBasedFormat implements Format { + + private final String formatName; + private final Rfc rfc; + private final String ruleName; + + public RfcBasedFormat(final String formatName, final Rfc rfc, final String ruleName) { + this.formatName = Objects.requireNonNull(formatName); + this.rfc = Objects.requireNonNull(rfc); + this.ruleName = Objects.requireNonNull(ruleName); + } + + @Override + public boolean applyTo(final String value) { + return rfc.findRuleByName(ruleName).map(r -> r.applyTo(value)).orElse(Boolean.FALSE); + } + + @Override + public String name() { + return formatName; + } + } + + interface Format { + boolean applyTo(String value); + String name(); + } + + private static class UnknownFormat implements Format { + + private final String name; + + public UnknownFormat(final String name) { + this.name = name; + } + + @Override + public String name() { + return name; + } + + @Override + public boolean applyTo(final String value) { + return false; + } + } +} diff --git a/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/rfc/RegExRule.java b/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/rfc/RegExRule.java new file mode 100644 index 0000000..3814d3c --- /dev/null +++ b/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/rfc/RegExRule.java @@ -0,0 +1,41 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema.vocabulary.formatassertion.rfc; + +import java.util.Objects; +import java.util.regex.Pattern; + +final class RegExRule implements Rule { + + private final Pattern pattern; + + public RegExRule(final Pattern pattern) { + this.pattern = Objects.requireNonNull(pattern); + } + + @Override + public boolean applyTo(final String value) { + return pattern.matcher(value).matches(); + } +} diff --git a/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/rfc/Rfc.java b/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/rfc/Rfc.java new file mode 100644 index 0000000..810dd6e --- /dev/null +++ b/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/rfc/Rfc.java @@ -0,0 +1,30 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema.vocabulary.formatassertion.rfc; + +import java.util.Optional; + +public interface Rfc { + Optional findRuleByName(String ruleName); +} diff --git a/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/rfc/Rfc3339.java b/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/rfc/Rfc3339.java new file mode 100644 index 0000000..11a9db6 --- /dev/null +++ b/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/rfc/Rfc3339.java @@ -0,0 +1,59 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema.vocabulary.formatassertion.rfc; + +import java.util.Map; +import java.util.Optional; +import java.util.regex.Pattern; + +//create regex with: https://abnf.msweet.org/index.php +public final class Rfc3339 implements Rfc { + + private static final String DATE = "\\d{4}\\-\\d{2}\\-\\d{2}"; + private static final String TIMEZONE = "([Zz]|([+-]\\d{2}\\:\\d{2}))"; + private static final String TIME = "\\d{2}\\:\\d{2}\\:\\d{2}(\\.\\d+)?" + TIMEZONE; + + private static final Map RULES = Map.of( + "date-time", + Pattern.compile("^" + DATE + "[Tt]" + TIME + "$"), + "full-date", + Pattern.compile("^" + DATE + "$"), + "full-time", + Pattern.compile("^" + TIME + "$"), + "duration", + Pattern.compile( + "^" + + "([Pp]((" + + "(\\d+[Dd]|\\d+[Mm](\\d+[Dd])?|\\d+[Yy](\\d+[Mm](\\d+[Dd])?)?)" + + "(([Tt](\\d+[Hh](\\d+[Mm](\\d+[Ss])?)?|\\d+[Mm](\\d+[Ss])?|\\d+[Ss])))?" + + ")|([Tt](\\d+[Hh](\\d+[Mm](\\d+[Ss])?)?|\\d+[Mm](\\d+[Ss])?|\\d+[Ss]))|\\d+[Ww]))" + + "$" + ) + ); + + @Override + public Optional findRuleByName(final String ruleName) { + return Optional.ofNullable(RULES.get(ruleName)).map(RegExRule::new); + } +} diff --git a/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/rfc/Rule.java b/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/rfc/Rule.java new file mode 100644 index 0000000..2e4c1aa --- /dev/null +++ b/vocabulary/format-assertion/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/rfc/Rule.java @@ -0,0 +1,28 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema.vocabulary.formatassertion.rfc; + +public interface Rule { + boolean applyTo(String value); +} diff --git a/vocabulary/format-assertion/src/main/java/module-info.java b/vocabulary/format-assertion/src/main/java/module-info.java new file mode 100644 index 0000000..fd4d7c0 --- /dev/null +++ b/vocabulary/format-assertion/src/main/java/module-info.java @@ -0,0 +1,33 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +module io.github.sebastiantoepfer.jsonschema.vocabulary.formatassertion { + requires io.github.sebastiantoepfer.jsonschema; + requires io.github.sebastiantoepfer.jsonschema.vocabulary.spi; + requires io.github.sebastiantoepfer.ddd.common; + requires com.github.spotbugs.annotations; + requires jakarta.json; + + provides io.github.sebastiantoepfer.jsonschema.vocabulary.spi.LazyVocabularies + with io.github.sebastiantoepfer.jsonschema.vocabulary.formatassertion.FormatAssertionVocabulary; +} diff --git a/vocabulary/format-assertion/src/main/resources/rfc/rfc3339 b/vocabulary/format-assertion/src/main/resources/rfc/rfc3339 new file mode 100644 index 0000000..446b791 --- /dev/null +++ b/vocabulary/format-assertion/src/main/resources/rfc/rfc3339 @@ -0,0 +1,30 @@ +date-fullyear = 4DIGIT +date-month = 2DIGIT ; 01-12 +date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on + ; month/year +time-hour = 2DIGIT ; 00-23 +time-minute = 2DIGIT ; 00-59 +time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second + ; rules +time-secfrac = "." 1*DIGIT +time-numoffset = ("+" / "-") time-hour ":" time-minute +time-offset = "Z" / time-numoffset + +partial-time = time-hour ":" time-minute ":" time-second + [time-secfrac] +full-date = date-fullyear "-" date-month "-" date-mday +full-time = partial-time time-offset + +date-time = full-date "T" full-time + +dur-second = 1*DIGIT "S" +dur-minute = 1*DIGIT "M" [dur-second] +dur-hour = 1*DIGIT "H" [dur-minute] +dur-time = "T" (dur-hour / dur-minute / dur-second) +dur-day = 1*DIGIT "D" +dur-week = 1*DIGIT "W" +dur-month = 1*DIGIT "M" [dur-day] +dur-year = 1*DIGIT "Y" [dur-month] +dur-date = (dur-day / dur-month / dur-year) [dur-time] + +duration = "P" (dur-date / dur-time / dur-week) \ No newline at end of file diff --git a/vocabulary/format-assertion/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatAssertionKeyWordTypeTest.java b/vocabulary/format-assertion/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatAssertionKeyWordTypeTest.java new file mode 100644 index 0000000..950be2e --- /dev/null +++ b/vocabulary/format-assertion/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatAssertionKeyWordTypeTest.java @@ -0,0 +1,50 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema.vocabulary.formatassertion; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; +import jakarta.json.Json; +import org.junit.jupiter.api.Test; + +class FormatAssertionKeyWordTypeTest { + + @Test + void should_create_formatassertionkeyword() { + assertThat( + new FormatAssertionKeyWordType(new Formats()).createKeyword( + JsonSchemas.load(Json.createObjectBuilder().add("format", "email").build()) + ), + is(instanceOf(FormatKeyword.class)) + ); + } + + @Test + void should_know_her_name() { + assertThat(new FormatAssertionKeyWordType(new Formats()).name(), is("format")); + } +} diff --git a/vocabulary/format-assertion/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatAssertionVocabularyTest.java b/vocabulary/format-assertion/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatAssertionVocabularyTest.java new file mode 100644 index 0000000..baba2a0 --- /dev/null +++ b/vocabulary/format-assertion/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatAssertionVocabularyTest.java @@ -0,0 +1,64 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema.vocabulary.formatassertion; + +import static com.github.npathai.hamcrestopt.OptionalMatchers.isEmpty; +import static com.github.npathai.hamcrestopt.OptionalMatchers.isPresent; +import static com.github.npathai.hamcrestopt.OptionalMatchers.isPresentAnd; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.net.URI; +import org.junit.jupiter.api.Test; + +class FormatAssertionVocabularyTest { + + @Test + void should_return_empty_optional_for_unknow_id() { + assertThat(new FormatAssertionVocabulary().loadVocabularyWithId(URI.create("hello")), isEmpty()); + } + + @Test + void should_return_non_empty_optional_for_right_id() { + assertThat( + new FormatAssertionVocabulary() + .loadVocabularyWithId(URI.create("https://json-schema.org/draft/2020-12/vocab/format-assertion")), + isPresent() + ); + } + + @Test + void should_return_empty_for_invalid_keywordname() { + assertThat(new FormatAssertionVocabulary().findKeywordTypeByName("$schema"), isEmpty()); + } + + @Test + void should_return_keywordtype_for_format_keyword() { + assertThat( + new FormatAssertionVocabulary().findKeywordTypeByName("format"), + isPresentAnd(is(instanceOf(FormatAssertionKeyWordType.class))) + ); + } +} diff --git a/vocabulary/format-assertion/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatKeywordTest.java b/vocabulary/format-assertion/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatKeywordTest.java new file mode 100644 index 0000000..cb4f1d2 --- /dev/null +++ b/vocabulary/format-assertion/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatKeywordTest.java @@ -0,0 +1,89 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema.vocabulary.formatassertion; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.hasEntry; + +import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import jakarta.json.Json; +import org.junit.jupiter.api.Test; + +class FormatKeywordTest { + + @Test + void should_be_valid_for_any_number() { + assertThat(new FormatKeyword(new Formats().findByName("unknow")).isValidFor(Json.createValue(42)), is(true)); + } + + @Test + void should_be_invalid_for_any_string_with_unknow_format() { + assertThat(new FormatKeyword(new Formats().findByName("unknow")).isValidFor(Json.createValue("42")), is(false)); + } + + @Test + void should_be_valid_for_valid_formated_string() { + assertThat( + new FormatKeyword(new Formats().findByName("date-time")) + .asAssertion() + .isValidFor(Json.createValue("2024-11-29T00:05:00+01:00")), + is(true) + ); + } + + @Test + void should_know_hisName() { + final FormatKeyword keyword = new FormatKeyword(new Formats().findByName("unknow")); + + assertThat(keyword.hasName("test"), is(false)); + assertThat(keyword.hasName("format"), is(true)); + } + + @Test + void should_by_an_annotation_and_assertion() { + assertThat( + new FormatKeyword(new Formats().findByName("unknow")).categories(), + containsInAnyOrder(Keyword.KeywordCategory.ANNOTATION, Keyword.KeywordCategory.ASSERTION) + ); + } + + @Test + void should_retrun_name_of_format() { + assertThat( + new FormatKeyword(new Formats().findByName("date-time")).asAnnotation().valueFor(Json.createValue("")), + is(Json.createValue("date-time")) + ); + } + + @Test + void should_be_printable() { + assertThat( + new FormatKeyword(new Formats().findByName("unknow")).printOn(new HashMapMedia()), + hasEntry(is("format"), is("unknow")) + ); + } +} diff --git a/vocabulary/format-assertion/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatsTest.java b/vocabulary/format-assertion/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatsTest.java new file mode 100644 index 0000000..4dec5bd --- /dev/null +++ b/vocabulary/format-assertion/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/formatassertion/FormatsTest.java @@ -0,0 +1,62 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema.vocabulary.formatassertion; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import org.junit.jupiter.api.Test; + +class FormatsTest { + + @Test + void should_return_unknown() { + assertThat(new Formats().findByName("unknow").name(), is("unknow")); + assertThat(new Formats().findByName("unknow").applyTo("value"), is(false)); + } + + @Test + void should_found_dateTimeFormat() { + assertThat(new Formats().findByName("date-time").applyTo("2024-11-29T00:05:00+01:00"), is(true)); + assertThat(new Formats().findByName("date-time").applyTo("noDateTime"), is(false)); + } + + @Test + void should_found_dateFormat() { + assertThat(new Formats().findByName("date").applyTo("2024-11-29"), is(true)); + assertThat(new Formats().findByName("date").applyTo("noDate"), is(false)); + } + + @Test + void should_found_timeFormat() { + assertThat(new Formats().findByName("time").applyTo("00:05:00.0182Z"), is(true)); + assertThat(new Formats().findByName("time").applyTo("noTime"), is(false)); + } + + @Test + void should_found_durationFormat() { + assertThat(new Formats().findByName("duration").applyTo("PT1H10M"), is(true)); + assertThat(new Formats().findByName("duration").applyTo("noDuration"), is(false)); + } +} diff --git a/vocabulary/format-assertion/src/test/java/module-info.java b/vocabulary/format-assertion/src/test/java/module-info.java new file mode 100644 index 0000000..e4e0dc4 --- /dev/null +++ b/vocabulary/format-assertion/src/test/java/module-info.java @@ -0,0 +1,38 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +open module io.github.sebastiantoepfer.jsonschema.vocabulary.formatassertion { + requires io.github.sebastiantoepfer.jsonschema; + requires io.github.sebastiantoepfer.jsonschema.vocabulary.spi; + requires io.github.sebastiantoepfer.ddd.common; + requires jakarta.json; + + requires org.junit.jupiter.api; + requires org.junit.jupiter.params; + requires org.junit.jupiter.engine; + requires org.hamcrest; + requires hamcrest.optional; + + requires io.github.sebastiantoepfer.ddd.media.core; +} diff --git a/vocabulary/pom.xml b/vocabulary/pom.xml new file mode 100644 index 0000000..e888e99 --- /dev/null +++ b/vocabulary/pom.xml @@ -0,0 +1,22 @@ + + + + 4.0.0 + + + io.github.sebastian-toepfer.json-schema + json-schema + 0.4.0-SNAPSHOT + + + io.github.sebastian-toepfer.json-schema.vocabulary + json-schema-vocabulary + Json Schema :: vocabulary + pom + + + format-assertion + +