From e922ddd80da0862840990fc02bcf2c3bf5a54a6b Mon Sep 17 00:00:00 2001 From: Michael Dowling <mtdowling@gmail.com> Date: Sun, 19 Jan 2020 10:16:45 -0800 Subject: [PATCH] Make various improvements geared towards Gradle/CLI AWS protocol tests: - Use the Gradle plugin to build the AWS protocol tests. SmithyBuild: - Only run parallel projections if there's more than one. SmithyCli (breaking CLI changes): - to allow the writing to stderr and stdout to be completely customized using a Consumer<String>. This allows the Gradle plugin to log writes to stdout rather than rely on the non-thread-safe default behavior of intercepting calls. - Colors nows is used by calling out and err directly on an enum variant rather than a static method. - Adding the --logging parameter to every command and removed the static `configureLogging` method. - Adding `stdout` and `stderr` methods to the Cli. These are now called by Colors when writing. - Enabling disabling ANSI colors is done on Cli and not on Colors now. All of the methods used to influence the CLI globally is now all on the Cli. - Logging is only configured when a logging option is passed in. A custom logger is used that makes calls to the intercepted stderr method. - Running `build` now shows validation results too. SmithyModel: - ValidationEvents are now sorted by filename, line number, column, severity, shape ID, message, then finally the event ID. - Introducing the ValidationEventFormatter interface. There are now implementations of the current display (showing the event on a single line like CheckStyle), and a contextual formatter that shows the line of the source file that has the error. --- README.md | 3 +- .../guides/building-models/gradle-plugin.rst | 6 +- docs/source/guides/converting-to-openapi.rst | 2 +- smithy-aws-protocol-tests/build.gradle.kts | 9 +- .../ec2-query/empty-input-output.smithy | 0 .../ec2-query/input-lists.smithy | 0 .../smithy => model}/ec2-query/input.smithy | 0 .../smithy => model}/ec2-query/main.smithy | 0 .../ec2-query/xml-errors.smithy | 0 .../ec2-query/xml-lists.smithy | 0 .../ec2-query/xml-structs.smithy | 0 .../smithy => model}/json-rpc-1-1/main.json | 0 .../query/empty-input-output.smithy | 0 .../smithy => model}/query/input-lists.smithy | 0 .../smithy => model}/query/input-maps.smithy | 0 .../smithy => model}/query/input.smithy | 0 .../smithy => model}/query/main.smithy | 0 .../smithy => model}/query/xml-errors.smithy | 0 .../smithy => model}/query/xml-lists.smithy | 0 .../smithy => model}/query/xml-maps.smithy | 0 .../smithy => model}/query/xml-structs.smithy | 0 .../rest-json/empty-input-output.smithy | 0 .../rest-json/endpoint-host-trait.smithy | 0 .../smithy => model}/rest-json/errors.smithy | 0 .../rest-json/http-headers.smithy | 0 .../rest-json/http-labels.smithy | 0 .../rest-json/http-payload.smithy | 0 .../rest-json/http-prefix-headers.smithy | 0 .../rest-json/http-query.smithy | 0 .../rest-json/json-lists.smithy | 0 .../rest-json/json-maps.smithy | 0 .../rest-json/json-structs.smithy | 0 .../smithy => model}/rest-json/main.smithy | 0 .../rest-xml/document-lists.smithy | 0 .../rest-xml/document-maps.smithy | 0 .../rest-xml/document-structs.smithy | 0 .../rest-xml/document-xml-attributes.smithy | 0 .../rest-xml/empty-input-output.smithy | 0 .../rest-xml/endpoint-host-trait.smithy | 0 .../smithy => model}/rest-xml/errors.smithy | 0 .../rest-xml/http-headers.smithy | 0 .../rest-xml/http-labels.smithy | 0 .../rest-xml/http-payload.smithy | 0 .../rest-xml/http-prefix-headers.smithy | 0 .../rest-xml/http-query.smithy | 0 .../smithy => model}/rest-xml/main.smithy | 0 .../smithy => model}/shared-types.smithy | 0 .../main/resources/META-INF/smithy/manifest | 43 ----- .../smithy/aws/protocoltests/ModelTest.java | 19 -- .../amazon/smithy/build/SmithyBuildImpl.java | 17 +- .../java/software/amazon/smithy/cli/Cli.java | 179 +++++++++++++----- .../software/amazon/smithy/cli/Colors.java | 49 ++--- .../software/amazon/smithy/cli/Parser.java | 4 +- .../software/amazon/smithy/cli/SmithyCli.java | 15 +- .../smithy/cli/commands/BuildCommand.java | 27 +-- .../smithy/cli/commands/DiffCommand.java | 9 +- .../smithy/cli/commands/ValidateCommand.java | 7 +- .../amazon/smithy/cli/commands/Validator.java | 37 ++-- .../smithy/cli/commands/BuildCommandTest.java | 6 +- .../cli/commands/ValidateCommandTest.java | 8 +- .../ContextualValidationEventFormatter.java | 122 ++++++++++++ .../LineValidationEventFormatter.java | 36 ++++ .../model/validation/ValidationEvent.java | 38 +++- .../validation/ValidationEventFormatter.java | 29 +++ ...ontextualValidationEventFormatterTest.java | 59 ++++++ .../smithy/model/validation/context.smithy | 9 + 66 files changed, 504 insertions(+), 229 deletions(-) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/ec2-query/empty-input-output.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/ec2-query/input-lists.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/ec2-query/input.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/ec2-query/main.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/ec2-query/xml-errors.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/ec2-query/xml-lists.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/ec2-query/xml-structs.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/json-rpc-1-1/main.json (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/query/empty-input-output.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/query/input-lists.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/query/input-maps.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/query/input.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/query/main.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/query/xml-errors.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/query/xml-lists.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/query/xml-maps.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/query/xml-structs.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-json/empty-input-output.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-json/endpoint-host-trait.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-json/errors.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-json/http-headers.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-json/http-labels.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-json/http-payload.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-json/http-prefix-headers.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-json/http-query.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-json/json-lists.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-json/json-maps.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-json/json-structs.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-json/main.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-xml/document-lists.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-xml/document-maps.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-xml/document-structs.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-xml/document-xml-attributes.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-xml/empty-input-output.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-xml/endpoint-host-trait.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-xml/errors.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-xml/http-headers.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-xml/http-labels.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-xml/http-payload.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-xml/http-prefix-headers.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-xml/http-query.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/rest-xml/main.smithy (100%) rename smithy-aws-protocol-tests/{src/main/resources/META-INF/smithy => model}/shared-types.smithy (100%) delete mode 100644 smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/manifest delete mode 100644 smithy-aws-protocol-tests/src/test/java/software/amazon/smithy/aws/protocoltests/ModelTest.java create mode 100644 smithy-model/src/main/java/software/amazon/smithy/model/validation/ContextualValidationEventFormatter.java create mode 100644 smithy-model/src/main/java/software/amazon/smithy/model/validation/LineValidationEventFormatter.java create mode 100644 smithy-model/src/main/java/software/amazon/smithy/model/validation/ValidationEventFormatter.java create mode 100644 smithy-model/src/test/java/software/amazon/smithy/model/validation/ContextualValidationEventFormatterTest.java create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/validation/context.smithy diff --git a/README.md b/README.md index 6763a45513b..d332bfefbcd 100644 --- a/README.md +++ b/README.md @@ -57,8 +57,7 @@ Then, apply the Smithy Gradle Plugin in your `build.gradle.kts` file and run ```kotlin plugins { - java - id("software.amazon.smithy").version("0.4.2") + id("software.amazon.smithy").version("0.4.3") } ``` diff --git a/docs/source/guides/building-models/gradle-plugin.rst b/docs/source/guides/building-models/gradle-plugin.rst index 2b021b31a27..8aedd739a9e 100644 --- a/docs/source/guides/building-models/gradle-plugin.rst +++ b/docs/source/guides/building-models/gradle-plugin.rst @@ -21,7 +21,7 @@ The following example configures a project to use the Smithy Gradle plugin: .. code-tab:: kotlin plugins { - id("software.amazon.smithy").version("0.4.2") + id("software.amazon.smithy").version("0.4.3") } @@ -138,7 +138,7 @@ The following example ``build.gradle.kts`` will build a Smithy model using a .. code-tab:: kotlin plugins { - id("software.amazon.smithy").version("0.4.2") + id("software.amazon.smithy").version("0.4.3") } // The SmithyExtension is used to customize the build. This example @@ -184,7 +184,7 @@ build that uses the "external" projection. .. code-tab:: kotlin plugins { - id("software.amazon.smithy").version("0.4.2") + id("software.amazon.smithy").version("0.4.3") } buildscript { diff --git a/docs/source/guides/converting-to-openapi.rst b/docs/source/guides/converting-to-openapi.rst index 415fc80c2ab..66c61cc397b 100644 --- a/docs/source/guides/converting-to-openapi.rst +++ b/docs/source/guides/converting-to-openapi.rst @@ -101,7 +101,7 @@ specification from a Smithy model using a buildscript dependency: plugins { java - id("software.amazon.smithy").version("0.4.2") + id("software.amazon.smithy").version("0.4.3") } buildscript { diff --git a/smithy-aws-protocol-tests/build.gradle.kts b/smithy-aws-protocol-tests/build.gradle.kts index d1a42fcb428..2ab07378e6e 100644 --- a/smithy-aws-protocol-tests/build.gradle.kts +++ b/smithy-aws-protocol-tests/build.gradle.kts @@ -17,7 +17,12 @@ description = "Defines protocol tests for AWS HTTP protocols." extra["displayName"] = "Smithy :: AWS :: Protocol Tests" extra["moduleName"] = "software.amazon.smithy.aws.protocoltests" +plugins { + id("software.amazon.smithy").version("0.4.3") +} + dependencies { - api(project(":smithy-protocol-test-traits")) - api(project(":smithy-aws-traits")) + compile(project(":smithy-cli")) + implementation(project(":smithy-protocol-test-traits")) + implementation(project(":smithy-aws-traits")) } diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/ec2-query/empty-input-output.smithy b/smithy-aws-protocol-tests/model/ec2-query/empty-input-output.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/ec2-query/empty-input-output.smithy rename to smithy-aws-protocol-tests/model/ec2-query/empty-input-output.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/ec2-query/input-lists.smithy b/smithy-aws-protocol-tests/model/ec2-query/input-lists.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/ec2-query/input-lists.smithy rename to smithy-aws-protocol-tests/model/ec2-query/input-lists.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/ec2-query/input.smithy b/smithy-aws-protocol-tests/model/ec2-query/input.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/ec2-query/input.smithy rename to smithy-aws-protocol-tests/model/ec2-query/input.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/ec2-query/main.smithy b/smithy-aws-protocol-tests/model/ec2-query/main.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/ec2-query/main.smithy rename to smithy-aws-protocol-tests/model/ec2-query/main.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/ec2-query/xml-errors.smithy b/smithy-aws-protocol-tests/model/ec2-query/xml-errors.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/ec2-query/xml-errors.smithy rename to smithy-aws-protocol-tests/model/ec2-query/xml-errors.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/ec2-query/xml-lists.smithy b/smithy-aws-protocol-tests/model/ec2-query/xml-lists.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/ec2-query/xml-lists.smithy rename to smithy-aws-protocol-tests/model/ec2-query/xml-lists.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/ec2-query/xml-structs.smithy b/smithy-aws-protocol-tests/model/ec2-query/xml-structs.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/ec2-query/xml-structs.smithy rename to smithy-aws-protocol-tests/model/ec2-query/xml-structs.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/json-rpc-1-1/main.json b/smithy-aws-protocol-tests/model/json-rpc-1-1/main.json similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/json-rpc-1-1/main.json rename to smithy-aws-protocol-tests/model/json-rpc-1-1/main.json diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/query/empty-input-output.smithy b/smithy-aws-protocol-tests/model/query/empty-input-output.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/query/empty-input-output.smithy rename to smithy-aws-protocol-tests/model/query/empty-input-output.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/query/input-lists.smithy b/smithy-aws-protocol-tests/model/query/input-lists.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/query/input-lists.smithy rename to smithy-aws-protocol-tests/model/query/input-lists.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/query/input-maps.smithy b/smithy-aws-protocol-tests/model/query/input-maps.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/query/input-maps.smithy rename to smithy-aws-protocol-tests/model/query/input-maps.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/query/input.smithy b/smithy-aws-protocol-tests/model/query/input.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/query/input.smithy rename to smithy-aws-protocol-tests/model/query/input.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/query/main.smithy b/smithy-aws-protocol-tests/model/query/main.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/query/main.smithy rename to smithy-aws-protocol-tests/model/query/main.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/query/xml-errors.smithy b/smithy-aws-protocol-tests/model/query/xml-errors.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/query/xml-errors.smithy rename to smithy-aws-protocol-tests/model/query/xml-errors.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/query/xml-lists.smithy b/smithy-aws-protocol-tests/model/query/xml-lists.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/query/xml-lists.smithy rename to smithy-aws-protocol-tests/model/query/xml-lists.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/query/xml-maps.smithy b/smithy-aws-protocol-tests/model/query/xml-maps.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/query/xml-maps.smithy rename to smithy-aws-protocol-tests/model/query/xml-maps.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/query/xml-structs.smithy b/smithy-aws-protocol-tests/model/query/xml-structs.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/query/xml-structs.smithy rename to smithy-aws-protocol-tests/model/query/xml-structs.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/empty-input-output.smithy b/smithy-aws-protocol-tests/model/rest-json/empty-input-output.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/empty-input-output.smithy rename to smithy-aws-protocol-tests/model/rest-json/empty-input-output.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/endpoint-host-trait.smithy b/smithy-aws-protocol-tests/model/rest-json/endpoint-host-trait.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/endpoint-host-trait.smithy rename to smithy-aws-protocol-tests/model/rest-json/endpoint-host-trait.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/errors.smithy b/smithy-aws-protocol-tests/model/rest-json/errors.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/errors.smithy rename to smithy-aws-protocol-tests/model/rest-json/errors.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/http-headers.smithy b/smithy-aws-protocol-tests/model/rest-json/http-headers.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/http-headers.smithy rename to smithy-aws-protocol-tests/model/rest-json/http-headers.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/http-labels.smithy b/smithy-aws-protocol-tests/model/rest-json/http-labels.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/http-labels.smithy rename to smithy-aws-protocol-tests/model/rest-json/http-labels.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/http-payload.smithy b/smithy-aws-protocol-tests/model/rest-json/http-payload.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/http-payload.smithy rename to smithy-aws-protocol-tests/model/rest-json/http-payload.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/http-prefix-headers.smithy b/smithy-aws-protocol-tests/model/rest-json/http-prefix-headers.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/http-prefix-headers.smithy rename to smithy-aws-protocol-tests/model/rest-json/http-prefix-headers.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/http-query.smithy b/smithy-aws-protocol-tests/model/rest-json/http-query.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/http-query.smithy rename to smithy-aws-protocol-tests/model/rest-json/http-query.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/json-lists.smithy b/smithy-aws-protocol-tests/model/rest-json/json-lists.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/json-lists.smithy rename to smithy-aws-protocol-tests/model/rest-json/json-lists.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/json-maps.smithy b/smithy-aws-protocol-tests/model/rest-json/json-maps.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/json-maps.smithy rename to smithy-aws-protocol-tests/model/rest-json/json-maps.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/json-structs.smithy b/smithy-aws-protocol-tests/model/rest-json/json-structs.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/json-structs.smithy rename to smithy-aws-protocol-tests/model/rest-json/json-structs.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/main.smithy b/smithy-aws-protocol-tests/model/rest-json/main.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-json/main.smithy rename to smithy-aws-protocol-tests/model/rest-json/main.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/document-lists.smithy b/smithy-aws-protocol-tests/model/rest-xml/document-lists.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/document-lists.smithy rename to smithy-aws-protocol-tests/model/rest-xml/document-lists.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/document-maps.smithy b/smithy-aws-protocol-tests/model/rest-xml/document-maps.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/document-maps.smithy rename to smithy-aws-protocol-tests/model/rest-xml/document-maps.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/document-structs.smithy b/smithy-aws-protocol-tests/model/rest-xml/document-structs.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/document-structs.smithy rename to smithy-aws-protocol-tests/model/rest-xml/document-structs.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/document-xml-attributes.smithy b/smithy-aws-protocol-tests/model/rest-xml/document-xml-attributes.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/document-xml-attributes.smithy rename to smithy-aws-protocol-tests/model/rest-xml/document-xml-attributes.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/empty-input-output.smithy b/smithy-aws-protocol-tests/model/rest-xml/empty-input-output.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/empty-input-output.smithy rename to smithy-aws-protocol-tests/model/rest-xml/empty-input-output.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/endpoint-host-trait.smithy b/smithy-aws-protocol-tests/model/rest-xml/endpoint-host-trait.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/endpoint-host-trait.smithy rename to smithy-aws-protocol-tests/model/rest-xml/endpoint-host-trait.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/errors.smithy b/smithy-aws-protocol-tests/model/rest-xml/errors.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/errors.smithy rename to smithy-aws-protocol-tests/model/rest-xml/errors.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/http-headers.smithy b/smithy-aws-protocol-tests/model/rest-xml/http-headers.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/http-headers.smithy rename to smithy-aws-protocol-tests/model/rest-xml/http-headers.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/http-labels.smithy b/smithy-aws-protocol-tests/model/rest-xml/http-labels.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/http-labels.smithy rename to smithy-aws-protocol-tests/model/rest-xml/http-labels.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/http-payload.smithy b/smithy-aws-protocol-tests/model/rest-xml/http-payload.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/http-payload.smithy rename to smithy-aws-protocol-tests/model/rest-xml/http-payload.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/http-prefix-headers.smithy b/smithy-aws-protocol-tests/model/rest-xml/http-prefix-headers.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/http-prefix-headers.smithy rename to smithy-aws-protocol-tests/model/rest-xml/http-prefix-headers.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/http-query.smithy b/smithy-aws-protocol-tests/model/rest-xml/http-query.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/http-query.smithy rename to smithy-aws-protocol-tests/model/rest-xml/http-query.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/main.smithy b/smithy-aws-protocol-tests/model/rest-xml/main.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/rest-xml/main.smithy rename to smithy-aws-protocol-tests/model/rest-xml/main.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/shared-types.smithy b/smithy-aws-protocol-tests/model/shared-types.smithy similarity index 100% rename from smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/shared-types.smithy rename to smithy-aws-protocol-tests/model/shared-types.smithy diff --git a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/manifest b/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/manifest deleted file mode 100644 index 26b4a313d9b..00000000000 --- a/smithy-aws-protocol-tests/src/main/resources/META-INF/smithy/manifest +++ /dev/null @@ -1,43 +0,0 @@ -ec2-query/empty-input-output.smithy -ec2-query/input.smithy -ec2-query/input-lists.smithy -ec2-query/main.smithy -ec2-query/xml-errors.smithy -ec2-query/xml-lists.smithy -ec2-query/xml-structs.smithy -json-rpc-1-1/main.json -query/empty-input-output.smithy -query/input.smithy -query/input-lists.smithy -query/input-maps.smithy -query/main.smithy -query/xml-errors.smithy -query/xml-lists.smithy -query/xml-maps.smithy -query/xml-structs.smithy -rest-json/empty-input-output.smithy -rest-json/endpoint-host-trait.smithy -rest-json/errors.smithy -rest-json/http-headers.smithy -rest-json/http-labels.smithy -rest-json/http-payload.smithy -rest-json/http-prefix-headers.smithy -rest-json/http-query.smithy -rest-json/json-lists.smithy -rest-json/json-maps.smithy -rest-json/json-structs.smithy -rest-json/main.smithy -rest-xml/document-lists.smithy -rest-xml/document-maps.smithy -rest-xml/document-structs.smithy -rest-xml/document-xml-attributes.smithy -rest-xml/empty-input-output.smithy -rest-xml/endpoint-host-trait.smithy -rest-xml/errors.smithy -rest-xml/http-headers.smithy -rest-xml/http-labels.smithy -rest-xml/http-payload.smithy -rest-xml/http-prefix-headers.smithy -rest-xml/http-query.smithy -rest-xml/main.smithy -shared-types.smithy diff --git a/smithy-aws-protocol-tests/src/test/java/software/amazon/smithy/aws/protocoltests/ModelTest.java b/smithy-aws-protocol-tests/src/test/java/software/amazon/smithy/aws/protocoltests/ModelTest.java deleted file mode 100644 index 0029dc72fbc..00000000000 --- a/smithy-aws-protocol-tests/src/test/java/software/amazon/smithy/aws/protocoltests/ModelTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package software.amazon.smithy.aws.protocoltests; - -import org.junit.jupiter.api.Test; -import software.amazon.smithy.model.Model; -import software.amazon.smithy.model.validation.ValidatedResult; - -/** - * TODO: fix gradle plugin and remove this code. - */ -public class ModelTest { - @Test - public void loadsModel() { - ValidatedResult<Model> r = Model.assembler() - .discoverModels() - .assemble(); - System.out.println(r.getValidationEvents()); - r.unwrap(); - } -} diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/SmithyBuildImpl.java b/smithy-build/src/main/java/software/amazon/smithy/build/SmithyBuildImpl.java index 31c1754e63c..8b2ea110eff 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/SmithyBuildImpl.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/SmithyBuildImpl.java @@ -189,14 +189,25 @@ void applyAllProjections( } else { parallelProjectionNameOrder.add(name); parallelProjections.add(() -> { - ProjectionResult projectionResult = applyProjection(name, config, resolvedModel); - projectionResultConsumer.accept(projectionResult); + executeSerialProjection(resolvedModel, name, config, + projectionResultConsumer, projectionExceptionConsumer); return null; }); } } - if (!parallelProjections.isEmpty()) { + // Common case of only executing a single plugin per/projection. + if (parallelProjections.size() == 1) { + try { + parallelProjections.get(0).call(); + } catch (Throwable e) { + if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } else { + throw new RuntimeException(e); + } + } + } else if (!parallelProjections.isEmpty()) { executeParallelProjections(parallelProjections, parallelProjectionNameOrder, projectionExceptionConsumer); } } 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 db7ec8e5b01..98a1693b465 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 @@ -22,10 +22,11 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.TreeMap; +import java.util.function.Consumer; import java.util.logging.ConsoleHandler; +import java.util.logging.Formatter; import java.util.logging.Handler; import java.util.logging.Level; -import java.util.logging.LogManager; import java.util.logging.LogRecord; import java.util.logging.Logger; import java.util.logging.SimpleFormatter; @@ -44,7 +45,7 @@ * <li>--no-color: Explicitly disables ANSI colors.</li> * <li>--force-color: Explicitly enables ANSI colors.</li> * <li>--stacktrace: Prints the stacktrace of any CLI exception that is thrown.</li> - * <li>--quiet-logs: Disables writing log messages to STDOUT.</li> + * <li>--logging: Sets the log level to one of OFF, SEVERE, WARNING, INFO, FINE, ALL.</li> * </ul> * * <p>Why are we not using a library for this? Because parsing command line @@ -59,12 +60,19 @@ public final class Cli { public static final String FORCE_COLOR = "--force-color"; public static final String DEBUG = "--debug"; public static final String STACKTRACE = "--stacktrace"; - public static final String QUIET_LOGS = "--quiet-logs"; + public static final String LOGGING = "--logging"; + + /** Configures whether or not to use ANSI colors. */ + static boolean useAnsiColors = isAnsiColorSupported(); + private static final SimpleDateFormat FORMAT = new SimpleDateFormat("HH:mm:ss.SSS"); + // Note that we don't use a method reference here in case System.out or System.err are changed. + private static Consumer<String> stdout = s -> System.out.println(s); + private static Consumer<String> stderr = s -> System.err.println(s); + private final String applicationName; private final ClassLoader classLoader; - private boolean configureLogging; private Map<String, Command> commands = new TreeMap<>(); /** @@ -96,15 +104,6 @@ public void addCommand(Command command) { commands.put(command.getName(), command); } - /** - * Calling this method ensures that logging is configured for the CLI. - * - * @param configureLogging Set to true to configure log formats and levels. - */ - public void configureLogging(boolean configureLogging) { - this.configureLogging = configureLogging; - } - /** * Execute the command line using the given arguments. * @@ -126,17 +125,19 @@ public void run(String[] args) { Command command = commands.get(argument); Parser parser = command.getParser(); Arguments parsedArguments = parser.parse(args, 1); + // Use the --no-color argument to globally disable ANSI colors. if (parsedArguments.has(NO_COLOR)) { - Colors.setUseAnsiColors(false); + setUseAnsiColors(false); } else if (parsedArguments.has(FORCE_COLOR)) { - Colors.setUseAnsiColors(true); + setUseAnsiColors(true); } + // Automatically handle --help output for subcommands. if (parsedArguments.has(HELP)) { printHelp(command, parser); } else { - configureLogging(args); + configureLogging(parsedArguments); command.execute(parsedArguments, classLoader); } } else { @@ -148,37 +149,96 @@ public void run(String[] args) { } } - private void configureLogging(String[] args) { - if (configureLogging && !hasArgument(args, QUIET_LOGS)) { - Handler handler = getConsoleHandler(); - if (hasArgument(args, DEBUG)) { - handler.setFormatter(new DebugFormatter()); - handler.setLevel(Level.ALL); - // Configure logging level of all loggers. - Logger rootLogger = LogManager.getLogManager().getLogger(""); - rootLogger.setLevel(Level.ALL); - for (Handler h : rootLogger.getHandlers()) { - h.setLevel(Level.ALL); - } - } else { - handler.setFormatter(new BasicFormatter()); - handler.setLevel(Level.WARNING); - } - } + /** + * Configures a custom STDOUT printer. + * + * @param printer Consumer responsible for writing to STDOUT. + */ + public static void setStdout(Consumer<String> printer) { + stdout = printer; + } + + /** + * Configures a custom STDERR printer. + * + * @param printer Consumer responsible for writing to STDERR. + */ + public static void setStderr(Consumer<String> printer) { + stderr = printer; + } + + /** + * Write a line of text to the configured STDOUT. + * + * @param message Message to write. + */ + public static void stdout(Object message) { + stdout.accept(String.valueOf(message)); + } + + /** + * Write a line of text to the configured STDERR. + * + * @param message Message to write. + */ + public static void stderr(Object message) { + stderr.accept(String.valueOf(message)); } - private static Handler getConsoleHandler() { + /** + * Explicitly configures whether or not to use ANSI colors. + * + * @param useAnsiColors Set to true or false to enable/disable. + */ + public static void setUseAnsiColors(boolean useAnsiColors) { + Cli.useAnsiColors = useAnsiColors; + } + + /** + * Does a really simple check to see if ANSI colors are supported. + * + * @return Returns true if ANSI probably works. + */ + private static boolean isAnsiColorSupported() { + return System.console() != null && System.getenv().get("TERM") != null; + } + + private void configureLogging(Arguments parsedArgs) { + boolean configureLogging = parsedArgs.has(DEBUG) || parsedArgs.has(LOGGING); + + if (!configureLogging) { + return; + } + + Level level = Level.parse(parsedArgs.parameter(LOGGING, Level.ALL.getName())); + + // Remove any currently present console loggers. Logger rootLogger = Logger.getLogger(""); + removeConsoleHandler(rootLogger); + + if (parsedArgs.has(DEBUG)) { + // Debug ignores the given logging level and just logs everything. + CliLogHandler handler = new CliLogHandler(new DebugFormatter()); + handler.setLevel(Level.ALL); + rootLogger.addHandler(handler); + rootLogger.setLevel(Level.ALL); + for (Handler h : rootLogger.getHandlers()) { + h.setLevel(Level.ALL); + } + } else if (level != Level.OFF) { + CliLogHandler handler = new CliLogHandler(new BasicFormatter()); + handler.setLevel(level); + rootLogger.addHandler(handler); + } + } + private static void removeConsoleHandler(Logger rootLogger) { for (Handler handler : rootLogger.getHandlers()) { if (handler instanceof ConsoleHandler) { - return handler; + // Remove any console log handlers. + rootLogger.removeHandler(handler); } } - - Handler consoleHandler = new ConsoleHandler(); - rootLogger.addHandler(consoleHandler); - return consoleHandler; } private boolean hasArgument(String[] args, String name) { @@ -193,27 +253,26 @@ private boolean hasArgument(String[] args, String name) { private void printException(String[] args, Throwable throwable) { if (hasArgument(args, NO_COLOR)) { - Colors.setUseAnsiColors(false); + setUseAnsiColors(false); } - Colors.out(Colors.BOLD_RED, throwable.getMessage()); + Colors.BOLD_RED.out(throwable.getMessage()); if (hasArgument(args, STACKTRACE)) { StringWriter sw = new StringWriter(); throwable.printStackTrace(new PrintWriter(sw)); String trace = sw.toString(); - Colors.out(Colors.RED, trace); + Colors.RED.out(trace); } } private void printMainHelp() { - Colors.out(Colors.BRIGHT_WHITE, - String.format("Usage: %s [-h | --help] <command> [<args>]%n", applicationName)); - System.out.println("commands:"); + Colors.BRIGHT_WHITE.out(String.format("Usage: %s [-h | --help] <command> [<args>]%n", applicationName)); + stdout("commands:"); Map<String, String> table = new LinkedHashMap<>(); for (Map.Entry<String, Command> entry : commands.entrySet()) { table.put(" " + entry.getKey(), entry.getValue().getSummary()); } - System.out.println(createTable(table).trim()); + stdout(createTable(table).trim()); } private String createTable(Map<String, String> table) { @@ -255,7 +314,7 @@ private void printHelp(Command command, Parser parser) { // Print the options name if present. parser.getPositionalName().ifPresent(name -> example.append(" ").append(name)); - Colors.out(Colors.BRIGHT_WHITE, example.append("\n").toString()); + Colors.BRIGHT_WHITE.out(example.append("\n").toString()); // Print the summary of the command. StringBuilder body = new StringBuilder(); @@ -288,7 +347,7 @@ private void printHelp(Command command, Parser parser) { body.append("\n\n").append(command.getHelp().trim()); } - System.out.println(body); + stdout(body.toString()); } private static final class BasicFormatter extends SimpleFormatter { @@ -309,4 +368,28 @@ public synchronized String format(LogRecord r) { + r.getMessage() + System.lineSeparator(); } } + + /** + * Logs messages to the CLI's redirect stderr. + */ + private static final class CliLogHandler extends Handler { + private final Formatter formatter; + + CliLogHandler(Formatter formatter) { + this.formatter = formatter; + } + + @Override + public void publish(LogRecord record) { + if (isLoggable(record)) { + Cli.stderr(formatter.format(record)); + } + } + + @Override + public void flush() {} + + @Override + public void close() {} + } } 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 39c3e2f56a9..591fd51b2c3 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 @@ -41,9 +41,6 @@ public enum Colors { BRIGHT_CYAN(96), BRIGHT_WHITE(97); - /** Configures whether or not to use ANSI colors. */ - private static boolean useAnsiColors = useAnsi(); - private int escape; private boolean bold; @@ -57,53 +54,33 @@ public enum Colors { } /** - * Explicitly configures whether or not to use ANSI colors. - * - * @param useAnsiColors Set to true or false to enable/disable. - */ - public static void setUseAnsiColors(boolean useAnsiColors) { - Colors.useAnsiColors = useAnsiColors; - } - - /** - * Does a really simple check to see if ANSI colors are supported. - * - * @return Returns true if ANSI probably works. - */ - private static boolean useAnsi() { - return System.console() != null && System.getenv().get("TERM") != null; - } - - /** - * Prints to stdout using the provided color if ANSI colors are enabled. + * Prints to stdout using the Color if ANSI colors are enabled. * - * @param color ANSI color to print with. * @param message Message to print. */ - public static void out(Colors color, String message) { - if (useAnsiColors) { - System.out.println(format(color, message)); + public void out(String message) { + if (Cli.useAnsiColors) { + Cli.stdout(format(message)); } else { - System.out.println(message); + Cli.stdout(message); } } /** - * Prints to stderr using the provided color if ANSI colors are enabled. + * Prints to stderr using the Color if ANSI colors are enabled. * - * @param color ANSI color to print with. * @param message Message to print. */ - public static void err(Colors color, String message) { - if (useAnsiColors) { - System.err.println(format(color, message)); + public void err(String message) { + if (Cli.useAnsiColors) { + Cli.stderr(format(message)); } else { - System.err.println(message); + Cli.stderr(message); } } - private static String format(Colors color, String message) { - String colored = String.format("\u001b[%dm%s\u001b[0m", color.escape, message); - return color.bold ? String.format("\033[1m%s\033[0m", colored) : colored; + private String format(String message) { + String colored = String.format("\u001b[%dm%s\u001b[0m", escape, message); + return bold ? String.format("\033[1m%s\033[0m", colored) : colored; } } 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 b1e396639c1..d8545a02edd 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 @@ -245,13 +245,13 @@ public static final class Builder implements SmithyBuilder<Parser> { private List<Argument> arguments = new ArrayList<>(); private Builder() { - // Always include --help, --debug, --stacktrace, and --no-color options. + // Always include --help, --debug, --stacktrace, and --no-color options; and --logging X. 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"); - option(Cli.QUIET_LOGS, "Disables writing log messages to STDOUT"); + parameter(Cli.LOGGING, "Sets the log level to one of OFF, SEVERE, WARNING, INFO, FINE, ALL"); } @Override 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 184bf3f28f7..cfb49a34c00 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 @@ -29,7 +29,6 @@ public final class SmithyCli { public static final String ALLOW_UNKNOWN_TRAITS = "--allow-unknown-traits"; private ClassLoader classLoader = getClass().getClassLoader(); - private boolean configureLogging; private SmithyCli() {} @@ -49,7 +48,7 @@ public static SmithyCli create() { */ public static void main(String... args) { try { - SmithyCli.create().configureLogging(true).run(args); + SmithyCli.create().run(args); } catch (CliError e) { System.exit(e.code); } catch (Exception e) { @@ -68,17 +67,6 @@ public SmithyCli classLoader(ClassLoader classLoader) { return this; } - /** - * Configures the CLI to modify the JUL log level and format. - * - * @param configureLogging Set to true to modify log formats and levels. - * @return Returns the CLI. - */ - public SmithyCli configureLogging(boolean configureLogging) { - this.configureLogging = configureLogging; - return this; - } - /** * Runs the CLI using a list of arguments. * @@ -95,7 +83,6 @@ public void run(List<String> args) { */ public void run(String... args) { Cli cli = new Cli("smithy", classLoader); - cli.configureLogging(configureLogging); cli.addCommand(new ValidateCommand()); cli.addCommand(new BuildCommand()); cli.addCommand(new DiffCommand()); 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 4acf84f6ca9..7e41e223d9d 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 @@ -32,6 +32,7 @@ import software.amazon.smithy.build.SmithyBuild; import software.amazon.smithy.build.model.SmithyBuildConfig; import software.amazon.smithy.cli.Arguments; +import software.amazon.smithy.cli.Cli; import software.amazon.smithy.cli.CliError; import software.amazon.smithy.cli.Colors; import software.amazon.smithy.cli.Command; @@ -76,7 +77,7 @@ public void execute(Arguments arguments, ClassLoader classLoader) { String output = arguments.parameter("--output", null); List<String> models = arguments.positionalArguments(); - LOGGER.info(String.format("Building Smithy model sources: %s", models)); + Cli.stdout(String.format("Building Smithy model sources: %s", models)); SmithyBuildConfig.Builder configBuilder = SmithyBuildConfig.builder(); // Try to find a smithy-build.json file. @@ -85,7 +86,7 @@ public void execute(Arguments arguments, ClassLoader classLoader) { } if (config != null) { - LOGGER.info(String.format("Loading Smithy configs: [%s]", String.join(" ", config))); + Cli.stdout(String.format("Loading Smithy configs: [%s]", String.join(" ", config))); config.forEach(file -> configBuilder.load(Paths.get(file))); } @@ -93,7 +94,7 @@ public void execute(Arguments arguments, ClassLoader classLoader) { configBuilder.outputDirectory(output); try { Files.createDirectories(Paths.get(output)); - LOGGER.fine(String.format("Output directory set to: %s", output)); + LOGGER.info(String.format("Output directory set to: %s", output)); } catch (IOException e) { throw new CliError("Unable to create Smithy output directory: " + e.getMessage()); } @@ -102,8 +103,8 @@ public void execute(Arguments arguments, ClassLoader classLoader) { SmithyBuildConfig smithyBuildConfig = configBuilder.build(); // Build the model and fail if there are errors. - ValidatedResult<Model> sourceResult = buildModel(classLoader, models, arguments); - Model model = sourceResult.unwrap(); + Model model = buildModel(classLoader, models, arguments); + SmithyBuild smithyBuild = SmithyBuild.create(classLoader) .config(smithyBuildConfig) .model(model); @@ -126,7 +127,7 @@ public void execute(Arguments arguments, ClassLoader classLoader) { Colors color = resultConsumer.failedProjections.isEmpty() ? Colors.BRIGHT_BOLD_GREEN : Colors.BRIGHT_BOLD_YELLOW; - Colors.out(color, String.format( + color.out(String.format( "Smithy built %s projection(s), %s plugin(s), and %s artifacts", resultConsumer.projectionCount, resultConsumer.pluginCount, @@ -142,14 +143,14 @@ public void execute(Arguments arguments, ClassLoader classLoader) { } } - private ValidatedResult<Model> buildModel(ClassLoader classLoader, List<String> models, Arguments arguments) { + private Model buildModel(ClassLoader classLoader, List<String> models, Arguments arguments) { ModelAssembler assembler = CommandUtils.createModelAssembler(classLoader); CommandUtils.handleModelDiscovery(arguments, assembler, classLoader); CommandUtils.handleUnknownTraitsOption(arguments, assembler); models.forEach(assembler::addImport); ValidatedResult<Model> result = assembler.assemble(); - Validator.validate(result, true); - return result; + Validator.validate(result); + return result.getResult().orElseThrow(() -> new RuntimeException("No result; expected Validator to throw")); } private static final class ResultConsumer implements Consumer<ProjectionResult>, BiConsumer<String, Throwable> { @@ -168,7 +169,7 @@ public void accept(String name, Throwable exception) { message.append(element).append(System.lineSeparator()); } - System.out.println(message); + Cli.stdout(message); } @Override @@ -185,7 +186,7 @@ public void accept(ProjectionResult result) { message.append(event).append(System.lineSeparator()); } }); - Colors.out(Colors.RED, message.toString()); + Colors.RED.out(message.toString()); } else { // Only increment the projection count if it succeeded. projectionCount.incrementAndGet(); @@ -196,7 +197,9 @@ public void accept(ProjectionResult result) { // Get the base directory of the projection. Iterator<FileManifest> manifestIterator = result.getPluginManifests().values().iterator(); Path root = manifestIterator.hasNext() ? manifestIterator.next().getBaseDir().getParent() : null; - Colors.out(Colors.GREEN, String.format("Completed projection %s: %s", result.getProjectionName(), root)); + Colors.GREEN.out(String.format( + "Completed projection %s (%d shapes): %s", + result.getProjectionName(), result.getModel().toSet().size(), root)); // Increment the total number of artifacts written. for (FileManifest manifest : result.getPluginManifests().values()) { diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/DiffCommand.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/DiffCommand.java index 7acb37d3ae0..d1736bca25a 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/DiffCommand.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/DiffCommand.java @@ -19,6 +19,7 @@ import java.util.logging.Logger; import java.util.stream.Collectors; import software.amazon.smithy.cli.Arguments; +import software.amazon.smithy.cli.Cli; import software.amazon.smithy.cli.CliError; import software.amazon.smithy.cli.Colors; import software.amazon.smithy.cli.Command; @@ -74,15 +75,15 @@ public void execute(Arguments arguments, ClassLoader classLoader) { } if (!result.isEmpty()) { - System.out.println(result); + Cli.stdout(result); } if (hasDanger) { - Colors.out(Colors.BRIGHT_BOLD_RED, "Smithy diff detected danger"); + Colors.BRIGHT_BOLD_RED.out("Smithy diff detected danger"); } else if (hasWarning) { - Colors.out(Colors.BRIGHT_BOLD_YELLOW, "Smithy diff complete with warnings"); + Colors.BRIGHT_BOLD_YELLOW.out("Smithy diff complete with warnings"); } else { - Colors.out(Colors.BRIGHT_BOLD_GREEN, "Smithy diff complete"); + Colors.BRIGHT_BOLD_GREEN.out("Smithy diff complete"); } } 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 6bda03135d7..2712a742a23 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 @@ -16,7 +16,6 @@ package software.amazon.smithy.cli.commands; import java.util.List; -import java.util.logging.Logger; import software.amazon.smithy.cli.Arguments; import software.amazon.smithy.cli.Colors; import software.amazon.smithy.cli.Command; @@ -27,8 +26,6 @@ import software.amazon.smithy.model.validation.ValidatedResult; public final class ValidateCommand implements Command { - private static final Logger LOGGER = Logger.getLogger(ValidateCommand.class.getName()); - @Override public String getName() { return "validate"; @@ -52,7 +49,7 @@ public Parser getParser() { @Override public void execute(Arguments arguments, ClassLoader classLoader) { List<String> models = arguments.positionalArguments(); - LOGGER.info(String.format("Validating Smithy model sources: %s", models)); + Colors.BRIGHT_WHITE.out(String.format("Validating Smithy model sources: %s", models)); ModelAssembler assembler = CommandUtils.createModelAssembler(classLoader); CommandUtils.handleModelDiscovery(arguments, assembler, classLoader); @@ -61,6 +58,6 @@ public void execute(Arguments arguments, ClassLoader classLoader) { models.forEach(assembler::addImport); ValidatedResult<Model> modelResult = assembler.assemble(); Validator.validate(modelResult); - Colors.out(Colors.BRIGHT_BOLD_GREEN, "Smithy validation complete"); + 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 acd92857b30..ae4b355594b 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,14 +17,13 @@ import static java.lang.String.format; -import java.util.Collections; -import java.util.Comparator; +import software.amazon.smithy.cli.Cli; import software.amazon.smithy.cli.CliError; import software.amazon.smithy.cli.Colors; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.validation.ContextualValidationEventFormatter; import software.amazon.smithy.model.validation.Severity; import software.amazon.smithy.model.validation.ValidatedResult; -import software.amazon.smithy.model.validation.ValidationEvent; /** * Shares logic for validating a model and printing out events. @@ -33,38 +32,34 @@ final class Validator { private Validator() {} static void validate(ValidatedResult<Model> result) { - validate(result, false); - } + ContextualValidationEventFormatter formatter = new ContextualValidationEventFormatter(); - static void validate(ValidatedResult<Model> result, boolean quiet) { result.getValidationEvents().stream() .filter(event -> event.getSeverity() != Severity.SUPPRESSED) - .sorted(Comparator.comparing(ValidationEvent::toString)) + .sorted() .forEach(event -> { if (event.getSeverity() == Severity.WARNING) { - Colors.out(Colors.YELLOW, event.toString()); + Colors.YELLOW.out(formatter.format(event)); } else if (event.getSeverity() == Severity.DANGER || event.getSeverity() == Severity.ERROR) { - Colors.out(Colors.RED, event.toString()); + Colors.RED.out(formatter.format(event)); } else { - System.out.println(event); + Cli.stdout(event); } + Cli.stdout(""); }); long errors = result.getValidationEvents(Severity.ERROR).size(); long dangers = result.getValidationEvents(Severity.DANGER).size(); - 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()); - System.out.println(String.join("", Collections.nCopies(line.length(), "-"))); - System.out.println(line); - result.getResult().ifPresent(model -> System.out.println(String.format( - "Validated %d shapes in model", model.shapes().count()))); - } + 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 (errors + dangers > 0) { + if (!result.getResult().isPresent() || errors + dangers > 0) { // Show the error and danger severity events. throw new CliError(format("The model is invalid: %s ERROR(s), %d DANGER(s)", errors, dangers)); } diff --git a/smithy-cli/src/test/java/software/amazon/smithy/cli/commands/BuildCommandTest.java b/smithy-cli/src/test/java/software/amazon/smithy/cli/commands/BuildCommandTest.java index de8e333ba3a..2fd27f199ae 100644 --- a/smithy-cli/src/test/java/software/amazon/smithy/cli/commands/BuildCommandTest.java +++ b/smithy-cli/src/test/java/software/amazon/smithy/cli/commands/BuildCommandTest.java @@ -48,7 +48,7 @@ public void dumpsOutValidationErrorsAndFails() throws Exception { CliError e = Assertions.assertThrows(CliError.class, () -> { String model = getClass().getResource("unknown-trait.smithy").getPath(); - SmithyCli.create().configureLogging(true).run("build", model); + SmithyCli.create().run("build", model); }); System.setOut(out); @@ -67,7 +67,7 @@ public void printsSuccessfulProjections() throws Exception { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); PrintStream printStream = new PrintStream(outputStream); System.setOut(printStream); - SmithyCli.create().configureLogging(true).run("build", model); + SmithyCli.create().run("build", model); System.setOut(out); String output = outputStream.toString("UTF-8"); @@ -85,7 +85,7 @@ public void validationFailuresCausedByProjectionsAreDetected() throws Exception CliError e = Assertions.assertThrows(CliError.class, () -> { String model = getClass().getResource("valid-model.smithy").getPath(); String config = getClass().getResource("projection-build-failure.json").getPath(); - SmithyCli.create().configureLogging(true).run("build", "--debug", "--config", config, model); + SmithyCli.create().run("build", "--debug", "--config", config, model); }); System.setOut(out); 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 a402a58ed3d..b2681d85441 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 @@ -42,14 +42,14 @@ public void hasValidateCommand() throws Exception { @Test public void usesModelDiscoveryWithCustomValidClasspath() { String dir = getClass().getResource("valid.jar").getPath(); - SmithyCli.create().configureLogging(true).run("validate", "--debug", "--discover-classpath", dir); + SmithyCli.create().run("validate", "--debug", "--discover-classpath", dir); } @Test public void usesModelDiscoveryWithCustomInvalidClasspath() { CliError e = Assertions.assertThrows(CliError.class, () -> { String dir = getClass().getResource("invalid.jar").getPath(); - SmithyCli.create().configureLogging(true).run("validate", "--debug", "--discover-classpath", dir); + SmithyCli.create().run("validate", "--debug", "--discover-classpath", dir); }); assertThat(e.getMessage(), containsString("1 ERROR(s)")); @@ -59,7 +59,7 @@ public void usesModelDiscoveryWithCustomInvalidClasspath() { public void failsOnUnknownTrait() { CliError e = Assertions.assertThrows(CliError.class, () -> { String model = getClass().getResource("unknown-trait.smithy").getPath(); - SmithyCli.create().configureLogging(true).run("validate", model); + SmithyCli.create().run("validate", model); }); assertThat(e.getMessage(), containsString("1 ERROR(s)")); @@ -68,6 +68,6 @@ public void failsOnUnknownTrait() { @Test public void allowsUnknownTrait() { String model = getClass().getResource("unknown-trait.smithy").getPath(); - SmithyCli.create().configureLogging(true).run("validate", "--allow-unknown-traits", model); + SmithyCli.create().run("validate", "--allow-unknown-traits", model); } } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/ContextualValidationEventFormatter.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/ContextualValidationEventFormatter.java new file mode 100644 index 00000000000..c17df7d5b69 --- /dev/null +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/ContextualValidationEventFormatter.java @@ -0,0 +1,122 @@ +/* + * Copyright 2019 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.model.validation; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.util.Formatter; +import software.amazon.smithy.model.SourceLocation; +import software.amazon.smithy.model.shapes.ShapeId; + +/** + * This validation event formatter outputs a validation event that points + * to the source code line that triggered the event. + * + * <p>If the event does not have a source location, then this formatter + * will not attempt to load the contents of the model. + * + * <p>This formatter outputs messages similar to the following text:</p> + * + * <pre>{@code + * ERROR: aws.protocols.tests.ec2#IgnoresWrappingXmlName (Model) + * --> /foo/bar.smithy + * | + * 403 | apply MyShape @httpResponseTests([ + * | ^ + * = Unable to resolve trait `smithy.test#httpResponseTests`. If this is a custom trait, [...] + * }</pre> + */ +public final class ContextualValidationEventFormatter implements ValidationEventFormatter { + @Override + public String format(ValidationEvent event) { + StringWriter writer = new StringWriter(); + Formatter formatter = new Formatter(writer); + formatter.format("%s: %s (%s)%n", + event.getSeverity(), + event.getShapeId().map(ShapeId::toString).orElse("-"), + event.getEventId()); + + if (event.getSourceLocation() != SourceLocation.NONE) { + String humanReadableFilename = getHumanReadableFilename(event.getSourceLocation()); + String contextualLine = null; + try { + contextualLine = loadContextualLine(event.getSourceLocation()); + } catch (IOException e) { + // Do nothing. + } + + if (contextualLine == null) { + formatter.format(" @ %s%n", event.getSourceLocation()); + } else { + // Show the filename. + formatter.format(" @ %s%n", humanReadableFilename); + formatter.format(" |%n"); + // Show the line number and source code line. + formatter.format("%4d | %s%n", event.getSourceLocation().getLine(), contextualLine); + // Add a carat to point to the column of the error. + formatter.format(" | %" + event.getSourceLocation().getColumn() + "s%n", "^"); + } + } + + // Add the message and indent each line. + formatter.format(" = %s%n", event.getMessage().replace("\n", " \n")); + + // Close up the formatter. + formatter.flush(); + + return writer.toString(); + } + + // Filenames might start with a leading file:/. Strip that. + private String getHumanReadableFilename(SourceLocation source) { + String filename = source.getFilename(); + + if (filename.startsWith("file:")) { + filename = filename.substring(5); + } + + return filename; + } + + // Attempts to load a specific line from the model. + private String loadContextualLine(SourceLocation source) throws IOException { + // Ensure that there's a scheme. + String normalizedFile = source.getFilename(); + if (!source.getFilename().startsWith("file:") && !source.getFilename().startsWith("jar:")) { + normalizedFile = "file:" + normalizedFile; + } + + // Loading from a JAR needs special treatment, but this can + // all actually be handled in a uniform way using URLs. + URL url = new URL(normalizedFile); + URLConnection connection = url.openConnection(); + connection.setUseCaches(false); + + try (InputStream input = connection.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) { + return reader.lines() + .skip(source.getLine() - 1) + .findFirst() + .orElse(null); + } + } +} diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/LineValidationEventFormatter.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/LineValidationEventFormatter.java new file mode 100644 index 00000000000..8251495e5b1 --- /dev/null +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/LineValidationEventFormatter.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019 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.model.validation; + +import software.amazon.smithy.model.shapes.ShapeId; + +/** + * Writes {@code ValidationEvent} objects as a single line string. + */ +public final class LineValidationEventFormatter implements ValidationEventFormatter { + @Override + public String format(ValidationEvent event) { + return String.format( + "[%s] %s: %s | %s %s:%s:%s", + event.getSeverity(), + event.getShapeId().map(ShapeId::toString).orElse("-"), + event.getMessage(), + event.getEventId(), + event.getSourceLocation().getFilename(), + event.getSourceLocation().getLine(), + event.getSourceLocation().getColumn()); + } +} diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/ValidationEvent.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/ValidationEvent.java index 9058a202d65..d3e4d6ed7af 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/ValidationEvent.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/ValidationEvent.java @@ -38,14 +38,15 @@ * Events with a severity less than ERROR can be suppressed. All events contain * a message, severity, and eventId. */ -public final class ValidationEvent implements ToNode, ToSmithyBuilder<ValidationEvent> { +public final class ValidationEvent implements Comparable<ValidationEvent>, ToNode, ToSmithyBuilder<ValidationEvent> { + private static final ValidationEventFormatter DEFAULT_FORMATTER = new LineValidationEventFormatter(); private final SourceLocation sourceLocation; private final String message; private final String eventId; private final Severity severity; private final ShapeId shapeId; private final String suppressionReason; - private final String asString; + private int hash; private ValidationEvent(Builder builder) { if (builder.suppressionReason != null && builder.severity != Severity.SUPPRESSED) { @@ -58,9 +59,6 @@ private ValidationEvent(Builder builder) { this.eventId = SmithyBuilder.requiredState("eventId", builder.eventId); this.shapeId = builder.shapeId; this.suppressionReason = builder.suppressionReason; - this.asString = String.format("[%s] %s: %s | %s %s:%s:%s", - severity, shapeId != null ? shapeId : "-", message, eventId, - sourceLocation.getFilename(), sourceLocation.getLine(), sourceLocation.getColumn()); } public static Builder builder() { @@ -94,6 +92,27 @@ public static ValidationEvent fromSourceException(SourceException exception, Str .build(); } + @Override + public int compareTo(ValidationEvent other) { + int comparison = getSourceLocation().getFilename().compareTo(other.getSourceLocation().getFilename()); + if (comparison != 0) { + return comparison; + } + + comparison = Integer.compare(getSourceLocation().getLine(), other.getSourceLocation().getLine()); + if (comparison != 0) { + return comparison; + } + + comparison = Integer.compare(getSourceLocation().getColumn(), other.getSourceLocation().getColumn()); + if (comparison != 0) { + return comparison; + } + + // Fall back to a comparison that favors by severity, followed, by shape ID, etc... + return toString().compareTo(other.toString()); + } + @Override public Builder toBuilder() { Builder builder = new Builder(); @@ -125,12 +144,17 @@ && getShapeId().equals(other.getShapeId()) @Override public int hashCode() { - return asString.hashCode() + getSuppressionReason().hashCode(); + int result = hash; + if (result == 0) { + result = Objects.hash(eventId, shapeId, severity, sourceLocation, message, suppressionReason); + hash = result; + } + return result; } @Override public String toString() { - return asString; + return DEFAULT_FORMATTER.format(this); } @Override diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/ValidationEventFormatter.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/ValidationEventFormatter.java new file mode 100644 index 00000000000..03d305f6ea3 --- /dev/null +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/ValidationEventFormatter.java @@ -0,0 +1,29 @@ +/* + * Copyright 2019 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.model.validation; + +/** + * Formats {@code ValidationEvent}s. + */ +public interface ValidationEventFormatter { + /** + * Converts the event to a string. + * + * @param event Event to write as a string. + * @return Returns the event as a formatted string. + */ + String format(ValidationEvent event); +} diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/validation/ContextualValidationEventFormatterTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/validation/ContextualValidationEventFormatterTest.java new file mode 100644 index 00000000000..73b74633dfd --- /dev/null +++ b/smithy-model/src/test/java/software/amazon/smithy/model/validation/ContextualValidationEventFormatterTest.java @@ -0,0 +1,59 @@ +package software.amazon.smithy.model.validation; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.startsWith; + +import org.junit.jupiter.api.Test; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.SourceLocation; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.shapes.ShapeId; + +public class ContextualValidationEventFormatterTest { + @Test + public void loadsContext() { + Model model = Model.assembler() + .addImport(getClass().getResource("context.smithy")) + .assemble() + .unwrap(); + + Shape shape = model.expectShape(ShapeId.from("example.smithy#Foo")); + ValidationEvent event = ValidationEvent.builder() + .eventId("foo") + .severity(Severity.ERROR) + .message("This is the message") + .shape(shape) + .build(); + + String format = new ContextualValidationEventFormatter().format(event); + + assertThat(format, startsWith("ERROR: example.smithy#Foo (foo)")); + assertThat(format, containsString("\n @ ")); + assertThat(format, endsWith( + "\n |" + + "\n 3 | structure Foo {" + + "\n | ^" + + "\n = This is the message" + + "\n")); + } + + @Test + public void doesNotLoadSourceLocationNone() { + ValidationEvent event = ValidationEvent.builder() + .eventId("foo") + .severity(Severity.ERROR) + .message("This is the message") + .sourceLocation(SourceLocation.NONE) + .build(); + + String format = new ContextualValidationEventFormatter().format(event); + + assertThat(format, equalTo( + "ERROR: - (foo)" + + "\n = This is the message" + + "\n")); + } +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/validation/context.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/validation/context.smithy new file mode 100644 index 00000000000..22aea1bf31f --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/validation/context.smithy @@ -0,0 +1,9 @@ +namespace example.smithy + +structure Foo { + bar: String, +} + +structure Baz { + bam: String, +}