From 725743b40ddb89ddc3904b34ced536a6c81c28ed Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Sat, 9 May 2020 13:56:46 -0700 Subject: [PATCH] Add select command This commit adds the select command to the CLI. This command is used to print out the shapes in a model that match a selector, including the ability to print out a JSON document containing the shape and the variables captured when the shape was matched. Several commands and bits of functionality were cleaned up to make it easier to control writing things like validation to stderr or stdout. When using the select command, only the matched shapes or JSON output should be written to stdout, whereas validation info should be written to stderr. This commit also better organizes the output of --help so that arguments available to all commands and that aren't as important as command-specific arguments are shown at the end of the argument list rather than the beginning. --- .../java/software/amazon/smithy/cli/Cli.java | 20 ++- .../software/amazon/smithy/cli/Colors.java | 23 ++- .../software/amazon/smithy/cli/Parser.java | 11 +- .../software/amazon/smithy/cli/SmithyCli.java | 4 +- .../smithy/cli/commands/BuildCommand.java | 19 +-- .../smithy/cli/commands/CommandUtils.java | 29 +++- .../smithy/cli/commands/SelectCommand.java | 112 ++++++++++++++ .../smithy/cli/commands/ValidateCommand.java | 15 +- .../amazon/smithy/cli/commands/Validator.java | 46 ++++-- .../cli/commands/SelectCommandTest.java | 144 ++++++++++++++++++ .../smithy/cli/commands/valid-model.smithy | 2 +- 11 files changed, 362 insertions(+), 63 deletions(-) create mode 100644 smithy-cli/src/main/java/software/amazon/smithy/cli/commands/SelectCommand.java create mode 100644 smithy-cli/src/test/java/software/amazon/smithy/cli/commands/SelectCommandTest.java diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/Cli.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/Cli.java index dd930f4a867..1c56e81ca72 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/Cli.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/Cli.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -170,6 +170,24 @@ public static void setStderr(Consumer printer) { stderr = printer; } + /** + * Gets the stdout consumer. + * + * @return Returns the stdout consumer. + */ + public static Consumer getStdout() { + return stdout; + } + + /** + * Gets the stderr consumer. + * + * @return Returns the stderr consumer. + */ + public static Consumer getStderr() { + return stderr; + } + /** * Write a line of text to the configured STDOUT. * diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/Colors.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/Colors.java index fd7295459a0..258e6b33d18 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/Colors.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/Colors.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ package software.amazon.smithy.cli; +import java.util.function.Consumer; import software.amazon.smithy.utils.SmithyUnstableApi; /** @@ -62,11 +63,7 @@ public enum Colors { * @param message Message to print. */ public void out(String message) { - if (Cli.useAnsiColors) { - Cli.stdout(format(message)); - } else { - Cli.stdout(message); - } + write(Cli.getStdout(), message); } /** @@ -75,10 +72,20 @@ public void out(String message) { * @param message Message to print. */ public void err(String message) { + write(Cli.getStderr(), message); + } + + /** + * Writes the color output to the given consumer. + * + * @param consumer Consume to invoke. + * @param message Message to write. + */ + public void write(Consumer consumer, String message) { if (Cli.useAnsiColors) { - Cli.stderr(format(message)); + consumer.accept(format(message)); } else { - Cli.stderr(message); + consumer.accept(message); } } diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/Parser.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/Parser.java index 2f7a3d0ea1c..5e8688f3ebf 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/Parser.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/Parser.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -246,18 +246,17 @@ public static final class Builder implements SmithyBuilder { private String positionalHelp; private List arguments = new ArrayList<>(); - private Builder() { + @Override + public Parser build() { // Always include --help, --debug, --stacktrace, and --no-color options; and --logging X. + // This is done during build to move them to the end. Note that this could duplicate + // arguments if a builder is reused, but that seems highly unlikely. option(Cli.HELP, "-h", "Print this help"); option(Cli.DEBUG, "Display debug information"); option(Cli.STACKTRACE, "Display a stacktrace on error"); option(Cli.NO_COLOR, "Explicitly disable ANSI colors"); option(Cli.FORCE_COLOR, "Explicitly enables ANSI colors"); parameter(Cli.LOGGING, "Sets the log level to one of OFF, SEVERE, WARNING, INFO, FINE, ALL"); - } - - @Override - public Parser build() { return new Parser(this); } diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/SmithyCli.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/SmithyCli.java index cfb49a34c00..0358f60f2ed 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/SmithyCli.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/SmithyCli.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import java.util.List; import software.amazon.smithy.cli.commands.BuildCommand; import software.amazon.smithy.cli.commands.DiffCommand; +import software.amazon.smithy.cli.commands.SelectCommand; import software.amazon.smithy.cli.commands.ValidateCommand; /** @@ -86,6 +87,7 @@ public void run(String... args) { cli.addCommand(new ValidateCommand()); cli.addCommand(new BuildCommand()); cli.addCommand(new DiffCommand()); + cli.addCommand(new SelectCommand()); cli.run(args); } } diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/BuildCommand.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/BuildCommand.java index c9234b86cd4..a61b9edcc80 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/BuildCommand.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/BuildCommand.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -39,9 +39,8 @@ import software.amazon.smithy.cli.Parser; import software.amazon.smithy.cli.SmithyCli; import software.amazon.smithy.model.Model; -import software.amazon.smithy.model.loader.ModelAssembler; import software.amazon.smithy.model.validation.Severity; -import software.amazon.smithy.model.validation.ValidatedResult; +import software.amazon.smithy.utils.SetUtils; import software.amazon.smithy.utils.SmithyInternalApi; @SmithyInternalApi @@ -106,8 +105,8 @@ public void execute(Arguments arguments, ClassLoader classLoader) { SmithyBuildConfig smithyBuildConfig = configBuilder.build(); - // Build the model and fail if there are errors. - Model model = buildModel(classLoader, models, arguments); + // Build the model and fail if there are errors. Prints errors to stdout. + Model model = CommandUtils.buildModel(arguments, classLoader, SetUtils.of(Validator.Feature.STDOUT)); SmithyBuild smithyBuild = SmithyBuild.create(classLoader) .config(smithyBuildConfig) @@ -147,16 +146,6 @@ public void execute(Arguments arguments, ClassLoader classLoader) { } } - private Model buildModel(ClassLoader classLoader, List models, Arguments arguments) { - ModelAssembler assembler = CommandUtils.createModelAssembler(classLoader); - CommandUtils.handleModelDiscovery(arguments, assembler, classLoader); - CommandUtils.handleUnknownTraitsOption(arguments, assembler); - models.forEach(assembler::addImport); - ValidatedResult result = assembler.assemble(); - Validator.validate(result); - return result.getResult().orElseThrow(() -> new RuntimeException("No result; expected Validator to throw")); - } - private static final class ResultConsumer implements Consumer, BiConsumer { List failedProjections = Collections.synchronizedList(new ArrayList<>()); AtomicInteger artifactCount = new AtomicInteger(); diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/CommandUtils.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/CommandUtils.java index 1449edf2046..52fcd3c0ec7 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/CommandUtils.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/CommandUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -21,30 +21,45 @@ import java.nio.file.Paths; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.List; +import java.util.Set; import java.util.logging.Logger; import software.amazon.smithy.cli.Arguments; import software.amazon.smithy.cli.CliError; import software.amazon.smithy.cli.SmithyCli; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.loader.ModelAssembler; +import software.amazon.smithy.model.validation.ValidatedResult; final class CommandUtils { + private static final Logger LOGGER = Logger.getLogger(CommandUtils.class.getName()); private CommandUtils() {} - static void handleUnknownTraitsOption(Arguments arguments, ModelAssembler assembler) { - if (arguments.has(SmithyCli.ALLOW_UNKNOWN_TRAITS)) { - LOGGER.fine("Ignoring unknown traits"); - assembler.putProperty(ModelAssembler.ALLOW_UNKNOWN_TRAITS, true); - } + static Model buildModel(Arguments arguments, ClassLoader classLoader, Set features) { + List models = arguments.positionalArguments(); + ModelAssembler assembler = CommandUtils.createModelAssembler(classLoader); + CommandUtils.handleModelDiscovery(arguments, assembler, classLoader); + CommandUtils.handleUnknownTraitsOption(arguments, assembler); + models.forEach(assembler::addImport); + ValidatedResult result = assembler.assemble(); + Validator.validate(result, features); + return result.getResult().orElseThrow(() -> new RuntimeException("Expected Validator to throw")); } static ModelAssembler createModelAssembler(ClassLoader classLoader) { return Model.assembler(classLoader).putProperty(ModelAssembler.DISABLE_JAR_CACHE, true); } - static void handleModelDiscovery(Arguments arguments, ModelAssembler assembler, ClassLoader baseLoader) { + private static void handleUnknownTraitsOption(Arguments arguments, ModelAssembler assembler) { + if (arguments.has(SmithyCli.ALLOW_UNKNOWN_TRAITS)) { + LOGGER.fine("Ignoring unknown traits"); + assembler.putProperty(ModelAssembler.ALLOW_UNKNOWN_TRAITS, true); + } + } + + private static void handleModelDiscovery(Arguments arguments, ModelAssembler assembler, ClassLoader baseLoader) { if (arguments.has(SmithyCli.DISCOVER_CLASSPATH)) { discoverModelsWithClasspath(arguments, assembler); } else if (arguments.has(SmithyCli.DISCOVER)) { diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/SelectCommand.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/SelectCommand.java new file mode 100644 index 00000000000..939c72b6165 --- /dev/null +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/SelectCommand.java @@ -0,0 +1,112 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.cli.commands; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; +import software.amazon.smithy.cli.Arguments; +import software.amazon.smithy.cli.Cli; +import software.amazon.smithy.cli.Command; +import software.amazon.smithy.cli.Parser; +import software.amazon.smithy.cli.SmithyCli; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.SourceLocation; +import software.amazon.smithy.model.node.ArrayNode; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.selector.Selector; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.utils.IoUtils; +import software.amazon.smithy.utils.SetUtils; +import software.amazon.smithy.utils.SmithyInternalApi; + +@SmithyInternalApi +public final class SelectCommand implements Command { + @Override + public String getName() { + return "select"; + } + + @Override + public String getSummary() { + return "Queries a model using a selector"; + } + + @Override + public String getHelp() { + return "This command prints the shapes in a model that match a selector. The\n" + + "selector can be passed in using --selector or through stdin.\n\n" + + "By default, each matching shape ID is printed to stdout on a new line.\n" + + "Pass --vars to print out a JSON array that contains a 'shape' and 'vars'\n" + + "property, where the 'vars' property is a map of each variable that was\n" + + "captured when the shape was matched."; + } + + @Override + public Parser getParser() { + return Parser.builder() + .parameter("--selector", "The Smithy selector to execute. Reads from STDIN when not provided.") + .option("--vars", "Include the variables that were captured when the shape was matched. Uses JSON.") + .option(SmithyCli.ALLOW_UNKNOWN_TRAITS, "Ignores unknown traits when validating models") + .option(SmithyCli.DISCOVER, "-d", "Enables model discovery, merging in models found inside of jars") + .parameter(SmithyCli.DISCOVER_CLASSPATH, "Enables model discovery using a custom classpath for models") + .positional("", "Path to Smithy models or directories") + .build(); + } + + @Override + public void execute(Arguments arguments, ClassLoader classLoader) { + // Get the selector from --selector or from STDIN/ + Selector selector = arguments.has("--selector") + ? Selector.parse(arguments.parameter("--selector")) + : Selector.parse(IoUtils.toUtf8String(System.in)); + + // Don't write the summary to STDOUT, but do write errors to STDERR. + Model model = CommandUtils.buildModel(arguments, classLoader, SetUtils.of(Validator.Feature.QUIET)); + + if (!arguments.has("--vars")) { + sortShapeIds(selector.select(model)).forEach(Cli::stdout); + } else { + // Show the JSON output for writing with --vars. + List result = new ArrayList<>(); + selector.runner().model(model).selectMatches((shape, vars) -> { + result.add(Node.objectNodeBuilder() + .withMember("shape", Node.from(shape.getId().toString())) + .withMember("vars", collectVars(vars)) + .build()); + }); + Cli.stdout(Node.prettyPrintJson(new ArrayNode(result, SourceLocation.NONE))); + } + } + + private Stream sortShapeIds(Collection shapes) { + return shapes.stream().map(Shape::getId).map(ShapeId::toString).sorted(); + } + + private ObjectNode collectVars(Map> vars) { + ObjectNode.Builder varBuilder = Node.objectNodeBuilder(); + for (Map.Entry> varEntry : vars.entrySet()) { + ArrayNode value = sortShapeIds(varEntry.getValue()).map(Node::from).collect(ArrayNode.collect()); + varBuilder.withMember(varEntry.getKey(), value); + } + return varBuilder.build(); + } +} diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ValidateCommand.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ValidateCommand.java index fe4b98b4d77..d6f5bd39919 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ValidateCommand.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ValidateCommand.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -21,9 +21,7 @@ import software.amazon.smithy.cli.Command; import software.amazon.smithy.cli.Parser; import software.amazon.smithy.cli.SmithyCli; -import software.amazon.smithy.model.Model; -import software.amazon.smithy.model.loader.ModelAssembler; -import software.amazon.smithy.model.validation.ValidatedResult; +import software.amazon.smithy.utils.SetUtils; import software.amazon.smithy.utils.SmithyInternalApi; @SmithyInternalApi @@ -52,14 +50,7 @@ public Parser getParser() { public void execute(Arguments arguments, ClassLoader classLoader) { List models = arguments.positionalArguments(); Colors.BRIGHT_WHITE.out(String.format("Validating Smithy model sources: %s", models)); - - ModelAssembler assembler = CommandUtils.createModelAssembler(classLoader); - CommandUtils.handleModelDiscovery(arguments, assembler, classLoader); - CommandUtils.handleUnknownTraitsOption(arguments, assembler); - - models.forEach(assembler::addImport); - ValidatedResult modelResult = assembler.assemble(); - Validator.validate(modelResult); + CommandUtils.buildModel(arguments, classLoader, SetUtils.of()); Colors.BRIGHT_BOLD_GREEN.out("Smithy validation complete"); } } diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/Validator.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/Validator.java index ae4b355594b..94428f973fa 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/Validator.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/Validator.java @@ -17,6 +17,8 @@ import static java.lang.String.format; +import java.util.Set; +import java.util.function.Consumer; import software.amazon.smithy.cli.Cli; import software.amazon.smithy.cli.CliError; import software.amazon.smithy.cli.Colors; @@ -29,35 +31,55 @@ * Shares logic for validating a model and printing out events. */ final class Validator { + private Validator() {} - static void validate(ValidatedResult result) { + /** + * Validation features. + */ + enum Feature { + /** Shows validation events, but does not show the summary. */ + QUIET, + + /** Writes validation events to STDOUT instead of stderr. */ + STDOUT + } + + static void validate(ValidatedResult result, Set features) { ContextualValidationEventFormatter formatter = new ContextualValidationEventFormatter(); + boolean stdout = features.contains(Feature.STDOUT); + boolean quiet = features.contains(Feature.QUIET); + Consumer writer = stdout ? Cli.getStdout() : Cli.getStderr(); + result.getValidationEvents().stream() .filter(event -> event.getSeverity() != Severity.SUPPRESSED) .sorted() .forEach(event -> { if (event.getSeverity() == Severity.WARNING) { - Colors.YELLOW.out(formatter.format(event)); + Colors.YELLOW.write(writer, formatter.format(event)); } else if (event.getSeverity() == Severity.DANGER || event.getSeverity() == Severity.ERROR) { - Colors.RED.out(formatter.format(event)); + Colors.RED.write(writer, formatter.format(event)); } else { - Cli.stdout(event); + writer.accept(event.toString()); } - Cli.stdout(""); + writer.accept(""); }); long errors = result.getValidationEvents(Severity.ERROR).size(); long dangers = result.getValidationEvents(Severity.DANGER).size(); - String line = format( - "Validation result: %s ERROR(s), %d DANGER(s), %d WARNING(s), %d NOTE(s)", - errors, dangers, result.getValidationEvents(Severity.WARNING).size(), - result.getValidationEvents(Severity.NOTE).size()); - Cli.stdout(line); - result.getResult().ifPresent(model -> Cli.stdout(String.format( - "Validated %d shapes in model", model.shapes().count()))); + if (!quiet) { + String line = format( + "Validation result: %s ERROR(s), %d DANGER(s), %d WARNING(s), %d NOTE(s)", + errors, dangers, result.getValidationEvents(Severity.WARNING).size(), + result.getValidationEvents(Severity.NOTE).size()); + writer.accept(line); + + result.getResult().ifPresent(model -> { + writer.accept(String.format("Validated %d shapes in model", model.shapes().count())); + }); + } if (!result.getResult().isPresent() || errors + dangers > 0) { // Show the error and danger severity events. diff --git a/smithy-cli/src/test/java/software/amazon/smithy/cli/commands/SelectCommandTest.java b/smithy-cli/src/test/java/software/amazon/smithy/cli/commands/SelectCommandTest.java new file mode 100644 index 00000000000..a15fbd01fad --- /dev/null +++ b/smithy-cli/src/test/java/software/amazon/smithy/cli/commands/SelectCommandTest.java @@ -0,0 +1,144 @@ +package software.amazon.smithy.cli.commands; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.PrintStream; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.cli.CliError; +import software.amazon.smithy.cli.SmithyCli; +import software.amazon.smithy.model.node.ArrayNode; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; + +public class SelectCommandTest { + @Test + public void hasSelectCommand() throws Exception { + PrintStream out = System.out; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PrintStream printStream = new PrintStream(outputStream); + System.setOut(printStream); + SmithyCli.create().run("select", "--help"); + System.setOut(out); + String help = outputStream.toString("UTF-8"); + + assertThat(help, containsString("Queries")); + } + + @Test + public void dumpsOutValidationErrorsAndFails() throws Exception { + PrintStream out = System.out; + PrintStream err = System.err; + + ByteArrayOutputStream errStream = new ByteArrayOutputStream(); + PrintStream errPrintStream = new PrintStream(errStream); + System.setErr(errPrintStream); + + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + PrintStream outPrintStream = new PrintStream(outStream); + System.setOut(outPrintStream); + + CliError e = Assertions.assertThrows(CliError.class, () -> { + String model = getClass().getResource("unknown-trait.smithy").getPath(); + SmithyCli.create().run("select", "--selector", "string", model); + }); + + System.setOut(out); + System.setErr(err); + + String outputString = outStream.toString("UTF-8"); + String errorString = errStream.toString("UTF-8"); + + // STDERR has the validation events. + assertThat(errorString, containsString("Unable to resolve trait")); + + // STDOUT has the fatal error message + assertThat(outputString, containsString("The model is invalid")); + assertThat(e.getMessage(), containsString("The model is invalid")); + } + + @Test + public void printsSuccessfulMatchesToStdout() throws Exception { + String model = getClass().getResource("valid-model.smithy").getPath(); + + PrintStream out = System.out; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PrintStream printStream = new PrintStream(outputStream); + System.setOut(printStream); + SmithyCli.create().run("select", "--selector", "string", model); + System.setOut(out); + String output = outputStream.toString("UTF-8"); + + // This string shape should have matched. + assertThat(output, containsString("smithy.example#FooId")); + // Check that other shapes were not included. + assertThat(output, not(containsString("smithy.example#GetFooOutput"))); + } + + @Test + public void printsJsonVarsToStdout() throws Exception { + String model = getClass().getResource("valid-model.smithy").getPath(); + + // Take over stdout. + PrintStream out = System.out; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PrintStream printStream = new PrintStream(outputStream); + System.setOut(printStream); + + try { + SmithyCli.create().run("select", "--selector", "string $referenceMe(<)", "--vars", model); + } finally { + System.setOut(out); + } + + validateSelectorOutput(outputStream.toString("UTF-8")); + } + + @Test + public void readsSelectorFromStdinToo() throws Exception { + String model = getClass().getResource("valid-model.smithy").getPath(); + + // Send the selector through input stream. + InputStream in = System.in; + System.setIn(new ByteArrayInputStream("string $referenceMe(<)".getBytes())); + + // Take over stdout. + PrintStream out = System.out; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PrintStream printStream = new PrintStream(outputStream); + System.setOut(printStream); + + try { + // Note that --selector is omitted. + SmithyCli.create().run("select", "--vars", model); + } finally { + // Restore stdout and stdin. + System.setIn(in); + System.setOut(out); + } + + validateSelectorOutput(outputStream.toString("UTF-8")); + } + + private void validateSelectorOutput(String output) { + // The output must be valid JSON. + Node node = Node.parse(output); + + // Validate the contents. + ArrayNode array = node.expectArrayNode(); + for (Node element : array.getElements()) { + ObjectNode object = element.expectObjectNode(); + object.expectStringMember("shape"); + ObjectNode vars = object.expectObjectMember("vars"); + // Each variable is an array of shape IDs. + for (Node reference : vars.expectArrayMember("referenceMe").getElements()) { + reference.expectStringNode().expectShapeId(); + } + } + } +} diff --git a/smithy-cli/src/test/resources/software/amazon/smithy/cli/commands/valid-model.smithy b/smithy-cli/src/test/resources/software/amazon/smithy/cli/commands/valid-model.smithy index 4205635c31d..2a8ec838042 100644 --- a/smithy-cli/src/test/resources/software/amazon/smithy/cli/commands/valid-model.smithy +++ b/smithy-cli/src/test/resources/software/amazon/smithy/cli/commands/valid-model.smithy @@ -1,7 +1,7 @@ namespace smithy.example resource Foo { - identifier: {id: FooId}, + identifiers: {id: FooId}, read: GetFoo, }