Repository: smithy-lang/smithy
Pull request #2220 (2024-03-28 21:14:49)

Change the HTTP bindings validation to support any non-conflicting URI
#### Background

Changes the Smithy validation of the HTTP bindings to allow URI patterns that before were considered in conflict. In particular the following constraint was removed

> A label and a literal SHOULD NOT both occupy the same segment in patterns which are equivalent to that point if they share a host.

And the docs changed as

> A label and a literal MAY both occupy the same segment in patterns that are equivalent to that point if they share a host. Server implementations MUST route ambiguous requests to the operation with the most specific URI pattern

This feature will allow customers the use of URI path patterns that are not ambiguous but that are not currently supported, such as:

* `/abc/{xyz}` and
* `/abc/{xyz}/cde`

and also those that can be ambiguous such as

* `/abc/bcd/{xyz}`
* `/abc/{xyz}/cde`

Servers are expected to use Specificity Routing to route request that might be ambiguous at runtime as explained in the design doc ["URI Pattern Validation and Conflict Resolution"](https://github.com/smithy-lang/smithy/blob/main/designs/uri-conflict-resolution.md).

#### Testing
* Unit tests 

#### Links
* Links to additional context, if necessary
* Issue #, if applicable (see [here](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) for a list of keywords to use for linking issues)

---
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.



Patch:
diff --git a/designs/uri-conflict-resolution.md b/designs/uri-conflict-resolution.md
new file mode 100644
index 000000000..ed0bed8d1
--- /dev/null
+++ b/designs/uri-conflict-resolution.md
@@ -0,0 +1,141 @@
+# URI Pattern Validation and Conflict Resolution
+
+[**Draft**]
+
+**Author**:  Manuel Sugawara
+**Created**: 2022/12/13
+
+## Overview
+
+This document proposes changing the Smithy spec to relax the rule we
+use to validate HTTP bindings to avoid conflicts. Instead of avoiding
+conflicts we will require servers to implement a well-defined way to
+route requests for ambiguous URI that match two or more patterns by
+using the most specific route.
+
+## Motivation
+
+Smithy supports defining HTTP bindings using path patterns with
+optional labels, placeholders that can be used to capture segments of
+the path to be passed to the operation handler, and query parameters
+of the Unified Resource Identifiers (URI’s), however this feature has
+been restricted to avoid conflicting URIs, in particular the rule
+number 5 in the Smithy spec
+
+
+> A label and a literal SHOULD NOT both occupy the same segment in patterns which are equivalent to that point if they share a host.
+
+
+Removing or relaxing this rule has been a frequent request from
+customers given that it restricts the way service owners can model
+their API by disallowing the definition of path hierarchies that makes
+the most sense for their business logic. The proposal is to remove
+this constraint to allow the use of URI path patterns that are not
+ambiguous but that are not currently supported, such as:
+
+* `/abc/{xyz}` and
+* `/abc/{xyz}/cde`
+
+and also those that can be ambiguous such as 
+
+* `/abc/bcd/{xyz}`
+* `/abc/{xyz}/cde`
+
+while also defining a set of rules to unambiguously route requests
+when two or more path patterns match a given request based on the
+*specificity* of the path, which can be loosely defined as “a path
+with a non-label segment is considered more specific than one with a
+label segment in the same position”.
+
+Given that there is no reasonable way to distinguish *equivalent* path
+patterns (i.e., that look the same except for the name of the labels)
+we will disallow them, for instance,
+
+* `/abc/{foo}/cde`
+* `/abc/{bar}/cde`
+
+## Proposal
+
+### Specificity Routing
+
+The proposal is to  accept any non-equivalent URI path patterns and
+also specify the expected routing algorithm. The routing algorithm
+will choose the *best-ranked* match using the specificity of the path
+and literal query parameters when present. The Smithy documentation
+will be changed in the following way
+
+
+>A label and a literal MAY both occupy the same segment in patterns that are equivalent to that point if they share a host. Server implementations MUST route ambiguous requests to the operation with the most specific URI path.
+
+
+And the following algorithm to compare two paths will be added to the
+specification
+
+Given two ambiguous URI patterns `A` and `B` with segments
+`[A0, …, An]` and `[B0, …, Bm]` with query string literals
+`[AQ0, …, AQp]` and `[BQ0, …, BQq]` (with both `p` and `q` possibly
+zero, i.e., without query string literals), we use the following
+algorithm to compare them, for each index `x` from `0` to `min(n, m)`
+
+1. If `A[x]` and `B[x]` are both literals then continue (the literal values have to be equal otherwise the patterns would not be ambiguous)
+2. If `A[x]` is a literal and `B[x]` is a label then `A` is more specific than `B`, 
+3. If `A[x]` is a non-greedy label and `B[x]` is a greedy label then `A` is more specific than `B`
+4. If `n > m` then `A` is more specific than `B`
+5. if `p > q` then `A` is more specific than `B`
+
+#### Routing Example 1
+
+Given the following path patterns
+
+1. `/abc/bcd/{xyz}` 
+2. `/abc/{xyz}/cde` 
+3.  `/{xyz}/bcd/cde`
+
+This is how we will route different path requests
+
+* `/abc/bcd/cde` will route to pattern number 1 (ambiguous with numbers 2 and 3)
+* `/abc/**foo**/cde` will route to pattern number 2 (ambiguous with number 3)
+* `/**foo**/bcd/cde` will route to pattern number 3 (non-ambiguous)
+
+**Routing Example 2**
+
+Given the following path patterns
+
+1. `/abc/bcd/{xyz}` 
+2. `/abc/{xyz}/cde` 
+3.  `/{xyz}/bcd/cde?def=efg`
+
+This is how we will route different path requests
+
+* `/abc/bcd/cde?def=efg` will route to pattern number 1 (ambiguous with numbers 2 and 3, notice that path specificity wins over query string literals)
+* `/abc/`**`foo`**`/cde?def=efg` will route to pattern number 2 (ambiguous with number 3)
+* `/`**`foo`**`/bcd/cde?def=efg` will route to pattern number 3 (non-ambiguous)
+
+**Routing Example 3**
+
+1. `/abc/{xyz+}/bcd` 
+2. `/abc/{xyz+}` 
+
+This is how we will route different path requests
+
+* `/abc/**foo**/**bar**/bcd` will route to pattern number 1 (ambiguous with number 2)
+* `/abc/foo/bar/baz` will route to pattern number 2 (non-ambiguous)
+
+## Appendix
+
+### Compatibility with API Gateway
+
+API Gateway also uses
+[specificity](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-routes.html)
+to route requests, so this proposal will be mostly compatible with API
+Gateway. There is a catch to this: currently Smithy supports URI
+patterns with
+[query string literals](https://awslabs.github.io/smithy/2.0/spec/http-bindings.html#query-string-literals)
+that are used for matching requests. For API Gateway, query parameters
+are not used to match operations and while query parameters can be
+made in theory
+[required with a constant value](https://swagger.io/docs/specification/describing-parameters/#constant)
+(notice that API Gateway can validate that a required query param is
+present but does not validate the value itself) the response status
+might be different.
+
diff --git a/docs/conf.py b/docs/conf.py
index db090f35a..1eb884199 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -109,5 +109,5 @@ def setup(sphinx):
 # rewrites __smithy_gradle_version__ to the latest version found on Github
 def source_read_handler(app, docname, source):
     source[0] = source[0].replace(smithy_version_placeholder, smithy_version)
-    source[0] = source[0].replace(smithy_gradle_version_placeholder, smithy_typescript_codegen_version)
+    source[0] = source[0].replace(smithy_gradle_version_placeholder, smithy_gradle_plugin_version)
     source[0] = source[0].replace(smithy_typescript_version_placeholder, smithy_typescript_codegen_version)
diff --git a/docs/source-2.0/guides/gradle-plugin/index.rst b/docs/source-2.0/guides/gradle-plugin/index.rst
index c9568cd1a..cf6c09412 100644
--- a/docs/source-2.0/guides/gradle-plugin/index.rst
+++ b/docs/source-2.0/guides/gradle-plugin/index.rst
@@ -10,7 +10,7 @@ build artifacts from Smithy models, and generate JARs for Smithy models and mode
 
 .. toctree::
     :maxdepth: 1
-    :caption: Migrate to version 0.8.0+
+    :caption: Migrate to version 0.10.0+
 
     gradle-migration-guide
 
diff --git a/docs/source-2.0/guides/smithy-cli/cli_installation.rst b/docs/source-2.0/guides/smithy-cli/cli_installation.rst
index 528d7dd3f..471a09355 100644
--- a/docs/source-2.0/guides/smithy-cli/cli_installation.rst
+++ b/docs/source-2.0/guides/smithy-cli/cli_installation.rst
@@ -1,11 +1,11 @@
 .. _cli_installation:
 
 .. |release-uri| replace:: https://github.com/smithy-lang/smithy/releases/download/__smithy_version__
-.. |mac-tar| replace:: smithy-cli-darwin-x86_64.tar.gz
-.. |mac-arm-tar| replace:: smithy-cli-darwin-aarch64.tar.gz
-.. |linux-tar| replace:: smithy-cli-linux-x86_64.tar.gz
-.. |linux-arm-tar| replace:: smithy-cli-linux-aarch64.tar.gz
-.. |windows-tar| replace:: smithy-cli-windows-x64.tar.gz
+.. |mac| replace:: smithy-cli-darwin-x86_64
+.. |mac-arm| replace:: smithy-cli-darwin-aarch64
+.. |linux| replace:: smithy-cli-linux-x86_64
+.. |linux-arm| replace:: smithy-cli-linux-aarch64
+.. |windows| replace:: smithy-cli-windows-x64
 .. |verification-note| replace::
         Please refer to :ref:`cli_verification` for instructions
         on how to verify downloaded artifacts.
@@ -36,7 +36,7 @@ Releases of the Smithy CLI can be found on the `Smithy GitHub releases`_ page.
 
 .. tab:: MacOS
 
-    .. tab:: Homebrew
+    .. tab:: Homebrew (Recommended)
 
         You can get the CLI via Homebrew by first tapping the official
         `Smithy Homebrew Tap`_, and then installing the ``smithy-cli`` formula.
@@ -59,8 +59,9 @@ Releases of the Smithy CLI can be found on the `Smithy GitHub releases`_ page.
             :substitutions:
 
             mkdir -p smithy-install/smithy && \
-                curl -L |release-uri|/|mac-tar| -o smithy-install/|mac-tar| && \
-                tar xvzf smithy-install/|mac-tar| -C smithy-install/smithy
+                curl -L |release-uri|/|mac|.zip -o smithy-install/|mac|.zip && \
+                unzip -qo smithy-install/|mac|.zip -d smithy-install && \
+                mv smithy-install/|mac|/* smithy-install/smithy
 
         .. seealso::
             |verification-note|
@@ -99,8 +100,9 @@ Releases of the Smithy CLI can be found on the `Smithy GitHub releases`_ page.
             :substitutions:
 
             mkdir -p smithy-install/smithy && \
-                curl -L |release-uri|/|mac-arm-tar| -o smithy-install/|mac-arm-tar| && \
-                tar xvzf smithy-install/|mac-arm-tar| -C smithy-install/smithy
+                curl -L |release-uri|/|mac-arm|.zip -o smithy-install/|mac-arm|.zip && \
+                unzip -qo smithy-install/|mac-arm|.zip -d smithy-install && \
+                mv smithy-install/|mac-arm|/* smithy-install/smithy
 
         .. seealso::
             |verification-note|
@@ -141,8 +143,9 @@ Releases of the Smithy CLI can be found on the `Smithy GitHub releases`_ page.
             :substitutions:
 
             mkdir -p smithy-install/smithy && \
-                curl -L |release-uri|/|linux-tar| -o smithy-install/|linux-tar| && \
-                tar xvzf smithy-install/|linux-tar| -C smithy-install/smithy
+                curl -L |release-uri|/|linux|.zip -o smithy-install/|linux|.zip && \
+                unzip -qo smithy-install/|linux|.zip -d smithy-install && \
+                mv smithy-install/|linux|/* smithy-install/smithy
 
         .. seealso::
             |verification-note|
@@ -180,8 +183,9 @@ Releases of the Smithy CLI can be found on the `Smithy GitHub releases`_ page.
             :substitutions:
 
             mkdir -p smithy-install/smithy && \
-                curl -L |release-uri|/|linux-arm-tar| -o smithy-install/|linux-arm-tar| && \
-                tar xvzf smithy-install/|linux-arm-tar| -C smithy-install/smithy
+                curl -L |release-uri|/|linux-arm|.zip -o smithy-install/|linux-arm|.zip && \
+                unzip -qo smithy-install/|linux-arm|.zip -d smithy-install && \
+                mv smithy-install/|linux-arm|/* smithy-install/smithy
 
         .. seealso::
             |verification-note|
@@ -212,6 +216,20 @@ Releases of the Smithy CLI can be found on the `Smithy GitHub releases`_ page.
 
 .. tab:: Windows
 
+    .. tab:: Scoop (Recommended)
+
+        You can get the CLI via Scoop by first adding the official
+        `Smithy Scoop Bucket`_, and then installing from the ``smithy-cli`` manifest.
+
+        .. code-block:: powershell
+            :caption: powershell
+
+            scoop bucket add smithy-lang https://github.com/smithy-lang/scoop-bucket; `
+                scoop install smithy-lang/smithy-cli
+
+        After the install completes, you may run ``smithy --help`` to verify
+        your installation.
+
     .. tab:: Manual (x64)
 
         First, retrieve the latest smithy installation from the
@@ -222,13 +240,13 @@ Releases of the Smithy CLI can be found on the `Smithy GitHub releases`_ page.
             :substitutions:
 
             New-Item -Type Directory -Path smithy-install\smithy -Force; `
-                Invoke-WebRequest -Uri |release-uri|/|windows-tar| `
-                                  -OutFile smithy-install\|windows-tar|
+                Invoke-WebRequest -Uri |release-uri|/|windows|.zip `
+                                  -OutFile smithy-install\|windows|.zip
 
         .. seealso::
             |verification-note|
 
-        Now, untar the installation in the newly created directory and run
+        Now, unzip the installation in the newly created directory and run
         the installer (``install.bat``).
 
         .. important:: The following command typically requires running
@@ -241,8 +259,9 @@ Releases of the Smithy CLI can be found on the `Smithy GitHub releases`_ page.
             :caption: powershell
             :substitutions:
 
-            tar -xf smithy-install\|windows-tar| -C smithy-install\smithy;
-                 smithy-install\smithy\install
+            Expand-Archive smithy-install\|windows|.zip -DestinationPath smithy-install\; `
+                Move-Item -Path smithy-install\|windows|\* -Destination smithy-install\smithy; `
+                smithy-install\smithy\install
 
         Follow the installer prompts accordingly to complete the installation.
 
@@ -274,13 +293,13 @@ Before proceeding, please make sure you have both the GnuPG utility (``gpg``)
 and a sha256-checksum utility (``sha256sum``) installed on your system.
 We'll be using these to perform the verification.
 
-.. note:: This is only applicable to manual installations - Homebrew verifies
+.. note:: This is only applicable to manual installations - Homebrew and Scoop verify
     checksums before install.
 
 .. tab:: MacOS
 
     Download the detached signature file (``.asc``) and the sha256-checksum
-    file (``.sha256``) corresponding to the tarball (which you already
+    file (``.sha256``) corresponding to the zip file (which you already
     downloaded) from the `Smithy GitHub releases`_.
 
     .. tab:: Manual (x86)
@@ -289,15 +308,15 @@ We'll be using these to perform the verification.
             :caption: /bin/sh
             :substitutions:
 
-            curl -L '|release-uri|/|mac-tar|.{asc,sha256}' -o 'smithy-install/|mac-tar|.#1'
+            curl -L '|release-uri|/|mac|.zip.{asc,sha256}' -o 'smithy-install/|mac|.zip.#1'
 
-        Verify the checksum of the tarball using the `sha256sum` utility.
+        Verify the checksum of the zip file using the `sha256sum` utility.
 
         .. code-block:: sh
             :caption: /bin/sh
             :substitutions:
 
-            (cd smithy-install && sha256sum -c |mac-tar|.sha256)
+            (cd smithy-install && sha256sum -c |mac|.zip.sha256)
 
         Now, retrieve the public PGP key from the `release`_, and import
         it into your key-chain.
@@ -310,13 +329,13 @@ We'll be using these to perform the verification.
                 gpg --import smithy-install/smithy.asc
 
         Finally, after importing the key, verify the signature of the
-        tarball with gpg.
+        zip file with gpg.
 
         .. code-block:: sh
             :caption: /bin/sh
             :substitutions:
 
-            gpg --verify smithy-install/|mac-tar|.asc smithy-install/|mac-tar|
+            gpg --verify smithy-install/|mac|.zip.asc smithy-install/|mac|.zip
 
 
     .. tab:: Manual (ARM)
@@ -325,15 +344,15 @@ We'll be using these to perform the verification.
             :caption: /bin/sh
             :substitutions:
 
-            curl -L '|release-uri|/|mac-arm-tar|.{asc,sha256}' -o 'smithy-install/|mac-arm-tar|.#1'
+            curl -L '|release-uri|/|mac-arm|.zip.{asc,sha256}' -o 'smithy-install/|mac-arm|.zip.#1'
 
-        Verify the checksum of the tarball using the `sha256sum` utility.
+        Verify the checksum of the zip file using the `sha256sum` utility.
 
         .. code-block:: sh
             :caption: /bin/sh
             :substitutions:
 
-            (cd smithy-install && sha256sum -c |mac-arm-tar|.sha256)
+            (cd smithy-install && sha256sum -c |mac-arm|.zip.sha256)
 
         Now, retrieve the public PGP key from the `release`_, and import
         it into your key-chain.
@@ -346,19 +365,19 @@ We'll be using these to perform the verification.
                 gpg --import smithy-install/smithy.asc
 
         Finally, after importing the key, verify the signature of the
-        tarball with gpg.
+        zip file with gpg.
 
         .. code-block:: sh
             :caption: /bin/sh
             :substitutions:
 
-            gpg --verify smithy-install/|mac-arm-tar|.asc smithy-install/|mac-arm-tar|
+            gpg --verify smithy-install/|mac-arm|.zip.asc smithy-install/|mac-arm|.zip
 
 
 .. tab:: Linux
 
     Download the detached signature file (``.asc``) and the sha256-checksum
-    file (``.sha256``) corresponding to the tarball (which you already
+    file (``.sha256``) corresponding to the zip file (which you already
     downloaded) from the `Smithy GitHub releases`_.
 
     .. tab:: Manual (x86)
@@ -367,15 +386,15 @@ We'll be using these to perform the verification.
             :caption: /bin/sh
             :substitutions:
 
-            curl -L '|release-uri|/|linux-tar|.{asc,sha256}' -o 'smithy-install/|linux-tar|.#1'
+            curl -L '|release-uri|/|linux|.zip.{asc,sha256}' -o 'smithy-install/|linux|.zip.#1'
 
-        Verify the checksum of the tarball using the `sha256sum` utility.
+        Verify the checksum of the zip file using the `sha256sum` utility.
 
         .. code-block:: sh
             :caption: /bin/sh
             :substitutions:
 
-            (cd smithy-install && sha256sum -c |linux-tar|.sha256)
+            (cd smithy-install && sha256sum -c |linux|.zip.sha256)
 
         Now, retrieve the public PGP key from the `release`_, and import
         it into your key-chain.
@@ -388,13 +407,13 @@ We'll be using these to perform the verification.
                 gpg --import smithy-install/smithy.asc
 
         Finally, after importing the key, verify the signature of the
-        tarball with gpg.
+        zip file with gpg.
 
         .. code-block:: sh
             :caption: /bin/sh
             :substitutions:
 
-            gpg --verify smithy-install/|linux-tar|.asc smithy-install/|linux-tar|
+            gpg --verify smithy-install/|linux|.zip.asc smithy-install/|linux|.zip
 
 
     .. tab:: Manual (ARM)
@@ -403,15 +422,15 @@ We'll be using these to perform the verification.
             :caption: /bin/sh
             :substitutions:
 
-            curl -L '|release-uri|/|linux-arm-tar|.{asc,sha256}' -o 'smithy-install/|linux-arm-tar|.#1'
+            curl -L '|release-uri|/|linux-arm|.zip.{asc,sha256}' -o 'smithy-install/|linux-arm|.zip.#1'
 
-        Verify the checksum of the tarball using the `sha256sum` utility.
+        Verify the checksum of the zip file using the `sha256sum` utility.
 
         .. code-block:: sh
             :caption: /bin/sh
             :substitutions:
 
-            (cd smithy-install && sha256sum -c |linux-arm-tar|.sha256)
+            (cd smithy-install && sha256sum -c |linux-arm|.zip.sha256)
 
         Now, retrieve the public PGP key from the `release`_, and import
         it into your key-chain.
@@ -424,19 +443,19 @@ We'll be using these to perform the verification.
                 gpg --import smithy-install/smithy.asc
 
         Finally, after importing the key, verify the signature of the
-        tarball with gpg.
+        zip file with gpg.
 
         .. code-block:: sh
             :caption: /bin/sh
             :substitutions:
 
-            gpg --verify smithy-install/|linux-arm-tar|.asc smithy-install/|linux-arm-tar|
+            gpg --verify smithy-install/|linux-arm|.zip.asc smithy-install/|linux-arm|.zip
 
 
 .. tab:: Windows
 
     Download the detached signature file (``.asc``) and the sha256-checksum
-    file (``.sha256``) corresponding to the tarball (which you already
+    file (``.sha256``) corresponding to the zip file (which you already
     downloaded) from the `Smithy GitHub releases`_.
 
     .. tab:: Manual (x64)
@@ -445,18 +464,18 @@ We'll be using these to perform the verification.
             :caption: powershell
             :substitutions:
 
-            Invoke-WebRequest -Uri |release-uri|/|windows-tar|.asc `
-                -OutFile smithy-install\smithy-cli-windows-x64.tar.gz.asc;
-            Invoke-WebRequest -Uri |release-uri|/|windows-tar|.sha256 `
-                -OutFile smithy-install\smithy-cli-windows-x64.tar.gz.sha256
+            Invoke-WebRequest -Uri |release-uri|/|windows|.zip.asc `
+                -OutFile smithy-install\|windows|.zip.asc
+            Invoke-WebRequest -Uri |release-uri|/|windows|.zip.sha256 `
+                -OutFile smithy-install\|windows|.zip.sha256
 
-        Compute the actual checksum of the tarball using ``certutil``.
+        Compute the actual checksum of the zip file using ``certutil``.
 
         .. code-block:: powershell
             :caption: powershell
             :substitutions:
 
-            certutil -hashfile smithy-install\|windows-tar| SHA256
+            certutil -hashfile smithy-install\|windows|.zip SHA256
 
         Now, print out the expected checksum from the file that you
         downloaded (``.sha256``).
@@ -465,7 +484,7 @@ We'll be using these to perform the verification.
             :caption: powershell
             :substitutions:
 
-            cat smithy-install\|windows-tar|.sha256
+            cat smithy-install\|windows|.zip.sha256
 
         Verify that the output from the two commands matches. There may be a
         file-path appended to the latter command output - you
@@ -477,17 +496,18 @@ We'll be using these to perform the verification.
             :caption: powershell
             :substitutions:
 
-            Invoke-WebRequest -Uri |release-uri|/smithy.asc -OutFile smithy-install\smithy.asc; 
-            gpg --import smithy-install\smithy.asc
+            Invoke-WebRequest -Uri |release-uri|/smithy.asc `
+                -OutFile smithy-install\smithy.asc; ` 
+                gpg --import smithy-install\smithy.asc
 
         Finally, after importing the key, verify the signature of the
-        tarball with gpg.
+        zip file with gpg.
 
         .. code-block:: powershell
             :caption: powershell
             :substitutions:
 
-            gpg --verify smithy-install\|windows-tar|.asc smithy-install\|windows-tar|
+            gpg --verify smithy-install\|windows|.zip.asc smithy-install\|windows|.zip
 
 .. warning:: Upon verifying the signature, you will receive a
     ``WARNING`` message:
@@ -504,5 +524,6 @@ We'll be using these to perform the verification.
 
 .. _Smithy GitHub releases: https://github.com/smithy-lang/smithy/releases
 .. _Smithy Homebrew Tap: https://github.com/smithy-lang/homebrew-tap
+.. _Smithy Scoop Bucket: https://github.com/smithy-lang/scoop-bucket
 .. _release: https://github.com/smithy-lang/smithy/releases/download/__smithy_version__
 .. _key validation: https://www.gnupg.org/gph/en/manual/x334.html
diff --git a/docs/source-2.0/spec/http-bindings.rst b/docs/source-2.0/spec/http-bindings.rst
index ee106458b..fa00f8de0 100644
--- a/docs/source-2.0/spec/http-bindings.rst
+++ b/docs/source-2.0/spec/http-bindings.rst
@@ -351,9 +351,19 @@ Greedy labels
 
 A :dfn:`greedy label` is a label suffixed with the ``+`` qualifier that can be
 used to match more than one path segment. At most, one greedy label may exist
-in any path pattern, and if present, it MUST be the last label in the pattern.
+in any path pattern, and if present, it SHOULD be the last label in the pattern.
 Greedy labels MUST be bound to a string shape.
 
+.. important::
+
+    Servers implementing :ref:`Specificity Routing
+    <specificity-routing>` MAY support more than one greedy label and
+    not require it to be the last label in the pattern. The validation
+    events are therefore emitted as ``DANGER`` and can
+    suppressed. Most servers don't support having more than one greedy
+    label. Make sure that your server supports it and test that works
+    as expected before suppressing those events.
+
 Given a pattern of ``/my/uri/{label+}`` and an endpoint of ``http://yourhost``:
 
 .. list-table::
@@ -420,10 +430,9 @@ pattern of ``/prefix/{label+}/suffix`` and an endpoint of ``https://yourhost``:
 Pattern Validation and Conflict Avoidance
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Smithy validates the patterns within a service against each other to ensure
-that no two patterns conflict with each other for the same HTTP method. To
-prevent ambiguity when matching requests for different operations, the
-following rules are in place:
+Smithy validates the patterns within a service against each other to
+ensure that no two patterns conflict with each other for the same HTTP
+method. The following rules are in place:
 
 #. All labels MUST be delimited by '/' characters.
 
@@ -435,12 +444,12 @@ following rules are in place:
 #. At most, one greedy label MAY exist per pattern.
 
    - ``/{foo}/{bar+}`` is legal
-   - ``/{foo+}/{bar+}`` is illegal
+   - ``/{foo+}/bar/{baz+}`` is illegal
 
-#. If present, a greedy pattern MUST be the last label in a pattern.
+#. If present, a greedy pattern SHOULD be the last label in a pattern.
 
    - ``/{foo}/{bar+}`` is legal
-   - ``/{foo+}/{bar}`` is illegal
+   - ``/{foo+}/bar/{baz}`` is illegal
 
 #. Patterns MUST NOT be equivalent if they share a host.
 
@@ -448,28 +457,125 @@ following rules are in place:
    - Pattern ``/foo/{bar}`` and ``/foo/{baz}`` conflict regardless of any
      constraint traits on the label members.
 
-#. A label and a literal SHOULD NOT both occupy the same segment in patterns
-   which are equivalent to that point if they share a host.
+#. A label and a literal MAY both occupy the same segment in patterns
+   that are equivalent to that point if they share a host. Server
+   implementations MUST route ambiguous requests to the operation with
+   the most specific URI path (see :ref:`Specificity Routing <specificity-routing>`.)
+
+.. _specificity-routing:
+
+Specificity Routing
+~~~~~~~~~~~~~~~~~~~
+
+Specificity routing allows Smithy compliant servers to support
+ambiguous URI patterns and resolve requests at runtime. The algorithm
+chooses the *best-ranked* match using the specificity of the path and
+literal query parameters when present. The core of the algorithm can
+be loosely defined as “a path with a non-label segment is considered
+more specific than one with a label segment in the same
+position. Similarly a segment with a non-greedy label is considered
+more specific than a segment with a greedy label segment in the same
+position.”
+
+The following algorithm is used to compare two paths
+
+Given two ambiguous URI patterns ``A`` and ``B`` with segments ``[A0,
+…, An]`` and ``[B0, …, Bm]`` with query string literals ``[AQ0, …,
+AQp]`` and ``[BQ0, …, BQq]`` (with both ``p`` and ``q`` possibly zero,
+i.e., without query string literals), the following steps are taken to
+compare them, for each index ``x`` from ``0`` to ``min(n, m)``
+
+#. If ``A[x]`` and ``B[x]`` are both literals then continue (the
+   literal values have to be equal otherwise the patterns are not
+   ambiguous)
+
+#. If ``A[x]`` is a literal and ``B[x]`` is a label then ``A`` is more
+   specific than ``B``,
+
+#. If ``A[x]`` is a non-greedy label and ``B[x]`` is a greedy label
+   then ``A`` is more specific than ``B``
+
+#. If ``n > m`` then ``A`` is more specific than ``B``
+
+#. if ``p > q`` then ``A`` is more specific than ``B``
+
+**Routing Example 1**
 
-   - ``/foo/bar/{baz}`` and ``/foo/baz/bam`` can coexist.
-   - ``/foo/bar`` and ``/foo/{baz}/bam`` cannot coexist unless pattern
-     traits prevent ``{baz}`` from evaluating to ``bar`` because the label
-     occupies the same segment of another pattern with the same prefix.
+Given a service with the following URI patterns for the same method
 
-#. A query string literal with no value and a query string literal with an
-   empty value are considered equivalent. For example, ``/foo?baz`` and
-   ``/foo?baz=`` are considered the same route.
+#. ``/abc/bcd/{xyz}``
+#. ``/abc/{xyz}/cde`` 
+#. ``/{xyz}/bcd/cde``
 
-#. Patterns MAY conflict if the operations use different hosts. Different hosts
-   can be configured using the :ref:`endpoint-trait`'s ``hostPrefix`` property.
+.. list-table::
+    :header-rows: 1
+    :widths: 30 10 60
+
+    * - Request URI
+      - Pattern Matched
+      - Reason
+    * - ``/abc/bcd/cde``
+      - Pattern 1
+      - Ambiguous with patterns 2 and 3. The literal ``bcd`` is more
+        specific than the label ``{xyz}``
+    * - ``/abc/foo/cde`` 
+      - Pattern 2 
+      - Ambiguous with pattern 3. The literal segment ``abc`` is more
+        specific than the label ``{xyz}``.
+    * - ``/foo/bcd/cde`` 
+      - Pattern 3
+      - Non-ambiguous.
+
+**Routing Example 2**
+
+Given a service with the following URI patterns for the same method
+
+#. ``/abc/bcd/{xyz}``
+#. ``/abc/{xyz}/cde``
+#. ``/{xyz}/bcd/cde?def=efg``
+
+.. list-table::
+    :header-rows: 1
+    :widths: 30 10 60
 
-   - ``/foo/bar`` and ``/foo/{baz}/bam`` can coexist if one operation has no
-     endpoint trait and the other specifies ``foo.`` as the ``hostPrefix``.
-   - ``/foo/bar`` and ``/foo/{baz}/bam`` can coexist if one operation specifies
-     ``foo.`` as the ``hostPrefix`` and the other specifies ``bar.`` as the
-     ``hostPrefix``.
+    * - Request URI
+      - Pattern Matched
+      - Reason
+    * - ``/abc/bcd/cde?def=efg``
+      - Pattern 1
+      - Ambiguous with numbers 2 and 3. The literal segment ``abc`` is
+        more specific than the label ``{xyz}``. Notice that path
+        specificity wins over query string literals
+    * - ``/abc/foo/cde?def=efg``
+      - Pattern 2 
+      - Ambiguous with pattern 3. The literal segment ``abc`` is more
+        specific than the label ``{xyz}``.
+    * - ``/foo/bcd/cde?def=efg``
+      - Pattern 3
+      - Non-ambiguous.
+
+**Routing Example 3**
+
+Given a service with the following URI patterns for the same method
+
+#. ``/abc/{xyz+}/bcd``
+#. ``/abc/{xyz+}``
 
+.. list-table::
+    :header-rows: 1
+    :widths: 30 10 60
 
+    * - Request URI
+      - Pattern Matched
+      - Reason
+    * - ``/abc/foo/bar/bcd``
+      - Pattern 1
+      - Ambiguous with numbers 2. The literal segment ``bcd`` is
+        more specific than a non-segment.
+    * - ``/abc/foo/bar/baz``
+      - Pattern 2 
+      - Non-ambiguous.
+   
 .. smithy-trait:: smithy.api#httpError
 .. _httpError-trait:
 
diff --git a/docs/source-2.0/spec/json-ast.rst b/docs/source-2.0/spec/json-ast.rst
index 47fdb223c..0f975d0e7 100644
--- a/docs/source-2.0/spec/json-ast.rst
+++ b/docs/source-2.0/spec/json-ast.rst
@@ -361,7 +361,7 @@ The following example defines an :ref:`enum`:
                 "type": "enum",
                 "members": {
                     "FOO": {
-                        "target": "smithy.api#Unit"
+                        "target": "smithy.api#Unit",
                         "traits": {
                             "smithy.api#enumValue": "foo"
                         }
@@ -382,7 +382,7 @@ The following example defines an :ref:`intEnum`:
                 "type": "intEnum",
                 "members": {
                     "FOO": {
-                        "target": "smithy.api#Unit"
+                        "target": "smithy.api#Unit",
                         "traits": {
                             "smithy.api#enumValue": 1
                         }
diff --git a/gradle.properties b/gradle.properties
index bc986420c..305dfee84 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,3 +1,3 @@
 org.gradle.parallel=true
 org.gradle.jvmargs='-Dfile.encoding=UTF-8'
-smithyGradleVersion=0.10.1
+smithyGradleVersion=1.0.0
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/pattern/SmithyPattern.java b/smithy-model/src/main/java/software/amazon/smithy/model/pattern/SmithyPattern.java
index 82bcb7716..29fc9f4ad 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/pattern/SmithyPattern.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/pattern/SmithyPattern.java
@@ -54,9 +54,7 @@ public class SmithyPattern {
         segments = Objects.requireNonNull(builder.segments);
 
         checkForDuplicateLabels();
-        if (builder.allowsGreedyLabels) {
-            checkForLabelsAfterGreedyLabels();
-        } else if (segments.stream().anyMatch(Segment::isGreedyLabel)) {
+        if (!builder.allowsGreedyLabels && segments.stream().anyMatch(Segment::isGreedyLabel)) {
             throw new InvalidPatternException("Pattern must not contain a greedy label. Found " + pattern);
         }
     }
@@ -167,25 +165,6 @@ public class SmithyPattern {
         });
     }
 
-    private void checkForLabelsAfterGreedyLabels() {
-        // Make sure at most one greedy label exists, and that it is the
-        // last label segment.
-        for (int i = 0; i < segments.size(); i++) {
-            Segment s = segments.get(i);
-            if (s.isGreedyLabel()) {
-                for (int j = i + 1; j < segments.size(); j++) {
-                    if (segments.get(j).isGreedyLabel()) {
-                        throw new InvalidPatternException(
-                                "At most one greedy label segment may exist in a pattern: " + pattern);
-                    } else if (segments.get(j).isLabel()) {
-                        throw new InvalidPatternException(
-                                "A greedy label must be the last label in its pattern: " + pattern);
-                    }
-                }
-            }
-        }
-    }
-
     /**
      * @return Returns a builder used to create a SmithyPattern.
      */
@@ -310,12 +289,26 @@ public class SmithyPattern {
         }
 
         /**
-         * @return True if the segment is a label.
+         * @return True if the segment is a non-label literal.
+         */
+        public boolean isLiteral() {
+            return segmentType == Type.LITERAL;
+        }
+
+        /**
+         * @return True if the segment is a label regardless of whether is greedy or not.
          */
         public boolean isLabel() {
             return segmentType != Type.LITERAL;
         }
 
+        /**
+         * @return True if the segment is a non-greedy label.
+         */
+        public boolean isNonGreedyLabel() {
+            return segmentType == Type.LABEL;
+        }
+
         /**
          * @return True if the segment is a greedy label.
          */
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/pattern/UriPattern.java b/smithy-model/src/main/java/software/amazon/smithy/model/pattern/UriPattern.java
index 3df67138b..e9847ed2e 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/pattern/UriPattern.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/pattern/UriPattern.java
@@ -21,6 +21,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import software.amazon.smithy.utils.Pair;
 
@@ -70,8 +71,8 @@ public final class UriPattern extends SmithyPattern {
             throw new InvalidUriPatternException("URI pattern must not contain a fragment. Found " + uri);
         }
 
-        String[] parts = uri.split(java.util.regex.Pattern.quote("?"), 2);
-        String[] unparsedSegments = parts[0].split(java.util.regex.Pattern.quote("/"));
+        String[] parts = uri.split(Pattern.quote("?"), 2);
+        String[] unparsedSegments = parts[0].split(Pattern.quote("/"));
         List<Segment> segments = new ArrayList<>();
         // Skip the first "/" segment, and thus assume offset of 1.
         int offset = 1;
@@ -88,7 +89,7 @@ public final class UriPattern extends SmithyPattern {
             if (parts[1].contains("{") || parts[1].contains("}")) {
                 throw new InvalidUriPatternException("URI labels must not appear in the query string. Found " + uri);
             }
-            for (String kvp : parts[1].split(java.util.regex.Pattern.quote("&"))) {
+            for (String kvp : parts[1].split(Pattern.quote("&"))) {
                 String[] parameterParts = kvp.split("=", 2);
                 String actualKey = parameterParts[0];
                 if (queryLiterals.containsKey(actualKey)) {
@@ -127,30 +128,29 @@ public final class UriPattern extends SmithyPattern {
      * @return Returns true if there is a conflict.
      */
     public boolean conflictsWith(UriPattern otherPattern) {
-        if (!getConflictingLabelSegmentsMap(otherPattern).isEmpty()) {
-            return true;
-        }
-
         List<Segment> segments = getSegments();
         List<Segment> otherSegments = otherPattern.getSegments();
 
-        // By now we know there are no label conflicts, so one uri has more
-        // segments than the other then they don't conflict.
+        // If one uri has more segments than the other, they don't conflict.
         if (segments.size() != otherSegments.size()) {
             return false;
         }
 
-        // Now we need to check for the differences  in the static segments of the uri.
+        // We check if the patterns are equivalent, if so then there's a conflict.
         for (int i = 0; i < segments.size(); i++) {
             Segment segment = segments.get(i);
             Segment otherSegment = otherSegments.get(i);
-            // We've already checked for label conflicts, so we can skip them here.
-            if (segment.isLabel() || otherSegment.isLabel()) {
+            if (segment.isGreedyLabel() && otherSegment.isGreedyLabel()) {
+                continue;
+            }
+            if (segment.isNonGreedyLabel() && otherSegment.isNonGreedyLabel()) {
                 continue;
             }
-            if (!segment.getContent().equals(otherSegment.getContent())) {
-                return false;
+            if (segment.isLiteral() && otherSegment.isLiteral()
+                && segment.getContent().equals(otherSegment.getContent())) {
+                continue;
             }
+            return false;
         }
 
         // At this point, the path portions are equivalent. If the query
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpUriGreedyLabelValidator.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpUriGreedyLabelValidator.java
new file mode 100644
index 000000000..3ea4a6987
--- /dev/null
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpUriGreedyLabelValidator.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 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.validators;
+
+import java.util.ArrayList;
+import java.util.List;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.pattern.SmithyPattern;
+import software.amazon.smithy.model.pattern.UriPattern;
+import software.amazon.smithy.model.shapes.Shape;
+import software.amazon.smithy.model.traits.HttpTrait;
+import software.amazon.smithy.model.validation.AbstractValidator;
+import software.amazon.smithy.model.validation.ValidationEvent;
+
+/**
+ * Validates that at most one greedy label is present in the pattern, and, if any greedy label is present that it's
+ * the last label in the pattern. This validation emits DANGER events which can be suppressed if the server allows
+ * any of these. Some servers do, but most don't.
+ */
+public class HttpUriGreedyLabelValidator extends AbstractValidator {
+
+    private static final String MULTIPLE_GREEDY_LABELS = "MultipleGreedyLabels";
+    private static final String GREEDY_LABEL_IS_NOT_LAST_LABEL = "GreedyLabelIsNotLastLabel";
+
+    @Override
+    public List<ValidationEvent> validate(Model model) {
+        List<ValidationEvent> events = new ArrayList<>();
+
+        for (Shape shape : model.getShapesWithTrait(HttpTrait.class)) {
+            HttpTrait trait = shape.expectTrait(HttpTrait.class);
+            UriPattern pattern = trait.getUri();
+
+            List<SmithyPattern.Segment> segments = pattern.getSegments();
+            // Make sure at most one greedy label exists, and that it is the
+            // last label segment.
+            for (int i = 0; i < segments.size(); i++) {
+                SmithyPattern.Segment s = segments.get(i);
+                if (s.isGreedyLabel()) {
+                    for (int j = i + 1; j < segments.size(); j++) {
+                        if (segments.get(j).isGreedyLabel()) {
+                            events.add(danger(shape, trait,
+                                              "At most one greedy label segment may exist in a pattern: " + pattern,
+                                              MULTIPLE_GREEDY_LABELS));
+                        }
+                        if (segments.get(j).isLabel()) {
+                            events.add(danger(shape, trait,
+                                              "A greedy label must be the last label in its pattern: " + pattern,
+                                              GREEDY_LABEL_IS_NOT_LAST_LABEL));
+                        }
+                    }
+                }
+            }
+        }
+        return events;
+    }
+}
diff --git a/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator b/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
index 551dfd6bf..b70dbef6a 100644
--- a/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
+++ b/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
@@ -19,6 +19,7 @@ software.amazon.smithy.model.validation.validators.HttpPrefixHeadersTraitValidat
 software.amazon.smithy.model.validation.validators.HttpQueryParamsTraitValidator
 software.amazon.smithy.model.validation.validators.HttpQueryTraitValidator
 software.amazon.smithy.model.validation.validators.HttpResponseCodeSemanticsValidator
+software.amazon.smithy.model.validation.validators.HttpUriGreedyLabelValidator
 software.amazon.smithy.model.validation.validators.HttpUriConflictValidator
 software.amazon.smithy.model.validation.validators.HttpUriFormatValidator
 software.amazon.smithy.model.validation.validators.JsonNameValidator
diff --git a/smithy-validation-model/build.gradle b/smithy-validation-model/build.gradle
index 80f5d03be..509a4a24f 100644
--- a/smithy-validation-model/build.gradle
+++ b/smithy-validation-model/build.gradle
@@ -29,3 +29,7 @@ dependencies {
 }
 
 tasks["sourcesJar"].dependsOn("smithyJarStaging")
+
+smithy {
+    smithyBuildConfigs.set(project.files())
+}


Test patch:
diff --git a/smithy-aws-protocol-tests/build.gradle b/smithy-aws-protocol-tests/build.gradle
index 279f4b8a1..e477c53f7 100644
--- a/smithy-aws-protocol-tests/build.gradle
+++ b/smithy-aws-protocol-tests/build.gradle
@@ -36,4 +36,5 @@ tasks["sourcesJar"].dependsOn("smithyJarStaging")
 
 smithy {
     format.set(false)
+    smithyBuildConfigs.set(project.files())
 }
diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/pattern/SmithyPatternTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/pattern/SmithyPatternTest.java
index eae5c47b7..78fa2eda8 100644
--- a/smithy-model/src/test/java/software/amazon/smithy/model/pattern/SmithyPatternTest.java
+++ b/smithy-model/src/test/java/software/amazon/smithy/model/pattern/SmithyPatternTest.java
@@ -129,32 +129,6 @@ public class SmithyPatternTest {
         assertThat(thrown.getMessage(), containsString("Pattern must not contain a greedy label"));
     }
 
-    @Test
-    public void noMoreThanOneGreedyLabel() {
-        Throwable thrown = Assertions.assertThrows(InvalidPatternException.class, () -> {
-            String target = "/{foo+}/{baz+}";
-            SmithyPattern.builder()
-                    .segments(parser(target))
-                    .pattern(target)
-                    .build();
-        });
-
-        assertThat(thrown.getMessage(), containsString("At most one greedy label segment may exist in a pattern"));
-    }
-
-    @Test
-    public void greedyLabelsMustBeLastLabelInPattern() {
-        Throwable thrown = Assertions.assertThrows(InvalidPatternException.class, () -> {
-            String target = "/{foo+}/{baz}";
-            SmithyPattern.builder()
-                    .segments(parser(target))
-                    .pattern(target)
-                    .build();
-        });
-
-        assertThat(thrown.getMessage(), containsString("A greedy label must be the last label in its pattern"));
-    }
-
     @Test
     public void noEmptyLabels() {
         Throwable thrown = Assertions.assertThrows(InvalidPatternException.class, () -> {
diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/pattern/UriPatternTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/pattern/UriPatternTest.java
index 4bd3d54c4..2000dc7f3 100644
--- a/smithy-model/src/test/java/software/amazon/smithy/model/pattern/UriPatternTest.java
+++ b/smithy-model/src/test/java/software/amazon/smithy/model/pattern/UriPatternTest.java
@@ -112,40 +112,43 @@ public class UriPatternTest {
         UriPattern b = UriPattern.parse(patternB);
         if (a.conflictsWith(b) != isConflicting) {
             if (isConflicting) {
-                Assertions.fail("Expected conflict between `" + a + "` and `" + b + "`");
+                Assertions.fail(() -> "Expected conflict between `" + a + "` and `" + b + "`");
             } else {
-                Assertions.fail("Unexpected conflict between `" + a + "` and `" + b + "`");
+                Assertions.fail(() -> "Unexpected conflict between `" + a + "` and `" + b + "`");
             }
         }
     }
 
     public static Collection<Object[]> data() {
         return Arrays.asList(new Object[][] {
-                {"/a/{x}", "/{y}/a", true},
-                {"/{x}/a", "/a/{y}", true},
-                {"/a/{x}", "/a/{y}", true},
+                {"/a/{x}", "/{y}/a", false},
+                {"/{x}/a", "/a/{y}", false},
                 {"/a/{x}", "/b/{y}", false},
-                {"/{x+}", "/a", true},
-                {"/a", "/{x+}", true},
-                {"/a/{x}/b/{y}", "/a/{x+}", true},
-                {"/a/{x+}", "/a/{y}", true},
-                {"/a/{x}", "/a/{y+}", true},
+                {"/{x+}", "/a", false},
+                {"/a", "/{x+}", false},
+                {"/a/{x}/b/{y}", "/a/{x+}", false},
+                {"/a/{x+}", "/a/{y}", false},
+                {"/a/{x}", "/a/{y+}", false},
                 {"/a/{x+}", "/b/{y+}", false},
-                // Same length with conflicting labels.
-                {"/a/{x}/b", "/a/{y}/b", true},
-                // One is longer than the other.
+                {"/a/b/c/d", "/a/{b}/c/{d}", false},
+                {"/foo?a=b", "/foo?a=c", false},
                 {"/a/{x}/b", "/a/{y}/b/{z}", false},
-                // One is longer than the other.
                 {"/a/{x}/b", "/a/{y}/b/{z}/c", false},
-                {"/a/b/c", "/a/{b}/c", true},
-                {"/a/b/c/d", "/a/{b}/c/{d}", true},
-                {"/foo?a", "/foo?a", true},
-                {"/foo?a=b", "/foo?a=b", true},
-                {"/foo?a=b", "/foo?a=c", false},
-                {"/foo?a", "/foo?a=", true},
                 {"/a=b", "/a", false},
                 {"/a=b", "/a=", false},
                 {"/foo?a", "/foo?b", false},
+                // Conflicts can be resolved using query literals
+                {"/a/{x}/b", "/a/{y}/b?x", false},
+                {"/a/{x}/b?x", "/a/{y}/b?y", false},
+                {"/a/{x}/b?x=y", "/a/{y}/b?x=z", false},
+                // Only equivalent patterns are consider conflicts.
+                {"/a/{x}/b", "/a/{y}/b", true},
+                {"/a/{x}/b?x", "/a/{y}/b?x", true},
+                {"/a/{x}", "/a/{y}", true},
+                {"/a/{x+}", "/a/{y+}", true},
+                {"/foo?a", "/foo?a", true},
+                {"/foo?a=b", "/foo?a=b", true},
+                {"/foo?a", "/foo?a=", true},
         });
     }
 }
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-uri-conflict-validator.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-uri-conflict-validator.errors
index a2bfe7613..48a725b28 100644
--- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-uri-conflict-validator.errors
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-uri-conflict-validator.errors
@@ -3,17 +3,8 @@
 [ERROR] ns.foo#C: Operation URI, `/foo`, conflicts with other operation URIs in the same service: [`ns.foo#A` (/foo), `ns.foo#B` (/foo)] | HttpUriConflict
 [ERROR] ns.foo#D: Operation URI, `/baz`, conflicts with other operation URIs in the same service: [`ns.foo#E` (/baz)] | HttpUriConflict
 [ERROR] ns.foo#E: Operation URI, `/baz`, conflicts with other operation URIs in the same service: [`ns.foo#D` (/baz)] | HttpUriConflict
-[ERROR] ns.foo#H: Operation URI, `/pattern-match/{Id}`, conflicts with other operation URIs in the same service: [`ns.foo#I` (/pattern-match/{Id}), `ns.foo#J` (/pattern-match/{Id}), `ns.foo#K` (/pattern-match/foo)] | HttpUriConflict
-[ERROR] ns.foo#I: Operation URI, `/pattern-match/{Id}`, conflicts with other operation URIs in the same service: [`ns.foo#H` (/pattern-match/{Id}), `ns.foo#J` (/pattern-match/{Id}), `ns.foo#K` (/pattern-match/foo)] | HttpUriConflict
+[ERROR] ns.foo#H: Operation URI, `/pattern-match/{Id}`, conflicts with other operation URIs in the same service: [`ns.foo#I` (/pattern-match/{Id}), `ns.foo#J` (/pattern-match/{Id})] | HttpUriConflict
+[ERROR] ns.foo#I: Operation URI, `/pattern-match/{Id}`, conflicts with other operation URIs in the same service: [`ns.foo#H` (/pattern-match/{Id}), `ns.foo#J` (/pattern-match/{Id})] | HttpUriConflict
 [ERROR] ns.foo#J: Operation URI, `/pattern-match/{Id}`, conflicts with other operation URIs in the same service: [`ns.foo#H` (/pattern-match/{Id}), `ns.foo#I` (/pattern-match/{Id})] | HttpUriConflict
-[DANGER] ns.foo#J: Operation URI, `/pattern-match/{Id}`, conflicts with other operation URIs in the same service: [`ns.foo#K` (/pattern-match/foo)]. Pattern traits applied to the label members prevent the label value from evaluating to a conflict, but this is still a poor design. If this is acceptable, this can be suppressed. | HttpUriConflict
-[ERROR] ns.foo#K: Operation URI, `/pattern-match/foo`, conflicts with other operation URIs in the same service: [`ns.foo#H` (/pattern-match/{Id}), `ns.foo#I` (/pattern-match/{Id})] | HttpUriConflict
-[DANGER] ns.foo#K: Operation URI, `/pattern-match/foo`, conflicts with other operation URIs in the same service: [`ns.foo#J` (/pattern-match/{Id})]. Pattern traits applied to the label members prevent the label value from evaluating to a conflict, but this is still a poor design. If this is acceptable, this can be suppressed. | HttpUriConflict
-[DANGER] ns.foo#NestedAllowableConflicts: Operation URI, `/nested/{Id}/bar/baz`, conflicts with other operation URIs in the same service: [`ns.foo#NestedAllowableConflicts2` (/nested/foo/{Id}/baz)]. Pattern traits applied to the label members prevent the label value from evaluating to a conflict, but this is still a poor design. If this is acceptable, this can be suppressed. | HttpUriConflict
-[DANGER] ns.foo#NestedAllowableConflicts2: Operation URI, `/nested/foo/{Id}/baz`, conflicts with other operation URIs in the same service: [`ns.foo#NestedAllowableConflicts` (/nested/{Id}/bar/baz)]. Pattern traits applied to the label members prevent the label value from evaluating to a conflict, but this is still a poor design. If this is acceptable, this can be suppressed. | HttpUriConflict
-[DANGER] ns.foo#DifferingSegmentEndsConflictChecks: Operation URI, `/differing/{Id}/bar/bam/{OtherId}`, conflicts with other operation URIs in the same service: [`ns.foo#DifferingSegmentEndsConflictChecks2` (/differing/foo/bar/bin/{Id})]. Pattern traits applied to the label members prevent the label value from evaluating to a conflict, but this is still a poor design. If this is acceptable, this can be suppressed. | HttpUriConflict
-[DANGER] ns.foo#DifferingSegmentEndsConflictChecks2: Operation URI, `/differing/foo/bar/bin/{Id}`, conflicts with other operation URIs in the same service: [`ns.foo#DifferingSegmentEndsConflictChecks` (/differing/{Id}/bar/bam/{OtherId})]. Pattern traits applied to the label members prevent the label value from evaluating to a conflict, but this is still a poor design. If this is acceptable, this can be suppressed. | HttpUriConflict
-[ERROR] ns.foo#ConflictsWithoutPattern: Operation URI, `/no-pattern-conflict/foo`, conflicts with other operation URIs in the same service: [`ns.foo#ConflictsWithoutPattern2` (/no-pattern-conflict/{Id})] | HttpUriConflict
-[ERROR] ns.foo#ConflictsWithoutPattern2: Operation URI, `/no-pattern-conflict/{Id}`, conflicts with other operation URIs in the same service: [`ns.foo#ConflictsWithoutPattern` (/no-pattern-conflict/foo)] | HttpUriConflict
 [ERROR] ns.foo#LackOfPatternEnablesConflict1: Operation URI, `/double-endpoint-prefix-conflicts`, conflicts with other operation URIs in the same service: [`ns.foo#LackOfPatternEnablesConflict2` (/double-endpoint-prefix-conflicts)] | HttpUriConflict
 [ERROR] ns.foo#LackOfPatternEnablesConflict2: Operation URI, `/double-endpoint-prefix-conflicts`, conflicts with other operation URIs in the same service: [`ns.foo#LackOfPatternEnablesConflict1` (/double-endpoint-prefix-conflicts)] | HttpUriConflict
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-uri-greedy-labels-validator1.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-uri-greedy-labels-validator1.errors
new file mode 100644
index 000000000..73d4848e4
--- /dev/null
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-uri-greedy-labels-validator1.errors
@@ -0,0 +1,2 @@
+[DANGER] smithy.example#OperationOne: A greedy label must be the last label in its pattern: /example/{greedyOne+}/bar/{greedyTwo+} | HttpUriGreedyLabel.GreedyLabelIsNotLastLabel
+[DANGER] smithy.example#OperationOne: At most one greedy label segment may exist in a pattern: /example/{greedyOne+}/bar/{greedyTwo+} | HttpUriGreedyLabel.MultipleGreedyLabels
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-uri-greedy-labels-validator1.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-uri-greedy-labels-validator1.smithy
new file mode 100644
index 000000000..666090a8e
--- /dev/null
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-uri-greedy-labels-validator1.smithy
@@ -0,0 +1,28 @@
+$version: "2"
+namespace smithy.example
+
+service SmithyExample {
+    operations: [
+        OperationOne
+    ]
+}
+
+@http(code: 200, method: "GET", uri: "/example/{greedyOne+}/bar/{greedyTwo+}")
+@readonly
+operation OperationOne {
+    input: OperationOneInput
+    output: OperationOneOutput
+}
+
+structure OperationOneInput {
+    @httpLabel
+    @required
+    greedyOne: String
+
+    @httpLabel
+    @required
+    greedyTwo: String
+}
+
+structure OperationOneOutput {
+}
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-uri-greedy-labels-validator2.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-uri-greedy-labels-validator2.errors
new file mode 100644
index 000000000..fb07cd95a
--- /dev/null
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-uri-greedy-labels-validator2.errors
@@ -0,0 +1,2 @@
+[DANGER] smithy.example#OperationOne: A greedy label must be the last label in its pattern: /example/{greedyOne+}/bar/{nonGreedyLabel} | HttpUriGreedyLabel.GreedyLabelIsNotLastLabel
+
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-uri-greedy-labels-validator2.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-uri-greedy-labels-validator2.smithy
new file mode 100644
index 000000000..2b1f56bc2
--- /dev/null
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-uri-greedy-labels-validator2.smithy
@@ -0,0 +1,28 @@
+$version: "2"
+namespace smithy.example
+
+service SmithyExample {
+    operations: [
+        OperationOne
+    ]
+}
+
+@http(code: 200, method: "GET", uri: "/example/{greedyOne+}/bar/{nonGreedyLabel}")
+@readonly
+operation OperationOne {
+    input: OperationOneInput
+    output: OperationOneOutput
+}
+
+structure OperationOneInput {
+    @httpLabel
+    @required
+    greedyOne: String
+
+    @httpLabel
+    @required
+    nonGreedyLabel: String
+}
+
+structure OperationOneOutput {
+}
diff --git a/smithy-protocol-tests/build.gradle b/smithy-protocol-tests/build.gradle
index fb711a704..b27bd37cc 100644
--- a/smithy-protocol-tests/build.gradle
+++ b/smithy-protocol-tests/build.gradle
@@ -25,4 +25,5 @@ tasks["sourcesJar"].dependsOn("smithyJarStaging")
 
 smithy {
     format.set(false)
+    smithyBuildConfigs.set(project.files())
 }