From 1fdd0ae646fa1c9abff625b7839f95445d68da15 Mon Sep 17 00:00:00 2001 From: kstich Date: Mon, 6 Nov 2023 14:00:22 -0800 Subject: [PATCH] Add NodeValidationVisitor plugin for @uniqueItems --- .../validation/node/NodeValidatorPlugin.java | 3 +- .../validation/node/UniqueItemsPlugin.java | 47 +++++++++++++++++++ .../validation/NodeValidationVisitorTest.java | 7 +++ .../model/validation/node-validator.json | 9 ++++ 4 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 smithy-model/src/main/java/software/amazon/smithy/model/validation/node/UniqueItemsPlugin.java diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/NodeValidatorPlugin.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/NodeValidatorPlugin.java index 3556d13eff6..5398f3529c3 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/NodeValidatorPlugin.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/NodeValidatorPlugin.java @@ -64,7 +64,8 @@ static List getBuiltins() { new PatternTraitPlugin(), new RangeTraitPlugin(), new StringEnumPlugin(), - new StringLengthPlugin()); + new StringLengthPlugin(), + new UniqueItemsPlugin()); } /** diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/UniqueItemsPlugin.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/UniqueItemsPlugin.java new file mode 100644 index 00000000000..99139e5a451 --- /dev/null +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/UniqueItemsPlugin.java @@ -0,0 +1,47 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.model.validation.node; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import software.amazon.smithy.model.node.ArrayNode; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.shapes.CollectionShape; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.traits.UniqueItemsTrait; + +/** + * Validates that items in lists with the `@uniqueItems` trait are unique. + */ +final class UniqueItemsPlugin extends MemberAndShapeTraitPlugin { + UniqueItemsPlugin() { + super(CollectionShape.class, ArrayNode.class, UniqueItemsTrait.class); + } + + @Override + protected void check(Shape shape, UniqueItemsTrait trait, ArrayNode value, Context context, Emitter emitter) { + Set uniqueNodes = new HashSet<>(value.getElements()); + if (uniqueNodes.size() != value.size()) { + List duplicateNodes = new ArrayList<>(value.getElements()); + for (Node uniqueNode : uniqueNodes) { + // Collections remove the first encountered entry, so our unique + // nodes through a set will leave behind any duplicates. + duplicateNodes.remove(uniqueNode); + } + + Set duplicateValues = new LinkedHashSet<>(); + for (Node duplicateNode : duplicateNodes) { + duplicateValues.add(duplicateNode.toString()); + } + emitter.accept(value, String.format( + "Value provided for `%s` must have unique items, but the following items had multiple entries: " + + "[`%s`]", shape.getId(), String.join("`, `", duplicateValues))); + } + } +} diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/validation/NodeValidationVisitorTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/validation/NodeValidationVisitorTest.java index 185173a3ff5..0912847032c 100644 --- a/smithy-model/src/test/java/software/amazon/smithy/model/validation/NodeValidationVisitorTest.java +++ b/smithy-model/src/test/java/software/amazon/smithy/model/validation/NodeValidationVisitorTest.java @@ -207,6 +207,13 @@ public static Collection data() { {"ns.foo#List", "[10]", new String[] {"0: Expected string value for string shape, `ns.foo#String`; found number value, `10`"}}, {"ns.foo#List", "10", new String[] {"Expected array value for list shape, `ns.foo#List`; found number value, `10`"}}, + // unique list + {"ns.foo#UniqueList", "[\"a\"]", null}, + {"ns.foo#UniqueList", "[\"a\", \"b\"]", null}, + {"ns.foo#UniqueList", "[\"a\", \"a\"]", new String[] {"Value provided for `ns.foo#UniqueList` must have unique items, but the following items had multiple entries: [`a`]"}}, + {"ns.foo#UniqueList", "[\"a\", \"a\", \"a\"]", new String[] {"Value provided for `ns.foo#UniqueList` must have unique items, but the following items had multiple entries: [`a`]"}}, + {"ns.foo#UniqueList", "[\"a\", \"a\", \"b\", \"b\"]", new String[] {"Value provided for `ns.foo#UniqueList` must have unique items, but the following items had multiple entries: [`a`, `b`]"}}, + // map {"ns.foo#Map", "{\"a\":[\"b\"]}", null}, {"ns.foo#Map", "{\"a\":[\"b\"], \"c\":[\"d\"]}", null}, diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/validation/node-validator.json b/smithy-model/src/test/resources/software/amazon/smithy/model/validation/node-validator.json index da895424e24..3b3375b42cc 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/validation/node-validator.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/validation/node-validator.json @@ -133,6 +133,15 @@ } } }, + "ns.foo#UniqueList": { + "type": "list", + "member": { + "target": "ns.foo#String" + }, + "traits": { + "smithy.api#uniqueItems": {} + } + }, "ns.foo#String": { "type": "string" },