From 5992fb8f383aece488089e73e1ddedab3ca2c2fa Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Thu, 8 Feb 2024 09:57:52 -0600 Subject: [PATCH] Add csv output format to validate command You can now pass `--validation-format text|csv` to the validate command to get CSV or text output. Text continues to be the default when not specified. --- .../smithy/cli/commands/ModelBuilder.java | 11 ++- .../smithy/cli/commands/ValidateCommand.java | 1 + .../ValidationEventFormatOptions.java | 98 +++++++++++++++++++ .../cli/commands/ValidateCommandTest.java | 35 ++++++- 4 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ValidationEventFormatOptions.java diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ModelBuilder.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ModelBuilder.java index de67ab126b7..3db1d4f49c8 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ModelBuilder.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ModelBuilder.java @@ -130,6 +130,13 @@ public Model build() { : new ValidatorOptions(); resolveMinSeverity(standardOptions, validatorOptions); + // Determine how to format the output, whether it's text (the default) or CSV. + // Only some commands (like validate) actually let you customize the output format, so assume a default. + ValidationEventFormatOptions formatOptions = arguments.hasReceiver(ValidationEventFormatOptions.class) + ? arguments.getReceiver(ValidationEventFormatOptions.class) + : new ValidationEventFormatOptions(); + ValidationEventFormatOptions.ValidationFormat validationOutputFormat = formatOptions.format(); + ClassLoader classLoader = env.classLoader(); ColorFormatter colors = env.colors(); CliPrinter stderr = env.stderr(); @@ -180,13 +187,15 @@ public Model build() { .titleLabel(titleLabel, titleLabelStyles) .build(); + validationOutputFormat.beginPrinting(validationPrinter); for (ValidationEvent event : sortedEvents) { // Only log events that are >= --severity. Note that setting --quiet inherently // configures events to need to be >= DANGER. Also filter using --show-validators and --hide-validators. if (validatorOptions.isVisible(event)) { - validationPrinter.println(formatter.format(event)); + validationOutputFormat.print(validationPrinter, formatter, event); } } + validationOutputFormat.endPrinting(validationPrinter); env.flush(); // Note: disabling validation will still show a summary of failures if the model can't be loaded. 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 34c7ea5ea52..aca6d6a49f0 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 @@ -48,6 +48,7 @@ public int execute(Arguments arguments, Env env) { arguments.addReceiver(new DiscoveryOptions()); arguments.addReceiver(new ValidatorOptions()); arguments.addReceiver(new BuildOptions()); + arguments.addReceiver(new ValidationEventFormatOptions()); CommandAction action = HelpActionWrapper.fromCommand( this, diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ValidationEventFormatOptions.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ValidationEventFormatOptions.java new file mode 100644 index 00000000000..7c031c102cc --- /dev/null +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ValidationEventFormatOptions.java @@ -0,0 +1,98 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.cli.commands; + +import java.util.function.Consumer; +import software.amazon.smithy.cli.ArgumentReceiver; +import software.amazon.smithy.cli.CliError; +import software.amazon.smithy.cli.CliPrinter; +import software.amazon.smithy.cli.HelpPrinter; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.validation.ValidationEvent; +import software.amazon.smithy.model.validation.ValidationEventFormatter; + +final class ValidationEventFormatOptions implements ArgumentReceiver { + + private static final String VALIDATION_FORMAT = "--validation-format"; + + enum ValidationFormat { + TEXT { + @Override + void print(CliPrinter printer, ValidationEventFormatter formatter, ValidationEvent event) { + printer.println(formatter.format(event)); + } + }, + + CSV { + @Override + void beginPrinting(CliPrinter printer) { + printer.println("severity,id,shape,file,message,hint,suppressionReason"); + } + + @Override + void print(CliPrinter printer, ValidationEventFormatter formatter, ValidationEvent event) { + printer.println( + String.format("\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"", + event.getSeverity().toString(), + formatCsv(event.getId()), + event.getShapeId().map(ShapeId::toString).orElse(""), + formatCsv( + event.getSourceLocation().getFilename() + + ':' + event.getSourceLocation().getLine() + + ':' + event.getSourceLocation().getColumn()), + formatCsv(event.getMessage()), + formatCsv(event.getHint().orElse("")), + formatCsv(event.getSuppressionReason().orElse("")))); + } + }; + + void beginPrinting(CliPrinter printer) {} + + abstract void print(CliPrinter printer, ValidationEventFormatter formatter, ValidationEvent event); + + void endPrinting(CliPrinter printer) {} + + private static String formatCsv(String value) { + // Replace DQUOTE with DQUOTEDQUOTE and escape newlines. + return value.replace("\"", "\"\"").replace("\n", "\\n"); + } + } + + private ValidationFormat format = ValidationFormat.TEXT; + + @Override + public void registerHelp(HelpPrinter printer) { + printer.param(VALIDATION_FORMAT, null, "text|csv", + "Specifies the format to write validation events (text or csv). Defaults to text."); + } + + @Override + public Consumer testParameter(String name) { + if (name.equals("--validation-format")) { + return s -> { + switch (s) { + case "csv": + format(ValidationFormat.CSV); + break; + case "text": + format(ValidationFormat.TEXT); + break; + default: + throw new CliError("Unexpected " + VALIDATION_FORMAT + ": `" + s + "`"); + } + }; + } + return null; + } + + void format(ValidationFormat format) { + this.format = format; + } + + ValidationFormat format() { + return format; + } +} diff --git a/smithy-cli/src/test/java/software/amazon/smithy/cli/commands/ValidateCommandTest.java b/smithy-cli/src/test/java/software/amazon/smithy/cli/commands/ValidateCommandTest.java index 942ead46723..b0fc88e7cf0 100644 --- a/smithy-cli/src/test/java/software/amazon/smithy/cli/commands/ValidateCommandTest.java +++ b/smithy-cli/src/test/java/software/amazon/smithy/cli/commands/ValidateCommandTest.java @@ -18,7 +18,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import java.net.URISyntaxException; @@ -216,4 +215,38 @@ public void canHideEventsById() throws Exception { assertThat(result.stdout(), not(containsString("EmitDangers"))); assertThat(result.stdout(), containsString("HttpLabelTrait")); } + + @Test + public void canOutputCsv() throws Exception { + Path validationEventsModel = Paths.get(getClass().getResource("validation-events.smithy").toURI()); + CliUtils.Result result = CliUtils.runSmithy("validate", "--validation-format", "csv", + validationEventsModel.toString()); + + assertThat(result.code(), not(0)); + assertThat(result.stdout(), containsString("suppressionReason")); + assertThat(result.stdout(), containsString("EmitWarnings")); + assertThat(result.stdout(), containsString("EmitDangers")); + assertThat(result.stdout(), containsString("HttpLabelTrait")); + } + + @Test + public void canOutputText() throws Exception { + Path validationEventsModel = Paths.get(getClass().getResource("validation-events.smithy").toURI()); + CliUtils.Result result = CliUtils.runSmithy("validate", "--validation-format", "text", + validationEventsModel.toString()); + + assertThat(result.code(), not(0)); + assertThat(result.stdout(), not(containsString("suppressionReason"))); + assertThat(result.stdout(), containsString("EmitWarnings")); + assertThat(result.stdout(), containsString("EmitDangers")); + assertThat(result.stdout(), containsString("HttpLabelTrait")); + } + + @Test + public void outputFormatMustBeValid() { + CliUtils.Result result = CliUtils.runSmithy("validate", "--validation-format", "HELLO"); + + assertThat(result.code(), not(0)); + assertThat(result.stderr(), containsString("Unexpected --validation-format: `HELLO`")); + } }