Repository: smithy-lang/smithy
Pull request #2222 (2024-04-02 16:15:53)

Use `@deprecated` info when building OpenAPI description

#### Background
The objective is to add `@deprecated` to the description of the definition in OpenAPI.

Let me know if this makes sense or if the way the description is built makes sense.

#### Testing

Added a couple of tests.



Patch:
diff --git a/.github/workflows/post-release.yml b/.github/workflows/post-release.yml
new file mode 100644
index 000000000..3d3d7333d
--- /dev/null
+++ b/.github/workflows/post-release.yml
@@ -0,0 +1,29 @@
+# Runs certain jobs after a release is published or on an as-needed basis (manually),
+# e.g. triggering an update to the homebrew-tap when a new version of the CLI is available
+
+name: post-release
+on:
+  release:
+    types: [released]
+  workflow_dispatch: # on button click
+    inputs:
+      version:
+        description: 'The version that was released (e.g. 1.47.0)'
+        required: true
+        type: string
+
+env:
+  version: ${{ github.event.inputs.version || github.ref_name }}
+
+jobs:
+  homebrew-tap:
+    runs-on: ubuntu-latest
+    steps:
+    - name: Trigger homebrew-tap update 
+      uses: benc-uk/workflow-dispatch@v1
+      with:
+        workflow: autoupdate
+        repo: smithy-lang/homebrew-tap
+        inputs: '{ "version": "${{ env.version }}" }'
+        # Required when using the `repo` option
+        token: "${{ secrets.PR_TOKEN }}"
\ No newline at end of file
diff --git a/.github/workflows/update-brew.yml b/.github/workflows/update-brew.yml
deleted file mode 100644
index 9a86b65de..000000000
--- a/.github/workflows/update-brew.yml
+++ /dev/null
@@ -1,118 +0,0 @@
-name: update-brew
-on:
-  release:
-    types: [released]
-  workflow_dispatch: # on button click
-
-env:
-  release_prefix: https://github.com/smithy-lang/smithy/releases/download
-
-jobs:
-  download-and-save:
-    runs-on: ubuntu-latest
-    steps:
-      - name: mac
-        if: startsWith(github.ref, 'refs/tags/')
-        run: |
-          version="${GITHUB_REF##*/}"
-          hash_url="${{ env.release_prefix }}/${version}/smithy-cli-darwin-x86_64.tar.gz"
-          echo $hash_url
-          curl -fJLO $hash_url.sha256
-      - name: mac-arm
-        if: startsWith(github.ref, 'refs/tags/')
-        run: |
-          version="${GITHUB_REF##*/}"
-          hash_url="${{ env.release_prefix }}/${version}/smithy-cli-darwin-aarch64.tar.gz"
-          echo $hash_url
-          curl -fJLO $hash_url.sha256
-      - name: linux
-        if: startsWith(github.ref, 'refs/tags/')
-        run: |
-          version="${GITHUB_REF##*/}"
-          hash_url="${{ env.release_prefix }}/${version}/smithy-cli-linux-x86_64.tar.gz"
-          echo $hash_url
-          curl -fJLO $hash_url.sha256
-      - name: linux-arm
-        if: startsWith(github.ref, 'refs/tags/')
-        run: |
-          version="${GITHUB_REF##*/}"
-          hash_url="${{ env.release_prefix }}/${version}/smithy-cli-linux-aarch64.tar.gz"
-          echo $hash_url
-          curl -fJLO $hash_url.sha256
-      - name: Save artifacts
-        uses: actions/upload-artifact@v4
-        with:
-          name: brew-artifacts
-          path: '*.sha256'
-          retention-days: 7
-
-  create-pr:
-    runs-on: ubuntu-latest
-    needs: download-and-save
-    permissions:
-      pull-requests: write
-    steps:
-      - name: Get artifacts
-        id: download
-        uses: actions/download-artifact@v4
-        with:
-          name: brew-artifacts
-      - name: Checkout bottle repo
-        uses: actions/checkout@v4
-        with:
-          repository: 'smithy-lang/homebrew-tap'
-          path: 'homebrew-tap'
-          ref: 'main'
-          token: ${{ secrets.PR_TOKEN }}
-      - name: Update version
-        run: |
-          version="${GITHUB_REF##*/}"
-          tmp=$(mktemp)
-          jq --arg version "${version}" '.version = $version' homebrew-tap/bottle-configs/smithy-cli.json > "$tmp" && mv "$tmp" homebrew-tap/bottle-configs/smithy-cli.json
-      - name: Update root_url
-        run: |
-          version="${GITHUB_REF##*/}"
-          tmp=$(mktemp)
-          jq --arg release_url "${{ env.release_prefix }}" --arg version "${version}" '.bottle.root_url = "\($release_url)/\($version)/smithy-cli"' homebrew-tap/bottle-configs/smithy-cli.json > "$tmp" && mv "$tmp" homebrew-tap/bottle-configs/smithy-cli.json
-      - name: Update mac
-        run: |
-          version="${GITHUB_REF##*/}"
-          sha=$(cut -d " " -f1 ${{steps.download.outputs.download-path}}/smithy-cli-darwin-x86_64.tar.gz.sha256)
-          tmp=$(mktemp)
-          jq --arg sha "$sha" '.bottle.sha256.sierra = "'$sha'"' homebrew-tap/bottle-configs/smithy-cli.json > "$tmp" && mv "$tmp" homebrew-tap/bottle-configs/smithy-cli.json
-      - name: Update mac-arm
-        run: |
-          version="${GITHUB_REF##*/}"
-          sha=$(cut -d " " -f1 ${{steps.download.outputs.download-path}}/smithy-cli-darwin-aarch64.tar.gz.sha256)
-          tmp=$(mktemp)
-          jq --arg sha "$sha" '.bottle.sha256.arm64_big_sur = "'$sha'"' homebrew-tap/bottle-configs/smithy-cli.json > "$tmp" && mv "$tmp" homebrew-tap/bottle-configs/smithy-cli.json
-      - name: Update linux
-        run: |
-          version="${GITHUB_REF##*/}"
-          sha=$(cut -d " " -f1 ${{steps.download.outputs.download-path}}/smithy-cli-linux-x86_64.tar.gz.sha256)
-          tmp=$(mktemp)
-          jq --arg sha "$sha" '.bottle.sha256.linux = "'$sha'"' homebrew-tap/bottle-configs/smithy-cli.json > "$tmp" && mv "$tmp" homebrew-tap/bottle-configs/smithy-cli.json
-      - name: Update linux-arm
-        run: |
-          version="${GITHUB_REF##*/}"
-          sha=$(cut -d " " -f1 ${{steps.download.outputs.download-path}}/smithy-cli-linux-aarch64.tar.gz.sha256)
-          tmp=$(mktemp)
-          jq --arg sha "$sha" '.bottle.sha256.linux_arm = "'$sha'"' homebrew-tap/bottle-configs/smithy-cli.json > "$tmp" && mv "$tmp" homebrew-tap/bottle-configs/smithy-cli.json
-      - name: Set up new git branch for version bump
-        working-directory: homebrew-tap
-        run: |
-          git config user.name 'smithy-automation'
-          git config user.email 'github-smithy-automation@amazon.com'
-          git checkout -b "automation/bump-smithy-cli-version/${GITHUB_REF##*/}"
-      - name: Create PR
-        working-directory: homebrew-tap
-        run: |
-          git add bottle-configs/smithy-cli.json
-          git commit -m "chore: upgrade smithy-cli to ${GITHUB_REF##*/}"
-          git push --set-upstream origin "automation/bump-smithy-cli-version/${GITHUB_REF##*/}"
-          gh pr create \
-            --title "[Automation] chore: upgrade smithy-cli to ${GITHUB_REF##*/}" \
-            --body "Created by ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
-            --base main
-        env:
-          GITHUB_TOKEN: ${{ secrets.PR_TOKEN }}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 427c39628..96aea2704 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -78,3 +78,12 @@ If you discover a potential security issue in this project we ask that you notif
 ## Licensing
 
 See the [LICENSE](https://github.com/smithy-lang/smithy/blob/main/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
+
+Please use the following license header in new code files:
+
+```
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+```
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-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaShapeVisitor.java b/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaShapeVisitor.java
index 46550dfff..4f6898ff5 100644
--- a/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaShapeVisitor.java
+++ b/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaShapeVisitor.java
@@ -18,6 +18,7 @@ package software.amazon.smithy.jsonschema;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import java.util.regex.Pattern;
 import software.amazon.smithy.model.Model;
@@ -43,6 +44,7 @@ import software.amazon.smithy.model.shapes.StructureShape;
 import software.amazon.smithy.model.shapes.TimestampShape;
 import software.amazon.smithy.model.shapes.UnionShape;
 import software.amazon.smithy.model.traits.DefaultTrait;
+import software.amazon.smithy.model.traits.DeprecatedTrait;
 import software.amazon.smithy.model.traits.DocumentationTrait;
 import software.amazon.smithy.model.traits.EnumTrait;
 import software.amazon.smithy.model.traits.LengthTrait;
@@ -100,13 +102,16 @@ final class JsonSchemaShapeVisitor extends ShapeVisitor.Default<Schema> {
         if (converter.isInlined(member)) {
             return member.accept(this);
         } else {
+            Schema.Builder refBuilder = Schema.builder().ref(converter.toPointer(member.getTarget()));
+            if (member.hasTrait(DeprecatedTrait.class) && getJsonSchemaVersion() != JsonSchemaVersion.DRAFT07) {
+                refBuilder.deprecated(true);
+            }
             // Wrap the ref and default in an allOf if disableDefaultValues has been not been disabled on config.
             if (member.hasTrait(DefaultTrait.class) && !converter.getConfig().getDisableDefaultValues()) {
-                Schema ref = Schema.builder().ref(converter.toPointer(member.getTarget())).build();
                 Schema def = Schema.builder().defaultValue(member.expectTrait(DefaultTrait.class).toNode()).build();
-                return Schema.builder().allOf(ListUtils.of(ref, def)).build();
+                return Schema.builder().allOf(ListUtils.of(refBuilder.build(), def)).build();
             }
-            return Schema.builder().ref(converter.toPointer(member.getTarget())).build();
+            return refBuilder.build();
         }
     }
 
@@ -274,9 +279,7 @@ final class JsonSchemaShapeVisitor extends ShapeVisitor.Default<Schema> {
      * @return Returns the updated schema builder.
      */
     private Schema.Builder updateBuilder(Shape shape, Schema.Builder builder) {
-        shape.getMemberTrait(model, DocumentationTrait.class)
-                .map(DocumentationTrait::getValue)
-                .ifPresent(builder::description);
+        descriptionMessage(shape).ifPresent(builder::description);
 
         shape.getTrait(TitleTrait.class)
                 .map(TitleTrait::getValue)
@@ -329,9 +332,31 @@ final class JsonSchemaShapeVisitor extends ShapeVisitor.Default<Schema> {
             builder.defaultValue(shape.expectTrait(DefaultTrait.class).toNode());
         }
 
+        if (shape.hasTrait(DeprecatedTrait.class) && getJsonSchemaVersion() != JsonSchemaVersion.DRAFT07) {
+            builder.deprecated(true);
+        }
+
         return builder;
     }
 
+    private Optional<String> descriptionMessage(Shape shape) {
+        StringBuilder builder = new StringBuilder();
+        shape
+            .getTrait(DocumentationTrait.class)
+            .ifPresent(trait ->
+                builder.append(trait.getValue())
+            );
+        shape
+            .getTrait(DeprecatedTrait.class)
+            .ifPresent(trait ->
+                builder
+                    .append("\n")
+                    .append(trait.getDeprecatedDescription(shape.getType()))
+            );
+        String description = builder.toString().trim();
+        return description.isEmpty() ? Optional.empty() : Optional.of(description);
+    }
+
     /**
      * Builds a schema builder and applied schema mappers.
      *
@@ -351,4 +376,8 @@ final class JsonSchemaShapeVisitor extends ShapeVisitor.Default<Schema> {
 
         return builder.build();
     }
+
+    private JsonSchemaVersion getJsonSchemaVersion() {
+        return converter.getConfig().getJsonSchemaVersion();
+    }
 }
diff --git a/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/Schema.java b/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/Schema.java
index 08a33ccf8..7d820b970 100644
--- a/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/Schema.java
+++ b/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/Schema.java
@@ -102,6 +102,7 @@ public final class Schema implements ToNode, ToSmithyBuilder<Schema> {
     private final boolean writeOnly;
     private final String comment;
     private final Node examples;
+    private final boolean deprecated;
 
     private final String contentEncoding;
     private final String contentMediaType;
@@ -153,6 +154,7 @@ public final class Schema implements ToNode, ToSmithyBuilder<Schema> {
         writeOnly = builder.writeOnly;
         comment = builder.comment;
         examples = builder.examples;
+        deprecated = builder.deprecated;
 
         contentEncoding = builder.contentEncoding;
         contentMediaType = builder.contentMediaType;
@@ -312,6 +314,10 @@ public final class Schema implements ToNode, ToSmithyBuilder<Schema> {
         return Optional.ofNullable(examples);
     }
 
+    public boolean isDeprecated() {
+        return deprecated;
+    }
+
     public Optional<String> getContentEncoding() {
         return Optional.ofNullable(contentEncoding);
     }
@@ -364,6 +370,7 @@ public final class Schema implements ToNode, ToSmithyBuilder<Schema> {
 
                 .withOptionalMember("comment", getComment().map(Node::from))
                 .withOptionalMember("examples", getExamples())
+                .withOptionalMember("deprecated", this.deprecated ? Optional.of(Node.from(true)) : Optional.empty())
                 .withOptionalMember("title", getTitle().map(Node::from))
                 .withOptionalMember("description", getDescription().map(Node::from))
                 .withOptionalMember("format", getFormat().map(Node::from))
@@ -421,6 +428,10 @@ public final class Schema implements ToNode, ToSmithyBuilder<Schema> {
             result.withMember("writeOnly", Node.from(true));
         }
 
+        if (deprecated) {
+            result.withMember("deprecated", Node.from(true));
+        }
+
         for (Map.Entry<String, ToNode> entry : extensions.entrySet()) {
             result.withMember(entry.getKey(), entry.getValue().toNode());
         }
@@ -540,6 +551,7 @@ public final class Schema implements ToNode, ToSmithyBuilder<Schema> {
                 .writeOnly(writeOnly)
                 .comment(comment)
                 .examples(examples)
+                .deprecated(deprecated)
 
                 .contentEncoding(contentEncoding)
                 .contentMediaType(contentMediaType);
@@ -611,6 +623,7 @@ public final class Schema implements ToNode, ToSmithyBuilder<Schema> {
         private boolean writeOnly;
         private String comment;
         private Node examples;
+        private boolean deprecated;
 
         private String contentEncoding;
         private String contentMediaType;
@@ -853,6 +866,11 @@ public final class Schema implements ToNode, ToSmithyBuilder<Schema> {
             return this;
         }
 
+        public Builder deprecated(boolean deprecated) {
+            this.deprecated = deprecated;
+            return this;
+        }
+
         public Builder extensions(Map<String, Node> extensions) {
             this.extensions.clear();
             this.extensions.putAll(extensions);
@@ -945,6 +963,8 @@ public final class Schema implements ToNode, ToSmithyBuilder<Schema> {
                     return this.contentMediaType(null);
                 case "examples":
                     return this.examples(null);
+                case "deprecated":
+                    return this.deprecated(false);
                 default:
                     LOGGER.warning("Unknown JSON Schema config 'disable' property: " + propertyName);
                     return this;
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/traits/DeprecatedTrait.java b/smithy-model/src/main/java/software/amazon/smithy/model/traits/DeprecatedTrait.java
index d5b0b6ffa..d960b5d19 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/traits/DeprecatedTrait.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/traits/DeprecatedTrait.java
@@ -19,6 +19,7 @@ import java.util.Optional;
 import software.amazon.smithy.model.node.Node;
 import software.amazon.smithy.model.node.ObjectNode;
 import software.amazon.smithy.model.shapes.ShapeId;
+import software.amazon.smithy.model.shapes.ShapeType;
 import software.amazon.smithy.utils.MapUtils;
 import software.amazon.smithy.utils.ToSmithyBuilder;
 
@@ -72,6 +73,20 @@ public final class DeprecatedTrait extends AbstractTrait implements ToSmithyBuil
         return Optional.ofNullable(message);
     }
 
+    public String getDeprecatedDescription(ShapeType shapeType) {
+        StringBuilder builder = new StringBuilder("This ");
+        builder.append(shapeType == ShapeType.OPERATION ? "operation" : "shape").append(" is deprecated");
+        if (since != null) {
+            builder.append(" since ").append(since);
+        }
+        if (message != null) {
+            builder.append(": ").append(message);
+        } else {
+            builder.append(".");
+        }
+        return builder.toString();
+    }
+
     @Override
     protected Node createNode() {
         return new ObjectNode(MapUtils.of(), getSourceLocation())
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-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/simple-service-aws.cfn.json b/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/simple-service-aws.cfn.json
index 2c61b7979..e0d8e2ffe 100644
--- a/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/simple-service-aws.cfn.json
+++ b/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/simple-service-aws.cfn.json
@@ -19,7 +19,8 @@
   },
   "properties": {
     "FooDeprecatedMutableProperty": {
-      "type": "string"
+      "type": "string",
+      "description": "This shape is deprecated: Use the `fooValidFullyMutableProperty` property."
     },
     "FooId": {
       "type": "string"
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-jsonschema/src/test/java/software/amazon/smithy/jsonschema/JsonSchemaConverterTest.java b/smithy-jsonschema/src/test/java/software/amazon/smithy/jsonschema/JsonSchemaConverterTest.java
index c65c8b9b3..21410679d 100644
--- a/smithy-jsonschema/src/test/java/software/amazon/smithy/jsonschema/JsonSchemaConverterTest.java
+++ b/smithy-jsonschema/src/test/java/software/amazon/smithy/jsonschema/JsonSchemaConverterTest.java
@@ -54,6 +54,7 @@ import software.amazon.smithy.model.shapes.ShortShape;
 import software.amazon.smithy.model.shapes.StringShape;
 import software.amazon.smithy.model.shapes.StructureShape;
 import software.amazon.smithy.model.shapes.UnionShape;
+import software.amazon.smithy.model.traits.DeprecatedTrait;
 import software.amazon.smithy.model.traits.DocumentationTrait;
 import software.amazon.smithy.model.traits.EnumDefinition;
 import software.amazon.smithy.model.traits.EnumTrait;
@@ -780,4 +781,110 @@ public class JsonSchemaConverterTest {
                 IoUtils.toUtf8String(getClass().getResourceAsStream("int-enums-disabled.jsonschema.v07.json")));
         Node.assertEquals(document.toNode(), expected);
     }
+
+    @Test
+    public void supportsDeprecatedTraitOnAStruct() {
+        StringShape string = StringShape.builder().id("smithy.api#String").build();
+        StructureShape shape = StructureShape.builder()
+                .id(ShapeId.from("a.b#C"))
+                .addMember(MemberShape.builder()
+                        .id(ShapeId.from("a.b#C$member"))
+                        .target(string.getId())
+                        .build())
+                .addTrait(DeprecatedTrait.builder()
+                        .message("I'm deprecated")
+                        .since("sinceVersion")
+                        .build())
+                .build();
+        Model model = Model.builder().addShapes(shape, string).build();
+        JsonSchemaConfig config = new JsonSchemaConfig();
+        config.setJsonSchemaVersion(JsonSchemaVersion.DRAFT2020_12);
+        SchemaDocument document = JsonSchemaConverter.builder()
+                .model(model)
+                .config(config)
+                .build()
+                .convertShape(shape);
+
+        assertThat(document.getRootSchema().isDeprecated(), equalTo(true));
+    }
+    
+    @Test
+    public void dontAddDeprecatedTraitOnAStructWhenOldVersion() {
+        StringShape string = StringShape.builder().id("smithy.api#String").build();
+        StructureShape shape = StructureShape.builder()
+                .id(ShapeId.from("a.b#C"))
+                .addMember(MemberShape.builder()
+                        .id(ShapeId.from("a.b#C$member"))
+                        .target(string.getId())
+                        .build())
+                .addTrait(DeprecatedTrait.builder()
+                        .message("I'm deprecated")
+                        .since("sinceVersion")
+                        .build())
+                .build();
+        Model model = Model.builder().addShapes(shape, string).build();
+        JsonSchemaConfig config = new JsonSchemaConfig();
+        config.setJsonSchemaVersion(JsonSchemaVersion.DRAFT07);
+        SchemaDocument document = JsonSchemaConverter.builder()
+                .model(model)
+                .config(config)
+                .build()
+                .convertShape(shape);
+
+        assertThat(document.getRootSchema().isDeprecated(), equalTo(false));
+    }
+
+    @Test
+    public void supportsDeprecatedTraitOnAMember() {
+        StringShape string = StringShape.builder().id("smithy.api#String").build();
+        StructureShape shape = StructureShape.builder()
+                .id(ShapeId.from("a.b#C"))
+                .addMember(MemberShape.builder()
+                        .id(ShapeId.from("a.b#C$member"))
+                        .target(string.getId())
+                        .addTrait(DeprecatedTrait.builder()
+                                .message("I'm deprecated")
+                                .since("sinceVersion")
+                                .build())
+                        .build())
+                .build();
+        Model model = Model.builder().addShapes(shape, string).build();
+        JsonSchemaConfig config = new JsonSchemaConfig();
+        config.setJsonSchemaVersion(JsonSchemaVersion.DRAFT2020_12);
+        SchemaDocument document = JsonSchemaConverter.builder()
+                .model(model)
+                .config(config)
+                .build()
+                .convertShape(shape);
+
+        Schema memberSchema = document.getRootSchema().getProperties().get("member");
+        assertThat(memberSchema.isDeprecated(), equalTo(true));
+    }
+
+    @Test
+    public void dontAddDeprecatedTraitOnAMemberWhenOldVersion() {
+        StringShape string = StringShape.builder().id("smithy.api#String").build();
+        StructureShape shape = StructureShape.builder()
+                .id(ShapeId.from("a.b#C"))
+                .addMember(MemberShape.builder()
+                        .id(ShapeId.from("a.b#C$member"))
+                        .target(string.getId())
+                        .addTrait(DeprecatedTrait.builder()
+                                .message("I'm deprecated")
+                                .since("sinceVersion")
+                                .build())
+                        .build())
+                .build();
+        Model model = Model.builder().addShapes(shape, string).build();
+        JsonSchemaConfig config = new JsonSchemaConfig();
+        config.setJsonSchemaVersion(JsonSchemaVersion.DRAFT07);
+        SchemaDocument document = JsonSchemaConverter.builder()
+                .model(model)
+                .config(config)
+                .build()
+                .convertShape(shape);
+
+        Schema memberSchema = document.getRootSchema().getProperties().get("member");
+        assertThat(memberSchema.isDeprecated(), equalTo(false));
+    }
 }
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/java/software/amazon/smithy/model/traits/DeprecatedTraitTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/traits/DeprecatedTraitTest.java
index bcb4c8979..242d310ce 100644
--- a/smithy-model/src/test/java/software/amazon/smithy/model/traits/DeprecatedTraitTest.java
+++ b/smithy-model/src/test/java/software/amazon/smithy/model/traits/DeprecatedTraitTest.java
@@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test;
 import software.amazon.smithy.model.SourceException;
 import software.amazon.smithy.model.node.Node;
 import software.amazon.smithy.model.shapes.ShapeId;
+import software.amazon.smithy.model.shapes.ShapeType;
 
 public class DeprecatedTraitTest {
     @Test
@@ -48,4 +49,29 @@ public class DeprecatedTraitTest {
             provider.createTrait(ShapeId.from("smithy.api#deprecated"), ShapeId.from("ns.qux#foo"), Node.from("abc"));
         });
     }
+
+    @Test
+    public void returnDefaultDescription() {
+        DeprecatedTrait deprecatedTrait = DeprecatedTrait.builder().build();
+        assertThat(deprecatedTrait.getDeprecatedDescription(ShapeType.OPERATION), equalTo("This operation is deprecated."));
+        assertThat(deprecatedTrait.getDeprecatedDescription(ShapeType.STRING), equalTo("This shape is deprecated."));
+    }
+
+    @Test
+    public void returnDescriptionWhenMessageSet() {
+        DeprecatedTrait deprecatedTrait = DeprecatedTrait.builder().message("Use X shape instead.").build();
+        assertThat(deprecatedTrait.getDeprecatedDescription(ShapeType.STRING), equalTo("This shape is deprecated: Use X shape instead."));
+    }
+
+    @Test
+    public void returnDescriptionWhenSinceSet() {
+        DeprecatedTrait deprecatedTrait = DeprecatedTrait.builder().since("2020-01-01").build();
+        assertThat(deprecatedTrait.getDeprecatedDescription(ShapeType.STRING), equalTo("This shape is deprecated since 2020-01-01."));
+    }
+
+    @Test
+    public void returnDescriptionWhenBothSinceAndMessageSet() {
+        DeprecatedTrait deprecatedTrait = DeprecatedTrait.builder().since("2020-01-01").message("Use X shape instead.").build();
+        assertThat(deprecatedTrait.getDeprecatedDescription(ShapeType.STRING), equalTo("This shape is deprecated since 2020-01-01: Use X shape instead."));
+    }
 }
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-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiJsonSchemaMapperTest.java b/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiJsonSchemaMapperTest.java
index eb87b08b6..6a1318e4b 100644
--- a/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiJsonSchemaMapperTest.java
+++ b/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiJsonSchemaMapperTest.java
@@ -43,6 +43,7 @@ import software.amazon.smithy.model.shapes.ShortShape;
 import software.amazon.smithy.model.shapes.StringShape;
 import software.amazon.smithy.model.shapes.StructureShape;
 import software.amazon.smithy.model.traits.DeprecatedTrait;
+import software.amazon.smithy.model.traits.DocumentationTrait;
 import software.amazon.smithy.model.traits.DynamicTrait;
 import software.amazon.smithy.model.traits.ExternalDocumentationTrait;
 import software.amazon.smithy.model.traits.SensitiveTrait;
@@ -131,6 +132,26 @@ public class OpenApiJsonSchemaMapperTest {
         assertThat(document.getRootSchema().getExtension("deprecated").get(), equalTo(Node.from(true)));
     }
 
+    @Test
+    public void appendsDeprecatedInfoInDescription() {
+        String message = "Use a.b#D instead.";
+        String since = "2020-01-01";
+        IntegerShape shape = IntegerShape.builder()
+                .id("a.b#C")
+                .addTrait(DeprecatedTrait.builder().message(message).since(since).build())
+                .addTrait(new DocumentationTrait("This is an integer."))
+                .build();
+        Model model = Model.builder().addShape(shape).build();
+        SchemaDocument document = JsonSchemaConverter.builder()
+                .addMapper(new OpenApiJsonSchemaMapper())
+                .model(model)
+                .build()
+                .convertShape(shape);
+
+        String expected = "This is an integer.\nThis shape is deprecated since 2020-01-01: Use a.b#D instead.";
+        assertThat(document.getRootSchema().getDescription().get(), equalTo(expected));
+    }
+
     @Test
     public void supportsInt32() {
         IntegerShape shape = IntegerShape.builder().id("a.b#C").build();
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())
 }