Repository: tavis-software/Tavis.UriTemplates
Branch: main
Commit: b8c26d0a2489
Files: 33
Total size: 111.4 KB
Directory structure:
gitextract_a58_z357/
├── .gitattributes
├── .github/
│ ├── CODEOWNERS
│ ├── dependabot.yml
│ └── workflows/
│ ├── auto-merge-dependabot.yml
│ ├── buildAndDeploy.yml
│ └── codeql-analysis.yml
├── .gitignore
├── .gitmodules
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── UriTemplateKey.snk
├── UriTemplates.sln
├── build.cmd
└── src/
├── UriTemplateTests/
│ ├── App.config
│ ├── ParameterMatchingTests.cs
│ ├── SpecTests.cs
│ ├── UriExtensionTests.cs
│ ├── UriTemplateConverterTests.cs
│ ├── UriTemplateExtensionsTests.cs
│ ├── UriTemplateTableTests.cs
│ ├── UriTemplateTests.csproj
│ └── UsageTests.cs
└── UriTemplates/
├── OperatorInfo.cs
├── QueryStringParameterOrder.cs
├── Result.cs
├── UriTemplate.cs
├── UriTemplateConverter.cs
├── UriTemplateExtensions.cs
├── UriTemplateTable.cs
├── UriTemplates.csproj
└── VarSpec.cs
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
*.jpg binary
*.png binary
*.gif binary
*.cs text diff=csharp
*.vb text
*.c text
*.cpp text
*.cxx text
*.h text
*.hxx text
*.py text
*.rb text
*.java text
*.html text
*.htm text
*.css text
*.scss text
*.sass text
*.less text
*.js text
*.lisp text
*.clj text
*.sql text
*.php text
*.lua text
*.m text
*.asm text
*.erl text
*.fs text
*.fsx text
*.hs text
*.csproj text merge=union
*.vbproj text merge=union
*.fsproj text merge=union
*.dbproj text merge=union
*.sln text eol=crlf merge=union
================================================
FILE: .github/CODEOWNERS
================================================
* @darrelmiller
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: nuget
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
- package-ecosystem: gitsubmodule
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
================================================
FILE: .github/workflows/auto-merge-dependabot.yml
================================================
name: Auto-merge dependabot updates
on:
pull_request:
branches: [ main ]
permissions:
pull-requests: write
contents: write
jobs:
dependabot-merge:
runs-on: ubuntu-latest
if: ${{ github.actor == 'dependabot[bot]' }}
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v2.4.0
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Enable auto-merge for Dependabot PRs
# Only if version bump is not a major version change
if: ${{steps.metadata.outputs.update-type != 'version-update:semver-major'}}
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
================================================
FILE: .github/workflows/buildAndDeploy.yml
================================================
name: Build and Test
on:
workflow_dispatch:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
env:
solutionName: UriTemplates.sln
outputFolder: ./buildArtifacts
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: true
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 6.0.x
- name: Restore dependencies
run: dotnet restore ${{ env.solutionName }}
- name: Check formatting
run: dotnet format --verify-no-changes --verbosity diagnostic
- name: Build
run: dotnet build ${{ env.solutionName }} --no-restore -c Release
- name: Test
run: dotnet test ${{ env.solutionName }} --no-build --verbosity normal -c Release /p:CollectCoverage=true /p:CoverletOutput=./TestResults/ /p:CoverletOutputFormat=opencover
- name: Pack
run: dotnet pack ${{ env.solutionName }} /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg --no-build --output ${{ env.outputFolder }} -c Release
- name: Upload Nuget Package and Symbols
uses: actions/upload-artifact@v4
with:
name: drop
path: |
${{ env.outputFolder }}/*.nupkg
${{ env.outputFolder }}/*.snupkg
deploy:
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
environment:
name: production
runs-on: ubuntu-latest
needs: [build]
steps:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 6.0.x
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: drop
- name: Nuget push
run: dotnet nuget push "*.nupkg" --skip-duplicate -s https://api.nuget.org/v3/index.json -k ${{ secrets.PUBLISH_GH_TOKEN }}
================================================
FILE: .github/workflows/codeql-analysis.yml
================================================
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
workflow_dispatch:
push:
branches: [main]
pull_request:
# The branches below must be a subset of the branches above
schedule:
- cron: "20 9 * * 5"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: ["csharp"]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: true
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 6.0.x
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
# - name: Autobuild
# uses: github/codeql-action/autobuild@v2
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
- name: Restore workloads
run: |
dotnet workload restore
dotnet workload install wasm-tools
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore -c Release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
================================================
FILE: .gitignore
================================================
#ignore thumbnails created by windows
Thumbs.db
#Ignore files build by Visual Studio
*.obj
*.exe
*.pdb
*.user
*.aps
*.pch
*.vspscc
*_i.c
*_p.c
*.ncb
*.suo
*.tlb
*.tlh
*.bak
*.cache
*.ilk
*.log
ClientBin
[Bb]in
[Dd]ebug*/
*.lib
*.sbr
obj/
[Rr]elease*/
_ReSharper*/
[Tt]est[Rr]esult*
Examples*/
SerializerTest*/
*.csproj.user
*.resharper*
Download/
#Ignore files from MonoDevelop
*.pidb
*.userprefs
packages
artifacts
*.nupkg
publish-tavis*.ps1
project.lock.json
.vs/
.idea
================================================
FILE: .gitmodules
================================================
[submodule "lib/uritemplate-test"]
path = lib/uritemplate-test
url = https://github.com/uri-templates/uritemplate-test.git
================================================
FILE: .travis.yml
================================================
language: csharp
sudo: false # use the new container-based Travis infrastructure
install:
- nuget restore UriTemplates.sln
script:
- xbuild /p:Configuration=Release /t:Compile build/Build.proj
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Added support for netstandard 2.1 [#69](https://github.com/tavis-software/Tavis.UriTemplates/issues/69)
### Changed
## [2.0.0]
- [Breaking] Updated Target Framework Moniker to `netstandard2.0` and drops `net35`, `net40`, `net45` and `netstandard1.0`.
## [1.1.2]
- Added Type converter support.
## [1.1.1]
- Fixed bug parsing query parameter with comma delimited values
## [1.1.0]
- Updated Target Framework Moniker from dotnet to netstandard1.0
## [1.0.0]
- This project has lived too long as a 0.x release. I believe it has seen enough production use to be considered a 1.0 release.
## [0.6.6]
- Bugfix
## [0.6.5]
- Added ability to retrieve URITemplates from UriTemplateTable
- Parameter matching
## [0.6.4]
- Added .net4 version of assembly
- Updated nuget to put portable lib in dotnet folder to enable coreclr support
- Made Resolve() thread safe by ensuring it does not share any state from one invocation to the next.
- Added support for profile92 to allow including in Portable libraries that target .net4
- Added support for case insensitive parameter names.
## [0.6.3]
- Added ToString() overload to allow retrieving unresolved template
## [0.6.2]
- URI Template Extension AddParameters now uses IDictionary instead of Dictionary
## [0.6.1]
- Added ClearParameter to unset a template parameter
- Added MakeTemplate URI extension for creating a Uri template based on the query string parameters of a URI
- Added GetQueryStringParameters URI extension for building dictionary of query parameters and values
- Added AddParameters overload that accepts a dictionary of template parameters
## [0.6.0]
- Added the ability to partially resolve templates using a new constructor parameter.
- Added new fluent interface using extension methods for quickly creating a template and resolving it.
- Created a .net 45 project
- Restructured folders to comply with recommendations made by David Fowler
- Added many more usage tests with real world scenarios
- Added support for Windows 8.1
- Fixed Unicode encoding problem
- Added support for passing non-string lists as parameters
- Added support for passing parameters values that are non-string.
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2012 Tavis Software Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License 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.
================================================
FILE: README.md
================================================
# Uri Templates #
[](https://github.com/tavis-software/Tavis.UriTemplates/actions/workflows/buildAndDeploy.yml) [](https://github.com/tavis-software/Tavis.UriTemplates/actions/workflows/codeql-analysis.yml) [](https://www.nuget.org/packages/Tavis.UriTemplates/)
.NET implementation of the [URI Template Spec RFC6570](http://tools.ietf.org/html/rfc6570).
Library implements Level 4 compliance and is tested against test cases from [UriTemplate test suite](https://github.com/uri-templates/uritemplate-test).
Here are some basic usage examples:
Replacing a path segment parameter,
```csharp
[Fact]
public void UpdatePathParameter()
{
var url = new UriTemplate("http://example.org/{tenant}/customers")
.AddParameter("tenant", "acmé")
.Resolve();
Assert.Equal("http://example.org/acm%C3%A9/customers", url);
}
```
Setting query string parameters,
```csharp
[Fact]
public void ShouldResolveUriTemplateWithNonStringParameter()
{
var url = new UriTemplate("http://example.org/location{?lat,lng}")
.AddParameters(new { lat = 31.464, lng = 74.386 })
.Resolve();
Assert.Equal("http://example.org/location?lat=31.464&lng=74.386", url);
}
```
Resolving a URI when parameters are not set will simply remove the parameters,
```csharp
[Fact]
public void SomeParametersFromAnObject()
{
var url = new UriTemplate("http://example.org{/environment}{/version}/customers{?active,country}")
.AddParameters(new
{
version = "v2",
active = "true"
})
.Resolve();
Assert.Equal("http://example.org/v2/customers?active=true", url);
}
```
You can even pass lists as parameters
```csharp
[Fact]
public void ApplyParametersObjectWithAListofInts()
{
var url = new UriTemplate("http://example.org/customers{?ids,order}")
.AddParameters(new
{
order = "up",
ids = new[] {21, 75, 21}
})
.Resolve();
Assert.Equal("http://example.org/customers?ids=21,75,21&order=up", url);
}
```
And dictionaries,
```csharp
[Fact]
public void ApplyDictionaryToQueryParameters()
{
var url = new UriTemplate("http://example.org/foo{?coords*}")
.AddParameter("coords", new Dictionary<string, string>
{
{"x", "1"},
{"y", "2"},
})
.Resolve();
Assert.Equal("http://example.org/foo?x=1&y=2", url);
}
```
We also handle all the complex URI encoding rules automatically.
```csharp
[Fact]
public void TestExtremeEncoding()
{
var url = new UriTemplate("http://example.org/sparql{?query}")
.AddParameter("query", "PREFIX dc: <http://purl.org/dc/elements/1.1/> SELECT ?book ?who WHERE { ?book dc:creator ?who }")
.Resolve();
Assert.Equal("http://example.org/sparql?query=PREFIX%20dc%3A%20%3Chttp%3A%2F%2Fpurl.org%2Fdc%2Felements%2F1.1%2F%3E%20SELECT%20%3Fbook%20%3Fwho%20WHERE%20%7B%20%3Fbook%20dc%3Acreator%20%3Fwho%20%7D", url);
}
```
There is a [blogpost](http://bizcoder.com/constructing-urls-the-easy-way) that discusses these examples and more in detail.
As well as having a set of regular usage tests, this library also executes tests based on a standard test suite. This test suite is pulled in as a Git Submodule, therefore when cloning this repo, you will need use the `--recursive` switch,
git clone --recursive git@github.com:tavis-software/Tavis.UriTemplates.git
Current this library does not pass all of the failure tests. I.e. If you pass an invalid URI Template, you may not get an exception.
================================================
FILE: UriTemplates.sln
================================================
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28307.489
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0C994FD2-43CE-4412-BCCD-628DBD8130DF}"
ProjectSection(SolutionItems) = preProject
License.txt = License.txt
Readme.md = Readme.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{718037C6-0F82-456A-B7EB-531694D035B4}"
ProjectSection(SolutionItems) = preProject
ReleaseNotes.md = ReleaseNotes.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UriTemplates", "src\UriTemplates\UriTemplates.csproj", "{F60E3FBC-22FE-43A7-A46B-93C30B74B72A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UriTemplateTests", "src\UriTemplateTests\UriTemplateTests.csproj", "{2D43074E-C8E2-4D1D-B64D-7A267908840B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|Mixed Platforms = Debug|Mixed Platforms
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|Mixed Platforms = Release|Mixed Platforms
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F60E3FBC-22FE-43A7-A46B-93C30B74B72A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F60E3FBC-22FE-43A7-A46B-93C30B74B72A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F60E3FBC-22FE-43A7-A46B-93C30B74B72A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{F60E3FBC-22FE-43A7-A46B-93C30B74B72A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{F60E3FBC-22FE-43A7-A46B-93C30B74B72A}.Debug|x86.ActiveCfg = Debug|Any CPU
{F60E3FBC-22FE-43A7-A46B-93C30B74B72A}.Debug|x86.Build.0 = Debug|Any CPU
{F60E3FBC-22FE-43A7-A46B-93C30B74B72A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F60E3FBC-22FE-43A7-A46B-93C30B74B72A}.Release|Any CPU.Build.0 = Release|Any CPU
{F60E3FBC-22FE-43A7-A46B-93C30B74B72A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{F60E3FBC-22FE-43A7-A46B-93C30B74B72A}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{F60E3FBC-22FE-43A7-A46B-93C30B74B72A}.Release|x86.ActiveCfg = Release|Any CPU
{F60E3FBC-22FE-43A7-A46B-93C30B74B72A}.Release|x86.Build.0 = Release|Any CPU
{2D43074E-C8E2-4D1D-B64D-7A267908840B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2D43074E-C8E2-4D1D-B64D-7A267908840B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D43074E-C8E2-4D1D-B64D-7A267908840B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{2D43074E-C8E2-4D1D-B64D-7A267908840B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{2D43074E-C8E2-4D1D-B64D-7A267908840B}.Debug|x86.ActiveCfg = Debug|Any CPU
{2D43074E-C8E2-4D1D-B64D-7A267908840B}.Debug|x86.Build.0 = Debug|Any CPU
{2D43074E-C8E2-4D1D-B64D-7A267908840B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2D43074E-C8E2-4D1D-B64D-7A267908840B}.Release|Any CPU.Build.0 = Release|Any CPU
{2D43074E-C8E2-4D1D-B64D-7A267908840B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{2D43074E-C8E2-4D1D-B64D-7A267908840B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{2D43074E-C8E2-4D1D-B64D-7A267908840B}.Release|x86.ActiveCfg = Release|Any CPU
{2D43074E-C8E2-4D1D-B64D-7A267908840B}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C814C2F5-05C7-4FAD-A250-2EB1FC0364A0}
EndGlobalSection
EndGlobal
================================================
FILE: build.cmd
================================================
@echo Off
set config=%1
if "%config%" == "" (
set config=debug
)
md artifacts
dotnet build --configuration %config% --verbosity normal /m /v:M /fl /flp:LogFile=msbuild.log; /nr:false
dotnet pack --no-build --configuration %config% --output %cd%\artifacts
================================================
FILE: src/UriTemplateTests/App.config
================================================
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<uri>
<idn enabled="All" />
<iriParsing enabled="true" />
</uri>
</configuration>
================================================
FILE: src/UriTemplateTests/ParameterMatchingTests.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Tavis.UriTemplates;
using Xunit;
using Xunit.Extensions;
namespace UriTemplateTests
{
public class ParameterMatchingTests
{
[Fact]
public void MatchUriToTemplate()
{
var uri = new Uri("http://example.com/foo/bar");
var sTemplate = "http://example.com/{p1}/{p2}";
var x = UriTemplate.CreateMatchingRegex(sTemplate);
var match = Regex.IsMatch(uri.AbsoluteUri, x);
Assert.True(match);
}
[Fact]
public void GetParameters()
{
var uri = new Uri("http://example.com/foo/bar");
var sTemplate = "http://example.com/{p1}/{p2}";
var x = UriTemplate.CreateMatchingRegex(sTemplate);
var regex = new Regex(x);
var match = regex.Match(uri.AbsoluteUri);
Assert.Equal("foo", match.Groups["p1"].Value);
Assert.Equal("bar", match.Groups["p2"].Value);
}
[Fact]
public void GetParametersWithOperators()
{
var uri = new Uri("http://example.com/foo/bar");
var template = new UriTemplate("http://example.com/{+p1}/{p2*}");
var parameters = template.GetParameters(uri);
Assert.Equal(2, parameters.Count);
Assert.Equal("foo", parameters["p1"]);
Assert.Equal("bar", parameters["p2"]);
}
[Fact]
public void GetParametersFromQueryString()
{
var uri = new Uri("http://example.com/foo/bar?blur=45");
var template = new UriTemplate("http://example.com/{+p1}/{p2*}{?blur}");
var parameters = template.GetParameters(uri);
Assert.Equal(3, parameters.Count);
Assert.Equal("foo", parameters["p1"]);
Assert.Equal("bar", parameters["p2"]);
Assert.Equal("45", parameters["blur"]);
}
[Fact]
public void GetParametersFromMultipleQueryString()
{
var uri = new Uri("http://example.com/foo/bar?blur=45");
var template = new UriTemplate("http://example.com/{+p1}/{p2*}{?blur,blob}");
var parameters = template.GetParameters(uri);
Assert.Equal(3, parameters.Count);
Assert.Equal("foo", parameters["p1"]);
Assert.Equal("bar", parameters["p2"]);
Assert.Equal("45", parameters["blur"]);
}
[Fact]
public void GetParametersFromMultipleQueryStringWithTwoParamValues()
{
var uri = new Uri("http://example.com/foo/bar?blur=45&blob=23");
var template = new UriTemplate("http://example.com/{+p1}/{p2*}{?blur,blob}");
var parameters = template.GetParameters(uri);
Assert.Equal(4, parameters.Count);
Assert.Equal("foo", parameters["p1"]);
Assert.Equal("bar", parameters["p2"]);
Assert.Equal("45", parameters["blur"]);
Assert.Equal("23", parameters["blob"]);
}
[Fact]
public void GetParameterFromArrayParameter()
{
var uri = new Uri("http://example.com?blur=45,23");
var template = new UriTemplate("http://example.com{?blur}");
var parameters = template.GetParameters(uri);
Assert.Single(parameters);
Assert.Equal("45,23", parameters["blur"]);
}
[Fact]
public void GetParametersFromMultipleQueryStringWithOptionalAndMandatoryParameters()
{
var uri = new Uri("http://example.com/foo/bar?blur=45&blob=23");
var template = new UriTemplate("http://example.com/{+p1}/{p2*}{?blur}{&blob}");
var parameters = template.GetParameters(uri);
Assert.Equal(4, parameters.Count);
Assert.Equal("foo", parameters["p1"]);
Assert.Equal("bar", parameters["p2"]);
Assert.Equal("45", parameters["blur"]);
Assert.Equal("23", parameters["blob"]);
}
[Fact]
public void GetParametersFromMultipleQueryStringWithOptionalParameters()
{
var uri = new Uri("http://example.com/foo/bar");
var template = new UriTemplate("http://example.com/{+p1}/{p2*}{?blur,blob}");
var parameters = template.GetParameters(uri);
Assert.Equal("foo", parameters["p1"]);
Assert.Equal("bar", parameters["p2"]);
}
[Fact]
public void TestGlimpseUrl()
{
var uri = new Uri("http://example.com/Glimpse.axd?n=glimpse_ajax&parentRequestId=123232323&hash=23ADE34FAE&callback=http%3A%2F%2Fexample.com%2Fcallback");
var template = new UriTemplate("http://example.com/Glimpse.axd?n=glimpse_ajax&parentRequestId={parentRequestId}{&hash,callback}");
var parameters = template.GetParameters(uri);
Assert.Equal(3, parameters.Count);
Assert.Equal("123232323", parameters["parentRequestId"]);
Assert.Equal("23ADE34FAE", parameters["hash"]);
Assert.Equal("http://example.com/callback", parameters["callback"]);
}
[Fact]
public void TestUrlWithQuestionMarkAsFirstCharacter()
{
var parameters = new UriTemplate("?hash={hash}").GetParameters(new Uri("http://localhost:5000/glimpse/metadata?hash=123")); ;
Assert.Single(parameters);
Assert.Equal("123", parameters["hash"]);
}
[Fact]
public void TestExactParameterCount()
{
var uri = new Uri("http://example.com/foo?bar=10");
var template = new UriTemplate("http://example.com/foo{?bar}");
var parameters = template.GetParameters(uri);
Assert.Single(parameters);
}
[Fact]
public void SimplePerfTest()
{
var uri = new Uri("http://example.com/Glimpse.axd?n=glimpse_ajax&parentRequestId=123232323&hash=23ADE34FAE&callback=http%3A%2F%2Fexample.com%2Fcallback");
var template = new UriTemplate("http://example.com/Glimpse.axd?n=glimpse_ajax&parentRequestId={parentRequestId}{&hash,callback}");
for (int i = 0; i < 100000; i++)
{
var parameters = template.GetParameters(uri);
}
}
[Fact]
public void Level1Decode()
{
var uri = new Uri("/Hello%20World", UriKind.RelativeOrAbsolute);
var template = new UriTemplate("/{p1}");
var parameters = template.GetParameters(uri);
Assert.Equal("Hello World", parameters["p1"]);
}
//[Fact]
//public void Level2Decode()
//{
// var uri = new Uri("/foo?path=Hello/World", UriKind.RelativeOrAbsolute);
// var template = new UriTemplate("/foo?path={+p1}");
// var parameters = template.GetParameters(uri);
// Assert.Equal("Hello/World", parameters["p1"]);
//}
[Fact]
public void FragmentParam()
{
var uri = new Uri("/foo#Hello%20World!", UriKind.RelativeOrAbsolute);
var template = new UriTemplate("/foo{#p1}");
var parameters = template.GetParameters(uri);
Assert.Equal("Hello World!", parameters["p1"]);
}
[Fact]
public void FragmentParams()
{
var uri = new Uri("/foo#Hello%20World!,blurg", UriKind.RelativeOrAbsolute);
var template = new UriTemplate("/foo{#p1,p2}");
var parameters = template.GetParameters(uri);
Assert.Equal("Hello World!", parameters["p1"]);
Assert.Equal("blurg", parameters["p2"]);
}
[Fact]
public void OptionalPathParam()
{
var uri = new Uri("/foo/yuck/bob", UriKind.RelativeOrAbsolute);
var template = new UriTemplate("/foo{/bar}/bob");
var parameters = template.GetParameters(uri);
Assert.Equal("yuck", parameters["bar"]);
}
[Fact]
public void OptionalPathParamWithMultipleValues()
{
var uri = new Uri("/foo/yuck/yob/bob", UriKind.RelativeOrAbsolute);
var template = new UriTemplate("/foo{/bar,baz}/bob");
var parameters = template.GetParameters(uri);
Assert.Equal(2, parameters.Count); // This current fails
Assert.Equal("yuck", parameters["bar"]);
Assert.Equal("yob", parameters["baz"]);
}
}
}
================================================
FILE: src/UriTemplateTests/SpecTests.cs
================================================
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json.Linq;
using Tavis.UriTemplates;
using Xunit;
using Xunit.Extensions;
namespace UriTemplateTests
{
public class UriTemplateTests2
{
[Theory, MemberData(nameof(SpecSamples))]
public void SpecSamplesTest(string template, string[] results, TestSet.TestCase testCase)
{
var uriTemplate = new UriTemplate(template);
foreach (var variable in testCase.TestSet.Variables)
{
uriTemplate.SetParameter(variable.Key, variable.Value);
}
string result = null;
result = uriTemplate.Resolve();
Assert.Contains(results, x => x == result);
}
[Theory, MemberData(nameof(ExtendedSamples))]
public void ExtendedSamplesTest(string template, string[] results, TestSet.TestCase testCase)
{
var uriTemplate = new UriTemplate(template);
foreach (var variable in testCase.TestSet.Variables)
{
uriTemplate.SetParameter(variable.Key, variable.Value);
}
string result = null;
ArgumentException aex = null;
try
{
result = uriTemplate.Resolve();
}
catch (ArgumentException ex)
{
aex = ex;
}
if (results[0] == "False")
{
Assert.NotNull(aex);
}
else
{
Assert.Contains(results, x => x == result);
}
}
[Theory(Skip = "Disabled for the moment."), MemberData(nameof(FailureSamples))]
public void FailureSamplesTest(string template, string[] results, TestSet.TestCase testCase)
{
var uriTemplate = new UriTemplate(template);
foreach (var variable in testCase.TestSet.Variables)
{
uriTemplate.SetParameter(variable.Key, variable.Value);
}
string result = null;
ArgumentException aex = null;
try
{
result = uriTemplate.Resolve();
}
catch (ArgumentException ex)
{
aex = ex;
}
if (results[0] == "False")
{
Assert.NotNull(aex);
}
else
{
Assert.Contains(results, x => x == result);
}
}
public static IEnumerable<object[]> SpecSamples
{
get
{
Stream stream = null;
var suites = new List<Dictionary<string, TestSet>>();
stream =
typeof(UriTemplateTests2).Assembly.GetManifestResourceStream("UriTemplateTests.spec-examples.json");
suites.Add(CreateTestSuite(new StreamReader(stream).ReadToEnd()));
stream = typeof(UriTemplateTests2).Assembly.GetManifestResourceStream("UriTemplateTests.spec-examples-by-section.json");
suites.Add(CreateTestSuite(new StreamReader(stream).ReadToEnd()));
foreach (var suite in suites)
{
foreach (var testset in suite.Values)
{
foreach (var testCase in testset.TestCases)
{
yield return new object[] { testCase.Template, testCase.Result, testCase };
}
}
}
}
}
public static IEnumerable<object[]> ExtendedSamples
{
get
{
Stream stream = null;
var suites = new List<Dictionary<string, TestSet>>();
stream =
typeof(UriTemplateTests2).Assembly.GetManifestResourceStream("UriTemplateTests.extended-tests.json");
suites.Add(CreateTestSuite(new StreamReader(stream).ReadToEnd()));
foreach (var suite in suites)
{
foreach (var testset in suite.Values)
{
foreach (var testCase in testset.TestCases)
{
yield return new object[] { testCase.Template, testCase.Result, testCase };
}
}
}
}
}
public static IEnumerable<object[]> FailureSamples
{
get
{
Stream stream = null;
var suites = new List<Dictionary<string, TestSet>>();
stream =
typeof(UriTemplateTests2).Assembly.GetManifestResourceStream("UriTemplateTests.negative-tests.json");
suites.Add(CreateTestSuite(new StreamReader(stream).ReadToEnd()));
foreach (var suite in suites)
{
foreach (var testset in suite.Values)
{
foreach (var testCase in testset.TestCases)
{
yield return new object[] { testCase.Template, testCase.Result, testCase };
}
}
}
}
}
private static Dictionary<string, TestSet> CreateTestSuite(string json)
{
JObject token = JObject.Parse(json);
var testSuite = new Dictionary<string, TestSet>();
foreach (JProperty levelSet in token.Children())
{
testSuite.Add(levelSet.Name, CreateTestSet(levelSet.Name, levelSet.Value));
}
return testSuite;
}
private static TestSet CreateTestSet(string name, JToken token)
{
var testSet = new TestSet();
testSet.Name = name;
var variables = token["variables"];
foreach (JProperty variable in variables)
{
ParseVariable(variable, testSet.Variables);
}
var testcases = token["testcases"];
foreach (var testcase in testcases)
{
testSet.TestCases.Add(CreateTestCase(testSet, testcase));
}
return testSet;
}
private static void ParseVariable(JProperty variable, Dictionary<string, object> dictionary)
{
if (variable.Value.Type == JTokenType.Array)
{
var array = (JArray)variable.Value;
if (array.Count == 0)
{
dictionary.Add(variable.Name, new List<string>());
}
else
{
dictionary.Add(variable.Name, array.Values<string>());
}
}
else if (variable.Value.Type == JTokenType.Object)
{
var jvalue = (JObject)variable.Value;
var dict = new Dictionary<string, string>();
foreach (var prop in jvalue.Properties())
{
dict[prop.Name] = prop.Value.ToString();
}
dictionary.Add(variable.Name, dict);
}
else
{
if (((JValue)variable.Value).Value == null)
{
dictionary.Add(variable.Name, null);
}
else
{
dictionary.Add(variable.Name, variable.Value.ToString());
}
}
}
private static TestSet.TestCase CreateTestCase(TestSet testSet, JToken testcase)
{
var testCase = new TestSet.TestCase(testSet);
testCase.Template = testcase[0].Value<string>();
if (testcase[1].Type == JTokenType.Array)
{
var results = (JArray)testcase[1];
testCase.Result = results.Select(jv => jv.Value<string>()).ToArray();
}
else
{
testCase.Result = new string[1];
testCase.Result[0] = testcase[1].Value<string>();
}
return testCase;
}
public class TestSet
{
public string Name { get; set; }
public int level { get; set; }
public Dictionary<string, object> Variables = new Dictionary<string, object>();
public List<TestCase> TestCases = new List<TestCase>();
public class TestCase
{
private readonly TestSet _testSet;
public TestCase(TestSet testSet)
{
_testSet = testSet;
}
public TestSet TestSet
{
get { return _testSet; }
}
public string Template { get; set; }
public string[] Result { get; set; }
}
}
}
}
================================================
FILE: src/UriTemplateTests/UriExtensionTests.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tavis.UriTemplates;
using Xunit;
namespace UriTemplateTests
{
public class UriExtensionTests
{
[Fact]
public void Change_an_existing_parameter_within_multiple()
{
var target = new Uri("http://example/customer?view=False&foo=bar");
var parameters = target.GetQueryStringParameters();
parameters["view"] = true;
var template = target.MakeTemplate(parameters);
Assert.Equal("http://example/customer?view=True&foo=bar", template.Resolve());
}
[Fact]
public void Change_an_existing_parameter()
{
var target = new Uri("http://example/customer?view=False&foo=bar");
var template = target.MakeTemplate();
template.SetParameter("view", true);
Assert.Equal("http://example/customer?view=True&foo=bar", template.Resolve());
}
[Fact]
public void Remove_an_existing_parameter()
{
var target = new Uri("http://example/customer?view=False&foo=bar");
var template = target.MakeTemplate();
template.ClearParameter("view");
Assert.Equal("http://example/customer?foo=bar", template.Resolve());
}
[Fact]
public void Remove_a_query_parameters2()
{
var target = new Uri("http://example.org/customer?format=xml&id=23");
var template = target.MakeTemplate();
template.ClearParameter("format");
Assert.Equal("http://example.org/customer?id=23", template.Resolve());
}
[Fact]
public void Add_multiple_parameters_to_uri()
{
var target = new Uri("http://example/customer");
var template = target.MakeTemplate(new Dictionary<string, object>
{
{"id", 99},
{"view", false}
});
Assert.Equal("http://example/customer?id=99&view=False", template.Resolve());
}
[Fact]
public void Add_parameters_to_uri_with_query_string_ignoring_path_parameter()
{
var target = new Uri("http://example/customer/{id}?view=true");
var template = target.MakeTemplate(target.GetQueryStringParameters()
.Union(new Dictionary<string, object> { { "context", "detail" } })
.ToDictionary(k => k.Key, v => v.Value));
template.AddParameter("id", 99);
Assert.Equal("http://example/customer/99?view=true&context=detail", template.Resolve());
}
}
}
================================================
FILE: src/UriTemplateTests/UriTemplateConverterTests.cs
================================================
using System.ComponentModel;
using Tavis.UriTemplates;
using Xunit;
namespace UriTemplateTests
{
public class UriTemplateConverterTests
{
[Theory]
[InlineData("http://example.org/{tenant}/customers")]
[InlineData("http://example.org/{environment}/{version}/customers{?active,country}")]
[InlineData("http://example.org/foo{?coords*}")]
public void ConvertFromString(string rawTemplate)
{
var converter = TypeDescriptor.GetConverter(typeof(UriTemplate));
var template = converter.ConvertFromString(rawTemplate);
Assert.NotNull(template);
Assert.Equal(rawTemplate, template.ToString());
}
}
}
================================================
FILE: src/UriTemplateTests/UriTemplateExtensionsTests.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tavis;
using Tavis.UriTemplates;
using Xunit;
namespace UriTemplateTests
{
public class UriTemplateExtensionsTests
{
[Fact]
public void UpdatePathParameter()
{
var url = new UriTemplate("http://example.org/{tenant}/customers")
.AddParameter("tenant", "acmé")
.Resolve();
Assert.Equal("http://example.org/acm%C3%A9/customers", url);
}
[Fact]
public void QueryParametersTheOldWay()
{
var url = new UriTemplate("http://example.org/customers?active={activeflag}")
.AddParameter("activeflag", "true")
.Resolve();
Assert.Equal("http://example.org/customers?active=true", url);
}
[Fact]
public void QueryParametersTheNewWay()
{
var url = new UriTemplate("http://example.org/customers{?active}")
.AddParameter("active", "true")
.Resolve();
Assert.Equal("http://example.org/customers?active=true", url);
}
[Fact]
public void QueryParametersTheNewWayWithoutValue()
{
var url = new UriTemplate("http://example.org/customers{?active}")
.AddParameters(null)
.Resolve();
Assert.Equal("http://example.org/customers", url);
}
[Fact]
public void ShouldResolveUriTemplateWithNonStringParameter()
{
var url = new UriTemplate("http://example.org/location{?lat,lng}")
.AddParameters(new { lat = 31.464, lng = 74.386 })
.Resolve();
Assert.Equal("http://example.org/location?lat=31.464&lng=74.386", url);
}
[Fact]
public void ParametersFromAnObject()
{
var url = new UriTemplate("http://example.org/{environment}/{version}/customers{?active,country}")
.AddParameters(new
{
environment = "dev",
version = "v2",
active = "true",
country = "CA"
})
.Resolve();
Assert.Equal("http://example.org/dev/v2/customers?active=true&country=CA", url);
}
[Fact]
public void SomeParametersFromAnObject()
{
var url = new UriTemplate("http://example.org{/environment}{/version}/customers{?active,country}")
.AddParameters(new
{
version = "v2",
active = "true"
})
.Resolve();
Assert.Equal("http://example.org/v2/customers?active=true", url);
}
[Fact]
public void ApplyDictionaryToQueryParameters()
{
var url = new UriTemplate("http://example.org/foo{?coords*}")
.AddParameter("coords", new Dictionary<string, string>
{
{"x", "1"},
{"y", "2"},
})
.Resolve();
Assert.Equal("http://example.org/foo?x=1&y=2", url);
}
[Fact]
public void ApplyParametersObjectToPathSegment()
{
var url = new UriTemplate("http://example.org/foo/{bar}/baz")
.AddParameters(new { bar = "yo" })
.Resolve();
Assert.Equal("http://example.org/foo/yo/baz", url);
}
[Fact]
public void ExtremeEncoding()
{
var url = new UriTemplate("http://example.org/sparql{?query}")
.AddParameter("query", "PREFIX dc: <http://purl.org/dc/elements/1.1/> SELECT ?book ?who WHERE { ?book dc:creator ?who }")
.Resolve();
Assert.Equal("http://example.org/sparql?query=PREFIX%20dc%3A%20%3Chttp%3A%2F%2Fpurl.org%2Fdc%2Felements%2F1.1%2F%3E%20SELECT%20%3Fbook%20%3Fwho%20WHERE%20%7B%20%3Fbook%20dc%3Acreator%20%3Fwho%20%7D", url);
}
[Fact]
public void ApplyParametersObjectWithAList()
{
var url = new UriTemplate("http://example.org/customers{?ids,order}")
.AddParameters(new
{
order = "up",
ids = new List<string> { "21", "75", "21" }
}).Resolve();
Assert.Equal("http://example.org/customers?ids=21,75,21&order=up", url);
}
[Fact]
public void ApplyParametersObjectWithAListofInts()
{
var url = new UriTemplate("http://example.org/customers{?ids,order}")
.AddParameters(new
{
order = "up",
ids = new[] { 21, 75, 21 }
})
.Resolve();
Assert.Equal("http://example.org/customers?ids=21,75,21&order=up", url);
}
[Fact]
public void ApplyParametersObjectWithAListofIntsExploded()
{
var url = new UriTemplate("http://example.org/customers{?ids*,order}")
.AddParameters(new
{
order = "up",
ids = new[] { 21, 75, 21 }
})
.Resolve();
Assert.Equal("http://example.org/customers?ids=21&ids=75&ids=21&order=up", url);
}
[Fact]
public void ApplyFoldersToPath()
{
var url = new UriTemplate("http://example.org/files{/folders*}{?filename}")
.AddParameters(new
{
folders = new[] { "customer", "project" },
filename = "proposal.pdf"
})
.Resolve();
Assert.Equal("http://example.org/files/customer/project?filename=proposal.pdf", url);
}
[Fact]
public void ParametersFromAnObjectFromInvalidUrl()
{
var url = new UriTemplate("http://{environment}.example.org/{version}/customers{?active,country}")
.AddParameters(new
{
environment = "dev",
version = "v2",
active = "true",
country = "CA"
})
.Resolve();
Assert.Equal("http://dev.example.org/v2/customers?active=true&country=CA", url);
}
[Fact]
public void ApplyFoldersToPathFromStringNotUrl()
{
var url = new UriTemplate("http://example.org{/folders*}{?filename}")
.AddParameters(new
{
folders = new[] { "files", "customer", "project" },
filename = "proposal.pdf"
})
.Resolve();
Assert.Equal("http://example.org/files/customer/project?filename=proposal.pdf", url);
}
[Fact]
public void ReplaceBaseAddress()
{
var url = new UriTemplate("{+baseUrl}api/customer/{id}")
.AddParameters(new
{
baseUrl = "http://example.org/",
id = "22"
})
.Resolve();
Assert.Equal("http://example.org/api/customer/22", url);
}
[Fact]
public void ReplaceBaseAddressButNotId()
{
var url = new UriTemplate("{+baseUrl}api/customer/{id}", resolvePartially: true)
.AddParameters(new
{
baseUrl = "http://example.org/"
})
.Resolve();
Assert.Equal("http://example.org/api/customer/{id}", url);
}
[Fact]
public void PartiallyParametersFromAnObjectFromInvalidUrl()
{
var url = new UriTemplate("http://{environment}.example.org/{version}/customers{?active,country}", resolvePartially: true)
.AddParameters(new
{
environment = "dev",
version = "v2"
})
.Resolve();
Assert.Equal("http://dev.example.org/v2/customers{?active,country}", url);
}
[Fact]
public void PartiallyApplyFoldersToPathFromStringNotUrl()
{
var url = new UriTemplate("http://example.org{/folders*}{?filename}", true)
.AddParameters(new
{
filename = "proposal.pdf"
})
.Resolve();
Assert.Equal("http://example.org{/folders*}?filename=proposal.pdf", url);
}
[Fact]
public void UseArbitraryClassAsParameter()
{
var url = new UriTemplate("/{test}", true)
.AddParameters(new
{
test = new Something()
})
.Resolve();
Assert.Equal("/something", url);
}
[Fact]
public void AddMultipleParametersToLink()
{
var template = new UriTemplate("http://localhost/api/{dataset}/customer{?foo,bar,baz}");
template.AddParameters(new Dictionary<string, object>
{
{"foo", "bar"},
{"baz", "99"},
{"dataset", "bob"}
});
var uri = template.Resolve();
Assert.Equal("http://localhost/api/bob/customer?foo=bar&baz=99", uri);
}
}
class Something
{
public override string ToString()
{
return "something";
}
}
}
================================================
FILE: src/UriTemplateTests/UriTemplateTableTests.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tavis.UriTemplates;
using Xunit;
namespace UriTemplateTests
{
public class UriTemplateTableTests
{
[Theory,
InlineData("/", "root"),
InlineData("/baz/fod/burg", ""),
InlineData("/baz/kit", "kit"),
InlineData("/baz/fod", "baz"),
InlineData("/baz/fod/blob", "blob"),
InlineData("/glah/flid/blob", "goo")]
public void FindPathTemplates(string url, string key)
{
var table = new UriTemplateTable(); // Shorter paths and literal path segments should be added to the table first.
table.Add("root", new UriTemplate("/"));
table.Add("foo", new UriTemplate("/foo/{bar}"));
table.Add("kit", new UriTemplate("/baz/kit"));
table.Add("baz", new UriTemplate("/baz/{bar}"));
table.Add("blob", new UriTemplate("/baz/{bar}/blob"));
table.Add("goo", new UriTemplate("/{goo}/{bar}/blob"));
var result = table.Match(new Uri(url, UriKind.RelativeOrAbsolute));
if (string.IsNullOrEmpty(key))
{
Assert.Null(result);
}
else
{
Assert.Equal(key, result.Key);
}
Assert.NotNull(table["goo"]);
Assert.Null(table["goo1"]);
}
[Theory,
InlineData("/games", "games"),
InlineData("/games/monopoly/Setup/23", "gamessetup"),
InlineData("/games/monopoly/Resources/foo/23", "resource"),
InlineData("/games/monopoly/22/Chat/33", "chat"),
InlineData("/games/monopoly/22/State/33", "state"),
]
public void FindTemplatesInGamesApi(string url, string key)
{
var table = new UriTemplateTable();
table.Add("games", new UriTemplate("/games"));
table.Add("gamessetup", new UriTemplate("/games/{gametitle}/Setup/{gamesid}"));
table.Add("resource", new UriTemplate("/games/{gametitle}/Resources/{resourcetype}/{resourceid}"));
table.Add("chat", new UriTemplate("/games/{gametitle}/{gameid}/Chat/{chatid}"));
table.Add("state", new UriTemplate("/games/{gametitle}/{gameid}/State/{stateid}"));
var result = table.Match(new Uri(url, UriKind.RelativeOrAbsolute));
if (string.IsNullOrEmpty(key))
{
Assert.Null(result);
}
else
{
Assert.Equal(key, result.Key);
}
}
[Theory,
InlineData("/foo?x=1&y=2", "fooxy3"),
InlineData("/foo?x=1", "fooxy2"),
InlineData("/foo?x=a,b,c,d", "fooxy2"),
InlineData("/foo?y=2", "fooxy"),
InlineData("/foo", "fooxy"),
]
public void FindTemplatesWithQueryStrings(string url, string key)
{
var table = new UriTemplateTable(); // More restrictive templates should have priority over less restrictive ones
table.Add("fooxy3", new UriTemplate("/foo?x={x}&y={y}"));
table.Add("fooxy2", new UriTemplate("/foo?x={x}{&y}"));
table.Add("fooxy4", new UriTemplate("/foo?x={x}{&z}"));
table.Add("fooxy", new UriTemplate("/foo{?x,y}"));
table.Add("foo", new UriTemplate("/foo"));
var result = table.Match(new Uri(url, UriKind.RelativeOrAbsolute));
if (string.IsNullOrEmpty(key))
{
Assert.Null(result);
}
else
{
Assert.Equal(key, result.Key);
}
}
[Fact]
public void FindTemplatesWithArrayQueryParameters()
{
var table = new UriTemplateTable(); // More restrictive templates should have priority over less restrictive ones
table.Add("fooxy3", new UriTemplate("/foo?x={x}&y={y}"));
table.Add("fooxy2", new UriTemplate("/foo?x={x}{&y}"));
table.Add("fooxy4", new UriTemplate("/foo?x={x}{&z}"));
table.Add("fooxy", new UriTemplate("/foo{?x,y}"));
table.Add("foo", new UriTemplate("/foo"));
var result = table.Match(new Uri("/foo?x=a,b,c,d", UriKind.RelativeOrAbsolute));
Assert.Equal("fooxy2", result.Key);
}
[Fact]
public void MatchTemplateWithDifferentOrderOfQueryStringParameters()
{
var table = new UriTemplateTable(); // More restrictive templates should have priority over less restrictive ones
table.Add("fooxy3", new UriTemplate("/foo?x={x}&y={y}"));
var result = table.Match(new Uri("/foo?y=a&x=b", UriKind.RelativeOrAbsolute), QueryStringParameterOrder.Any);
Assert.Equal("fooxy3", result.Key);
}
}
}
================================================
FILE: src/UriTemplateTests/UriTemplateTests.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="..\..\lib\uritemplate-test\extended-tests.json" Link="extended-tests.json" />
<EmbeddedResource Include="..\..\lib\uritemplate-test\negative-tests.json" Link="negative-tests.json" />
<EmbeddedResource Include="..\..\lib\uritemplate-test\spec-examples-by-section.json" Link="spec-examples-by-section.json" />
<EmbeddedResource Include="..\..\lib\uritemplate-test\spec-examples.json" Link="spec-examples.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\UriTemplates\UriTemplates.csproj" />
</ItemGroup>
</Project>
================================================
FILE: src/UriTemplateTests/UsageTests.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using Tavis.UriTemplates;
using Xunit;
namespace UriTemplateTests
{
public class UsageTests
{
[Fact]
public void TestHexEscape()
{
for (int i = 20; i < 128; i++)
{
Assert.Equal(Uri.HexEscape((char)i), Result.HexEscape((char)i));
}
}
[Fact]
public void ShouldAllowUriTemplateWithPathSegmentParameter()
{
var template = new UriTemplate("http://example.org/foo/{bar}/baz");
template.SetParameter("bar", "yo");
var uriString = template.Resolve();
Assert.Equal("http://example.org/foo/yo/baz", uriString);
}
[Fact]
public void ShouldAllowUriTemplateWithMultiplePathSegmentParameter()
{
var template = new UriTemplate("http://example.org/foo/{bar}/baz/{blar}");
template.SetParameter("bar", "yo");
template.SetParameter("blar", "yuck");
var uriString = template.Resolve();
Assert.Equal("http://example.org/foo/yo/baz/yuck", uriString);
}
[Fact]
public void ShouldResolveUriTemplateWithNonStringParameter()
{
var template = new UriTemplate("http://example.org/foo/{bar}/baz{?lat,lng}");
double lat = 31.464, lng = 74.386;
template.SetParameter("bar", "yo");
template.SetParameter("lat", lat);
template.SetParameter("lng", lng);
var uriString = template.Resolve();
Assert.Equal("http://example.org/foo/yo/baz?lat=31.464&lng=74.386", uriString);
}
[Fact]
public void ShouldResolveMatrixParameter()
{
var template = new UriTemplate("http://example.org/foo{;lat,lng}");
double lat = 31.464, lng = 74.386;
template.SetParameter("lat", lat);
template.SetParameter("lng", lng);
var uriString = template.Resolve();
Assert.Equal("http://example.org/foo;lat=31.464;lng=74.386", uriString);
}
[Fact]
public void ShouldAllowUriTemplateWithQueryParamsButNoValues()
{
var template = new UriTemplate("http://example.org/foo{?bar,baz}");
//template.SetParameter("bar", "yo");
//template.SetParameter("blar", "yuck");
var uriString = template.Resolve();
Assert.Equal("http://example.org/foo", uriString);
}
[Fact]
public void ShouldAllowPartialUriTemplateWithQueryParamsButNoValues()
{
var template = new UriTemplate("http://example.org/foo{?bar,baz}", resolvePartially: true);
//template.SetParameter("bar", "yo");
//template.SetParameter("blar", "yuck");
var uriString = template.Resolve();
Assert.Equal("http://example.org/foo{?bar,baz}", uriString);
}
[Fact]
public void ShouldAllowUriTemplateToRemoveParameter()
{
var template = new UriTemplate("http://example.org/foo{?bar,baz}");
template.SetParameter("bar", "yo");
template.SetParameter("baz", "yuck");
template.ClearParameter("bar");
var uriString = template.Resolve();
Assert.Equal("http://example.org/foo?baz=yuck", uriString);
}
[Fact]
public void ShouldAllowUriTemplateWithQueryParamsWithOneValue()
{
var template = new UriTemplate("http://example.org/foo{?bar,baz}");
template.SetParameter("baz", "yo");
var uriString = template.Resolve();
Assert.Equal("http://example.org/foo?baz=yo", uriString);
}
[Fact]
public void LabelExpansionWithDotPrefixAndEmptyKeys()
{
var template = new UriTemplate("X{.empty_keys}");
template.SetParameter("empty_keys", new Dictionary<string, string>());
var uriString = template.Resolve();
Assert.Equal("X", uriString);
}
[Fact]
public void QueryParametersFromDictionary()
{
var template = new UriTemplate("http://example.org/customers{?query*}");
template.SetParameter("query", new Dictionary<string, string>()
{
{"active","true"},
{"Country","Brazil"}
});
var uriString = template.Resolve();
Assert.Equal("http://example.org/customers?active=true&Country=Brazil", uriString);
}
[Fact]
public void ShouldAllowListAndSingleValueInQueryParam()
{
var template = new UriTemplate("http://example.org{/id*}{?fields,token}");
template.SetParameter("id", new List<string>() { "person", "albums" });
template.SetParameter("fields", new List<string>() { "id", "name", "picture" });
template.SetParameter("token", "12345");
var uriString = template.Resolve();
Assert.Equal("http://example.org/person/albums?fields=id,name,picture&token=12345", uriString);
}
[Fact]
public void ShouldHandleUriEncoding()
{
var template = new UriTemplate("http://example.org/sparql{?query}");
template.SetParameter("query", "PREFIX dc: <http://purl.org/dc/elements/1.1/> SELECT ?book ?who WHERE { ?book dc:creator ?who }");
var uriString = template.Resolve();
Assert.Equal("http://example.org/sparql?query=PREFIX%20dc%3A%20%3Chttp%3A%2F%2Fpurl.org%2Fdc%2Felements%2F1.1%2F%3E%20SELECT%20%3Fbook%20%3Fwho%20WHERE%20%7B%20%3Fbook%20dc%3Acreator%20%3Fwho%20%7D", uriString);
}
[Fact]
public void ShouldHandleEncodingAParametersThatIsAUriWithAUriAsAParameter()
{
var template = new UriTemplate("http://example.org/go{?uri}");
template.SetParameter("uri", "http://example.org/?uri=http%3A%2F%2Fexample.org%2F");
var uriString = template.Resolve();
Assert.Equal("http://example.org/go?uri=http%3A%2F%2Fexample.org%2F%3Furi%3Dhttp%253A%252F%252Fexample.org%252F", uriString);
}
[Fact]
public void ShouldThrowWhenExpressionIsNotClosed()
{
var result = string.Empty;
try
{
var template = new UriTemplate("http://example.org/foo/{bar/baz/");
var uriString = template.Resolve();
}
catch (ArgumentException ex)
{
result = ex.Message;
}
Assert.Equal("Malformed template, missing } : http://example.org/foo/{bar/baz/", result);
}
[Fact]
public void ShouldThrowWhenTemplateExpressionIsEmpty()
{
var result = string.Empty;
try
{
var template = new UriTemplate("http://example.org/foo/{}/baz/");
var uriString = template.Resolve();
}
catch (ArgumentException ex)
{
result = ex.Message;
}
Assert.Equal("Malformed template : http://example.org/foo/{}/baz/", result);
}
[Fact]
public void Query_param_with_exploded_array()
{
UriTemplate template = new UriTemplate("/foo/{foo}/baz{?haz*}");
template.SetParameter("foo", "1234");
template.SetParameter("haz", new string[] { "foo", "bar" });
string uri = template.Resolve();
Assert.Equal("/foo/1234/baz?haz=foo&haz=bar", uri);
}
[Fact]
public void Query_param_with_list_array()
{
UriTemplate template = new UriTemplate("/foo/{foo}/baz{?haz}");
template.SetParameter("foo", "1234");
template.SetParameter("haz", new string[] { "foo", "bar" });
string uri = template.Resolve();
Assert.Equal("/foo/1234/baz?haz=foo,bar", uri);
}
[Fact]
public void Query_param_with_empty_array()
{
UriTemplate template = new UriTemplate("/foo/{foo}/baz{?haz*}");
template.SetParameter("foo", "1234");
template.SetParameter("haz", new string[] { });
string uri = template.Resolve();
Assert.Equal("/foo/1234/baz", uri);
}
[Fact]
public void ResolveOptionalAndRequiredQueryParameters()
{
UriTemplate template = new UriTemplate("https://api.github.com/search/code?q={query}{&page,per_page,sort,order}");
template.SetParameter("query", "1234");
template.SetParameter("per_page", "19");
var result = template.Resolve();
Assert.Equal("https://api.github.com/search/code?q=1234&per_page=19", result);
}
[Fact]
public void ReservedCharacterExpansion()
{
UriTemplate template = new UriTemplate("https://foo.com/{?format}");
template.SetParameter("format", "application/vnd.foo+xml");
var result = template.Resolve();
Assert.Equal("https://foo.com/?format=application%2Fvnd.foo%2Bxml", result);
}
[Fact(Skip = "Unit tests should not require internet access!!")]
public void PreserveReservedCharacterExpansion()
{
UriTemplate template = new UriTemplate("https://foo.com/?format={+format}");
template.SetParameter("format", "application/vnd.foo+xml");
var result = template.Resolve();
Assert.Equal("https://foo.com/?format=application/vnd.foo+xml", result);
}
[Fact]
public void ShouldSupportUnicodeCharacters()
{
UriTemplate template = new UriTemplate("/lookup{?Stra%C3%9Fe}");
template.SetParameter("Stra%C3%9Fe", "Grüner Weg");
var result = template.Resolve();
Assert.Equal("/lookup?Stra%C3%9Fe=Gr%C3%BCner%20Weg", result);
}
// "assoc_special_chars" : { "šö䟜ñꀣ¥‡ÑÒÓÔÕ" : "ÖרÙÚàáâãäåæçÿ" }
[Fact]
public void ShouldSupportUnicodeCharacters2()
{
UriTemplate template = new UriTemplate("{?assoc_special_chars*}");
var dict = new Dictionary<string, string>
{
{"šö䟜ñꀣ¥‡ÑÒÓÔÕ", "ÖרÙÚàáâãäåæçÿ"}
};
template.SetParameter("assoc_special_chars", dict);
var result = template.Resolve();
Assert.Equal("?%C5%A1%C3%B6%C3%A4%C5%B8%C5%93%C3%B1%C3%AA%E2%82%AC%C2%A3%C2%A5%E2%80%A1%C3%91%C3%92%C3%93%C3%94%C3%95=%C3%96%C3%97%C3%98%C3%99%C3%9A%C3%A0%C3%A1%C3%A2%C3%A3%C3%A4%C3%A5%C3%A6%C3%A7%C3%BF", result);
}
[Fact]
public void Remove_a_query_parameters2()
{
var target = new Uri("http://example.org/customer?format=xml&id=23");
var template = target.MakeTemplate();
template.ClearParameter("format");
Assert.Equal("http://example.org/customer?id=23", template.Resolve());
}
[Fact]
public void EncodingTest1()
{
var url = new UriTemplate("/1/search/auto/{folder}{?query}")
.AddParameter("folder", "My Documents")
.AddParameter("query", "draft 2013")
.Resolve();
Assert.Equal("/1/search/auto/My%20Documents?query=draft%202013", url);
}
[Fact]
public void EncodingTest2()
{
// Parameter values get encoded but hyphen doesn't need to be encoded because it
// is an "unreserved" character according to RFC 3986
var url = new UriTemplate("{/greeting}")
.AddParameter("greeting", "hello-world")
.Resolve();
Assert.Equal("/hello-world", url);
// A slash does need to be encoded
var url2 = new UriTemplate("{/greeting}")
.AddParameter("greeting", "hello/world")
.Resolve();
Assert.Equal("/hello%2Fworld", url2);
// If you truly want to make multiple path segments then do this
var url3 = new UriTemplate("{/greeting*}")
.AddParameter("greeting", new List<string> { "hello", "world" })
.Resolve();
Assert.Equal("/hello/world", url3);
}
// /docs/salary.csv?columns=1,2
// /docs/salary.csv?column=1&column=2
// /emails?from[name]=Don&from[date]=1998-03-24&to[name]=Norm
// : /log?a=b&c=4
[Fact]
public void EncodingTest3()
{
// There are different ways that lists can be included in query params
// Just as a comma delimited list
var url = new UriTemplate("/docs/salary.csv{?columns}")
.AddParameter("columns", new List<int> { 1, 2 })
.Resolve();
Assert.Equal("/docs/salary.csv?columns=1,2", url);
// or as a multiple parameter instances
var url2 = new UriTemplate("/docs/salary.csv{?columns*}")
.AddParameter("columns", new List<int> { 1, 2 })
.Resolve();
Assert.Equal("/docs/salary.csv?columns=1&columns=2", url2);
}
[Fact]
public void EncodingTest4()
{
var url = new UriTemplate("/emails{?params*}")
.AddParameter("params", new Dictionary<string, string>
{
{"from[name]","Don"},
{"from[date]","1998-03-24"},
{"to[name]","Norm"}
})
.Resolve();
Assert.Equal("/emails?from[name]=Don&from[date]=1998-03-24&to[name]=Norm", url);
}
[Fact]
public void EncodingTest5()
{
///log?a=b&c=4
var url = new UriTemplate("/log?a={a}&c={c}")
.AddParameter("a", "b")
.AddParameter("c", "4")
.Resolve();
Assert.Equal("/log?a=b&c=4", url);
}
[Fact]
public void InvalidSpace()
{
Exception ex = null;
try
{
var url = new UriTemplate("/feeds/events{? fromId").Resolve();
}
catch (Exception e)
{
ex = e;
}
Assert.NotNull(ex);
}
}
}
================================================
FILE: src/UriTemplates/OperatorInfo.cs
================================================
namespace Tavis.UriTemplates
{
public class OperatorInfo
{
public bool Default { get; set; }
public string First { get; set; }
public char Separator { get; set; }
public bool Named { get; set; }
public string IfEmpty { get; set; }
public bool AllowReserved { get; set; }
}
}
================================================
FILE: src/UriTemplates/QueryStringParameterOrder.cs
================================================
namespace Tavis.UriTemplates
{
public enum QueryStringParameterOrder
{
Strict,
Any
}
}
================================================
FILE: src/UriTemplates/Result.cs
================================================
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Tavis.UriTemplates
{
public class Result
{
public bool ErrorDetected { get; set; }
public List<string> ParameterNames { get; set; }
private const string _UriReservedSymbols = ":/?#[]@!$&'()*+,;=";
private const string _UriUnreservedSymbols = "-._~";
private StringBuilder _Result = new StringBuilder();
public Result()
{
ParameterNames = new List<string>();
}
public StringBuilder Append(char value)
{
return _Result.Append(value);
}
public StringBuilder Append(string value)
{
return _Result.Append(value);
}
public override string ToString()
{
return _Result.ToString();
}
public void AppendName(string variable, OperatorInfo op, bool valueIsEmpty)
{
_Result.Append(variable);
if (valueIsEmpty) { _Result.Append(op.IfEmpty); } else { _Result.Append("="); }
}
public void AppendList(OperatorInfo op, bool explode, string variable, IList list)
{
foreach (object item in list)
{
if (op.Named && explode)
{
_Result.Append(variable);
_Result.Append("=");
}
AppendValue(item.ToString(), 0, op.AllowReserved);
_Result.Append(explode ? op.Separator : ',');
}
if (list.Count > 0)
{
_Result.Remove(_Result.Length - 1, 1);
}
}
public void AppendDictionary(OperatorInfo op, bool explode, IDictionary<string, string> dictionary)
{
foreach (string key in dictionary.Keys)
{
_Result.Append(Encode(key, op.AllowReserved));
if (explode) _Result.Append('='); else _Result.Append(',');
AppendValue(dictionary[key], 0, op.AllowReserved);
if (explode)
{
_Result.Append(op.Separator);
}
else
{
_Result.Append(',');
}
}
if (dictionary.Count() > 0)
{
_Result.Remove(_Result.Length - 1, 1);
}
}
public void AppendValue(string value, int prefixLength, bool allowReserved)
{
if (prefixLength != 0)
{
if (prefixLength < value.Length)
{
value = value.Substring(0, prefixLength);
}
}
_Result.Append(Encode(value, allowReserved));
}
private static string Encode(string p, bool allowReserved)
{
var result = new StringBuilder();
foreach (char c in p)
{
if ((c >= 'A' && c <= 'z') //Alpha
|| (c >= '0' && c <= '9') // Digit
|| _UriUnreservedSymbols.IndexOf(c) != -1 // Unreserved symbols - These should never be percent encoded
|| (allowReserved && _UriReservedSymbols.IndexOf(c) != -1)) // Reserved symbols - should be included if requested (+)
{
result.Append(c);
}
else
{
var bytes = Encoding.UTF8.GetBytes(new[] { c });
foreach (var abyte in bytes)
{
result.Append(HexEscape(abyte));
}
}
}
return result.ToString();
}
public static string HexEscape(byte i)
{
var esc = new char[3];
esc[0] = '%';
esc[1] = HexDigits[((i & 240) >> 4)];
esc[2] = HexDigits[(i & 15)];
return new string(esc);
}
public static string HexEscape(char c)
{
var esc = new char[3];
esc[0] = '%';
esc[1] = HexDigits[(((int)c & 240) >> 4)];
esc[2] = HexDigits[((int)c & 15)];
return new string(esc);
}
private static readonly char[] HexDigits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
}
}
================================================
FILE: src/UriTemplates/UriTemplate.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace Tavis.UriTemplates
{
using System;
using System.Collections;
using System.Collections.Generic;
#if TYPE_CONVERTER
using System.ComponentModel;
#endif
using System.Linq;
using System.Text;
#if TYPE_CONVERTER
[TypeConverter(typeof(UriTemplateConverter))]
#endif
public class UriTemplate
{
private static Dictionary<char, OperatorInfo> _Operators = new Dictionary<char, OperatorInfo>() {
{'\0', new OperatorInfo {Default = true, First = "", Separator = ',', Named = false, IfEmpty = "",AllowReserved = false}},
{'+', new OperatorInfo {Default = false, First = "", Separator = ',', Named = false, IfEmpty = "",AllowReserved = true}},
{'.', new OperatorInfo {Default = false, First = ".", Separator = '.', Named = false, IfEmpty = "",AllowReserved = false}},
{'/', new OperatorInfo {Default = false, First = "/", Separator = '/', Named = false, IfEmpty = "",AllowReserved = false}},
{';', new OperatorInfo {Default = false, First = ";", Separator = ';', Named = true, IfEmpty = "",AllowReserved = false}},
{'?', new OperatorInfo {Default = false, First = "?", Separator = '&', Named = true, IfEmpty = "=",AllowReserved = false}},
{'&', new OperatorInfo {Default = false, First = "&", Separator = '&', Named = true, IfEmpty = "=",AllowReserved = false}},
{'#', new OperatorInfo {Default = false, First = "#", Separator = ',', Named = false, IfEmpty = "",AllowReserved = true}}
};
private readonly string _template;
private readonly Dictionary<string, object> _Parameters;
private enum States { CopyingLiterals, ParsingExpression }
private readonly bool _resolvePartially;
public UriTemplate(string template, bool resolvePartially = false, bool caseInsensitiveParameterNames = false)
{
_resolvePartially = resolvePartially;
_template = template;
_Parameters = caseInsensitiveParameterNames
? new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
: new Dictionary<string, object>();
}
public override string ToString()
{
return _template;
}
public void SetParameter(string name, object value)
{
_Parameters[name] = value;
}
public void ClearParameter(string name)
{
_Parameters.Remove(name);
}
public void SetParameter(string name, string value)
{
_Parameters[name] = value;
}
public void SetParameter(string name, IEnumerable<string> value)
{
_Parameters[name] = value;
}
public void SetParameter(string name, IDictionary<string, string> value)
{
_Parameters[name] = value;
}
public IEnumerable<string> GetParameterNames()
{
Result result = ResolveResult();
return result.ParameterNames;
}
public string Resolve()
{
var result = ResolveResult();
return result.ToString();
}
private Result ResolveResult()
{
var currentState = States.CopyingLiterals;
var result = new Result();
StringBuilder currentExpression = null;
foreach (var character in _template.ToCharArray())
{
switch (currentState)
{
case States.CopyingLiterals:
if (character == '{')
{
currentState = States.ParsingExpression;
currentExpression = new StringBuilder();
}
else if (character == '}')
{
throw new ArgumentException("Malformed template, unexpected } : " + result.ToString());
}
else
{
result.Append(character);
}
break;
case States.ParsingExpression:
if (character == '}')
{
ProcessExpression(currentExpression, result);
currentState = States.CopyingLiterals;
}
else
{
currentExpression.Append(character);
}
break;
}
}
if (currentState == States.ParsingExpression)
{
result.Append("{");
result.Append(currentExpression.ToString());
throw new ArgumentException("Malformed template, missing } : " + result.ToString());
}
if (result.ErrorDetected)
{
throw new ArgumentException("Malformed template : " + result.ToString());
}
return result;
}
private void ProcessExpression(StringBuilder currentExpression, Result result)
{
if (currentExpression.Length == 0)
{
result.ErrorDetected = true;
result.Append("{}");
return;
}
OperatorInfo op = GetOperator(currentExpression[0]);
var firstChar = op.Default ? 0 : 1;
bool multivariableExpression = false;
var varSpec = new VarSpec(op);
for (int i = firstChar; i < currentExpression.Length; i++)
{
char currentChar = currentExpression[i];
switch (currentChar)
{
case '*':
varSpec.Explode = true;
break;
case ':': // Parse Prefix Modifier
var prefixText = new StringBuilder();
currentChar = currentExpression[++i];
while (currentChar >= '0' && currentChar <= '9' && i < currentExpression.Length)
{
prefixText.Append(currentChar);
i++;
if (i < currentExpression.Length) currentChar = currentExpression[i];
}
varSpec.PrefixLength = int.Parse(prefixText.ToString());
i--;
break;
case ',':
multivariableExpression = true;
var success = ProcessVariable(varSpec, result, multivariableExpression);
bool isFirst = varSpec.First;
// Reset for new variable
varSpec = new VarSpec(op);
if (success || !isFirst || _resolvePartially) varSpec.First = false;
if (!success && _resolvePartially) { result.Append(","); }
break;
default:
if (IsVarNameChar(currentChar))
{
varSpec.VarName.Append(currentChar);
}
else
{
result.ErrorDetected = true;
}
break;
}
}
ProcessVariable(varSpec, result, multivariableExpression);
if (multivariableExpression && _resolvePartially) result.Append("}");
}
private bool ProcessVariable(VarSpec varSpec, Result result, bool multiVariableExpression = false)
{
var varname = varSpec.VarName.ToString();
result.ParameterNames.Add(varname);
if (!_Parameters.ContainsKey(varname)
|| _Parameters[varname] == null
|| (_Parameters[varname] is IList && ((IList)_Parameters[varname]).Count == 0)
|| (_Parameters[varname] is IDictionary && ((IDictionary)_Parameters[varname]).Count == 0))
{
if (_resolvePartially == true)
{
if (multiVariableExpression)
{
if (varSpec.First)
{
result.Append("{");
}
result.Append(varSpec.ToString());
}
else
{
result.Append("{");
result.Append(varSpec.ToString());
result.Append("}");
}
return false;
}
return false;
}
if (varSpec.First)
{
result.Append(varSpec.OperatorInfo.First);
}
else
{
result.Append(varSpec.OperatorInfo.Separator);
}
object value = _Parameters[varname];
// Handle Strings
if (value is string)
{
var stringValue = (string)value;
if (varSpec.OperatorInfo.Named)
{
result.AppendName(varname, varSpec.OperatorInfo, string.IsNullOrEmpty(stringValue));
}
result.AppendValue(stringValue, varSpec.PrefixLength, varSpec.OperatorInfo.AllowReserved);
}
else
{
// Handle Lists
var list = value as IList;
if (list == null && value is IEnumerable<string>)
{
list = ((IEnumerable<string>)value).ToList<string>();
};
if (list != null)
{
if (varSpec.OperatorInfo.Named && !varSpec.Explode) // exploding will prefix with list name
{
result.AppendName(varname, varSpec.OperatorInfo, list.Count == 0);
}
result.AppendList(varSpec.OperatorInfo, varSpec.Explode, varname, list);
}
else
{
// Handle associative arrays
var dictionary = value as IDictionary<string, string>;
if (dictionary != null)
{
if (varSpec.OperatorInfo.Named && !varSpec.Explode) // exploding will prefix with list name
{
result.AppendName(varname, varSpec.OperatorInfo, dictionary.Count() == 0);
}
result.AppendDictionary(varSpec.OperatorInfo, varSpec.Explode, dictionary);
}
else
{
// If above all fails, convert the object to string using the default object.ToString() implementation
var stringValue = value.ToString();
if (varSpec.OperatorInfo.Named)
{
result.AppendName(varname, varSpec.OperatorInfo, string.IsNullOrEmpty(stringValue));
}
result.AppendValue(stringValue, varSpec.PrefixLength, varSpec.OperatorInfo.AllowReserved);
}
}
}
return true;
}
private static bool IsVarNameChar(char c)
{
return ((c >= 'A' && c <= 'z') //Alpha
|| (c >= '0' && c <= '9') // Digit
|| c == '_'
|| c == '%'
|| c == '.');
}
private static OperatorInfo GetOperator(char operatorIndicator)
{
OperatorInfo op;
switch (operatorIndicator)
{
case '+':
case ';':
case '/':
case '#':
case '&':
case '?':
case '.':
op = _Operators[operatorIndicator];
break;
default:
op = _Operators['\0'];
break;
}
return op;
}
private const string varname = "[a-zA-Z0-9_]*";
private const string op = "(?<op>[+#./;?&]?)";
private const string var = "(?<var>(?:(?<lvar>" + varname + ")[*]?,?)*)";
private const string varspec = "(?<varspec>{" + op + var + "})";
// (?<varspec>{(?<op>[+#./;?&]?)(?<var>[a-zA-Z0-9_]*[*]?|(?:(?<lvar>[a-zA-Z0-9_]*[*]?),?)*)})
private Regex _ParameterRegex = null;
private Uri _ComponentBaseUri = new Uri("https://localhost.com", UriKind.Absolute);
public IDictionary<string, object> GetParameters(Uri uri, QueryStringParameterOrder order = QueryStringParameterOrder.Strict)
{
switch (order)
{
case QueryStringParameterOrder.Strict:
{
if (_ParameterRegex == null)
{
var matchingRegex = CreateMatchingRegex(_template);
lock (this)
{
_ParameterRegex = new Regex(matchingRegex);
}
}
var match = _ParameterRegex.Match(uri.OriginalString);
var parameters = new Dictionary<string, object>();
for (int x = 1; x < match.Groups.Count; x++)
{
if (match.Groups[x].Success)
{
var paramName = _ParameterRegex.GroupNameFromNumber(x);
if (!string.IsNullOrEmpty(paramName))
{
parameters.Add(paramName, Uri.UnescapeDataString(match.Groups[x].Value));
}
}
}
return match.Success ? parameters : null;
}
case QueryStringParameterOrder.Any:
{
if (!uri.IsAbsoluteUri)
uri = new Uri(_ComponentBaseUri, uri);
var uriString = uri.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path | UriComponents.Fragment, UriFormat.UriEscaped);
var uriWithoutQuery = new Uri(uriString, UriKind.Absolute);
var pathParameters = GetParameters(uriWithoutQuery) ?? new Dictionary<string, object>(_Parameters.Comparer);
Result result = ResolveResult();
var parameterNames = result.ParameterNames;
foreach (var parameter in uri.GetQueryStringParameters())
{
if (!parameterNames.Contains(parameter.Key))
continue;
pathParameters.Add(parameter.Key, parameter.Value);
}
return pathParameters.Count == 0 ? null : pathParameters;
}
default:
throw new ArgumentOutOfRangeException(nameof(order), order, null);
}
}
public static string CreateMatchingRegex(string uriTemplate)
{
var findParam = new Regex(varspec);
var template = new Regex(@"([^{]|^)\?").Replace(uriTemplate, @"$+\?"); ;//.Replace("?",@"\?");
var regex = findParam.Replace(template, delegate (Match m)
{
var paramNames = m.Groups["lvar"].Captures.Cast<Capture>().Where(c => !string.IsNullOrEmpty(c.Value)).Select(c => c.Value).ToList();
var op = m.Groups["op"].Value;
switch (op)
{
case "?":
return GetQueryExpression(paramNames, prefix: "?");
case "&":
return GetQueryExpression(paramNames, prefix: "&");
case "#":
return GetExpression(paramNames, prefix: "#");
case "/":
return GetExpression(paramNames, prefix: "/");
case "+":
return GetExpression(paramNames);
default:
return GetExpression(paramNames);
}
});
return regex + "$";
}
public static string CreateMatchingRegex2(string uriTemplate)
{
var findParam = new Regex(varspec);
//split by host/path/query/fragment
var template = new Regex(@"([^{]|^)\?").Replace(uriTemplate, @"$+\?"); ;//.Replace("?",@"\?");
var regex = findParam.Replace(template, delegate (Match m)
{
var paramNames = m.Groups["lvar"].Captures.Cast<Capture>().Where(c => !string.IsNullOrEmpty(c.Value)).Select(c => c.Value).ToList();
var op = m.Groups["op"].Value;
switch (op)
{
case "?":
return GetQueryExpression(paramNames, prefix: "?");
case "&":
return GetQueryExpression(paramNames, prefix: "&");
case "#":
return GetExpression(paramNames, prefix: "#");
case "/":
return GetExpression(paramNames, prefix: "/");
case "+":
return GetExpression(paramNames);
default:
return GetExpression(paramNames);
}
});
return regex + "$";
}
private static string GetQueryExpression(List<String> paramNames, string prefix)
{
StringBuilder sb = new StringBuilder();
foreach (var paramname in paramNames)
{
sb.Append(@"\" + prefix + "?");
if (prefix == "?") prefix = "&";
sb.Append("(?:");
sb.Append(paramname);
sb.Append("=");
sb.Append("(?<");
sb.Append(paramname);
sb.Append(">");
sb.Append("[^/?&]+");
sb.Append(")");
sb.Append(")?");
}
return sb.ToString();
}
private static string GetExpression(List<String> paramNames, string prefix = null)
{
StringBuilder sb = new StringBuilder();
string paramDelim;
switch (prefix)
{
case "#":
paramDelim = "[^,]+";
break;
case "/":
paramDelim = "[^/?]+";
break;
case "?":
case "&":
paramDelim = "[^&#]+";
break;
case ";":
paramDelim = "[^;/?#]+";
break;
case ".":
paramDelim = "[^./?#]+";
break;
default:
paramDelim = "[^/?&]+";
break;
}
foreach (var paramname in paramNames)
{
if (string.IsNullOrEmpty(paramname)) continue;
if (prefix != null)
{
sb.Append(@"\" + prefix + "?");
if (prefix == "#") { prefix = ","; }
}
sb.Append("(?<");
sb.Append(paramname);
sb.Append(">");
sb.Append(paramDelim); // Param Value
sb.Append(")?");
}
return sb.ToString();
}
}
}
================================================
FILE: src/UriTemplates/UriTemplateConverter.cs
================================================
using System;
using System.ComponentModel;
using System.Globalization;
namespace Tavis.UriTemplates
{
#if TYPE_CONVERTER
/// <summary>
/// Converts to <see cref="UriTemplate"/> instances from other representations.
/// </summary>
public sealed class UriTemplateConverter
: TypeConverter
{
/// <inheritdoc/>
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
/// <inheritdoc/>
public override object ConvertFrom(
ITypeDescriptorContext context,
CultureInfo culture,
object value)
{
if (value == null) { return null; }
var template = value as string;
if (template != null)
{
if (template.Length == 0)
{
// For TypeConverter purposes, an empty string is "no value."
return null;
}
return new UriTemplate(template);
}
throw (NotSupportedException)GetConvertFromException(value);
}
}
#endif
}
================================================
FILE: src/UriTemplates/UriTemplateExtensions.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
namespace Tavis.UriTemplates
{
public static class UriTemplateExtensions
{
public static UriTemplate AddParameter(this UriTemplate template, string name, object value)
{
template.SetParameter(name, value);
return template;
}
public static UriTemplate AddParameters(this UriTemplate template, object parametersObject)
{
if (parametersObject != null)
{
IEnumerable<PropertyInfo> properties;
#if NETSTANDARD1_0
var type = parametersObject.GetType().GetTypeInfo();
properties = type.DeclaredProperties.Where(p=> p.CanRead);
#else
properties = parametersObject.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
#endif
foreach (var propinfo in properties)
{
template.SetParameter(propinfo.Name, propinfo.GetValue(parametersObject, null));
}
}
return template;
}
public static UriTemplate AddParameters(this UriTemplate uriTemplate, IDictionary<string, object> linkParameters)
{
if (linkParameters != null)
{
foreach (var parameter in linkParameters)
{
uriTemplate.SetParameter(parameter.Key, parameter.Value);
}
}
return uriTemplate;
}
}
public static class UriExtensions
{
public static UriTemplate MakeTemplate(this Uri uri)
{
var parameters = uri.GetQueryStringParameters();
return MakeTemplate(uri, parameters);
}
public static UriTemplate MakeTemplate(this Uri uri, IDictionary<string, object> parameters)
{
var target = uri.GetComponents(UriComponents.AbsoluteUri
& ~UriComponents.Query
& ~UriComponents.Fragment, UriFormat.Unescaped);
var template = new UriTemplate(target + "{?" + string.Join(",", parameters.Keys.ToArray()) + "}");
template.AddParameters(parameters);
return template;
}
public static Dictionary<string, object> GetQueryStringParameters(this Uri target)
{
Uri uri = target;
var parameters = new Dictionary<string, object>();
var reg = new Regex(@"([-A-Za-z0-9._~]*)=([^&]*)&?"); // Unreserved characters: http://tools.ietf.org/html/rfc3986#section-2.3
foreach (Match m in reg.Matches(uri.Query))
{
string key = m.Groups[1].Value.ToLowerInvariant();
string value = m.Groups[2].Value;
parameters.Add(key, value);
}
return parameters;
}
}
}
================================================
FILE: src/UriTemplates/UriTemplateTable.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Tavis.UriTemplates
{
public class UriTemplateTable
{
private Dictionary<string, UriTemplate> _Templates = new Dictionary<string, UriTemplate>();
public void Add(string key, UriTemplate template)
{
_Templates.Add(key, template);
}
public TemplateMatch Match(Uri url, QueryStringParameterOrder order = QueryStringParameterOrder.Strict)
{
foreach (var template in _Templates)
{
var parameters = template.Value.GetParameters(url, order);
if (parameters != null)
{
return new TemplateMatch() { Key = template.Key, Parameters = parameters, Template = template.Value };
}
}
return null;
}
public UriTemplate this[string key]
{
get
{
UriTemplate value;
if (_Templates.TryGetValue(key, out value))
{
return value;
}
else
{
return null;
}
}
}
}
public class TemplateMatch
{
public string Key { get; set; }
public UriTemplate Template { get; set; }
public IDictionary<string, object> Parameters { get; set; }
}
}
================================================
FILE: src/UriTemplates/UriTemplates.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;</TargetFrameworks>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<RootNamespace>Tavis</RootNamespace>
<AssemblyName>Tavis.UriTemplates</AssemblyName>
<Description>URI Template resolution library - Implementation of RFC 6570</Description>
<Authors>Darrel Miller</Authors>
<Version>2.0.0</Version>
<PackageReleaseNotes><![CDATA[
For full release notes see https://github.com/tavis-software/Tavis.UriTemplates/blob/main/CHANGELOG.md
]]></PackageReleaseNotes>
<PackageProjectUrl>https://github.com/tavis-software/Tavis.UriTemplates</PackageProjectUrl>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryType>git</RepositoryType>
<SignAssembly>true</SignAssembly>
<RepositoryUrl>https://github.com/tavis-software/Tavis.UriTemplates</RepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AssemblyOriginatorKeyFile>../../UriTemplateKey.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="all" />
</ItemGroup>
<PropertyGroup Condition="'$(TargetFramework)'!='netstandard1.0'">
<DefineConstants>DEBUG;TRACE;TYPE_CONVERTER</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<ItemGroup>
<None Include="../../LICENSE" Pack="true" PackagePath="" />
<None Include="../../README.md" Pack="true" PackagePath="" />
</ItemGroup>
</Project>
================================================
FILE: src/UriTemplates/VarSpec.cs
================================================
using System.Text;
namespace Tavis.UriTemplates
{
public class VarSpec
{
private readonly OperatorInfo _operatorInfo;
public StringBuilder VarName = new StringBuilder();
public bool Explode = false;
public int PrefixLength = 0;
public bool First = true;
public VarSpec(OperatorInfo operatorInfo)
{
_operatorInfo = operatorInfo;
}
public OperatorInfo OperatorInfo
{
get { return _operatorInfo; }
}
public override string ToString()
{
return (First ? _operatorInfo.First : "") +
VarName.ToString()
+ (Explode ? "*" : "")
+ (PrefixLength > 0 ? ":" + PrefixLength : "");
}
}
}
gitextract_a58_z357/
├── .gitattributes
├── .github/
│ ├── CODEOWNERS
│ ├── dependabot.yml
│ └── workflows/
│ ├── auto-merge-dependabot.yml
│ ├── buildAndDeploy.yml
│ └── codeql-analysis.yml
├── .gitignore
├── .gitmodules
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── UriTemplateKey.snk
├── UriTemplates.sln
├── build.cmd
└── src/
├── UriTemplateTests/
│ ├── App.config
│ ├── ParameterMatchingTests.cs
│ ├── SpecTests.cs
│ ├── UriExtensionTests.cs
│ ├── UriTemplateConverterTests.cs
│ ├── UriTemplateExtensionsTests.cs
│ ├── UriTemplateTableTests.cs
│ ├── UriTemplateTests.csproj
│ └── UsageTests.cs
└── UriTemplates/
├── OperatorInfo.cs
├── QueryStringParameterOrder.cs
├── Result.cs
├── UriTemplate.cs
├── UriTemplateConverter.cs
├── UriTemplateExtensions.cs
├── UriTemplateTable.cs
├── UriTemplates.csproj
└── VarSpec.cs
SYMBOL INDEX (155 symbols across 15 files)
FILE: src/UriTemplateTests/ParameterMatchingTests.cs
class ParameterMatchingTests (line 12) | public class ParameterMatchingTests
method MatchUriToTemplate (line 16) | [Fact]
method GetParameters (line 29) | [Fact]
method GetParametersWithOperators (line 45) | [Fact]
method GetParametersFromQueryString (line 59) | [Fact]
method GetParametersFromMultipleQueryString (line 75) | [Fact]
method GetParametersFromMultipleQueryStringWithTwoParamValues (line 90) | [Fact]
method GetParameterFromArrayParameter (line 107) | [Fact]
method GetParametersFromMultipleQueryStringWithOptionalAndMandatoryParameters (line 121) | [Fact]
method GetParametersFromMultipleQueryStringWithOptionalParameters (line 138) | [Fact]
method TestGlimpseUrl (line 153) | [Fact]
method TestUrlWithQuestionMarkAsFirstCharacter (line 169) | [Fact]
method TestExactParameterCount (line 182) | [Fact]
method SimplePerfTest (line 195) | [Fact]
method Level1Decode (line 212) | [Fact]
method FragmentParam (line 239) | [Fact]
method FragmentParams (line 253) | [Fact]
method OptionalPathParam (line 267) | [Fact]
method OptionalPathParamWithMultipleValues (line 280) | [Fact]
FILE: src/UriTemplateTests/SpecTests.cs
class UriTemplateTests2 (line 12) | public class UriTemplateTests2
method SpecSamplesTest (line 15) | [Theory, MemberData(nameof(SpecSamples))]
method ExtendedSamplesTest (line 32) | [Theory, MemberData(nameof(ExtendedSamples))]
method FailureSamplesTest (line 67) | [Theory(Skip = "Disabled for the moment."), MemberData(nameof(FailureS...
method CreateTestSuite (line 198) | private static Dictionary<string, TestSet> CreateTestSuite(string json)
method CreateTestSet (line 211) | private static TestSet CreateTestSet(string name, JToken token)
method ParseVariable (line 233) | private static void ParseVariable(JProperty variable, Dictionary<strin...
method CreateTestCase (line 271) | private static TestSet.TestCase CreateTestCase(TestSet testSet, JToken...
class TestSet (line 290) | public class TestSet
class TestCase (line 297) | public class TestCase
method TestCase (line 301) | public TestCase(TestSet testSet)
FILE: src/UriTemplateTests/UriExtensionTests.cs
class UriExtensionTests (line 10) | public class UriExtensionTests
method Change_an_existing_parameter_within_multiple (line 12) | [Fact]
method Change_an_existing_parameter (line 25) | [Fact]
method Remove_an_existing_parameter (line 36) | [Fact]
method Remove_a_query_parameters2 (line 47) | [Fact]
method Add_multiple_parameters_to_uri (line 60) | [Fact]
method Add_parameters_to_uri_with_query_string_ignoring_path_parameter (line 74) | [Fact]
FILE: src/UriTemplateTests/UriTemplateConverterTests.cs
class UriTemplateConverterTests (line 7) | public class UriTemplateConverterTests
method ConvertFromString (line 9) | [Theory]
FILE: src/UriTemplateTests/UriTemplateExtensionsTests.cs
class UriTemplateExtensionsTests (line 11) | public class UriTemplateExtensionsTests
method UpdatePathParameter (line 13) | [Fact]
method QueryParametersTheOldWay (line 24) | [Fact]
method QueryParametersTheNewWay (line 34) | [Fact]
method QueryParametersTheNewWayWithoutValue (line 44) | [Fact]
method ShouldResolveUriTemplateWithNonStringParameter (line 55) | [Fact]
method ParametersFromAnObject (line 66) | [Fact]
method SomeParametersFromAnObject (line 82) | [Fact]
method ApplyDictionaryToQueryParameters (line 96) | [Fact]
method ApplyParametersObjectToPathSegment (line 110) | [Fact]
method ExtremeEncoding (line 120) | [Fact]
method ApplyParametersObjectWithAList (line 129) | [Fact]
method ApplyParametersObjectWithAListofInts (line 142) | [Fact]
method ApplyParametersObjectWithAListofIntsExploded (line 156) | [Fact]
method ApplyFoldersToPath (line 170) | [Fact]
method ParametersFromAnObjectFromInvalidUrl (line 184) | [Fact]
method ApplyFoldersToPathFromStringNotUrl (line 202) | [Fact]
method ReplaceBaseAddress (line 218) | [Fact]
method ReplaceBaseAddressButNotId (line 233) | [Fact]
method PartiallyParametersFromAnObjectFromInvalidUrl (line 247) | [Fact]
method PartiallyApplyFoldersToPathFromStringNotUrl (line 263) | [Fact]
method UseArbitraryClassAsParameter (line 277) | [Fact]
method AddMultipleParametersToLink (line 293) | [Fact]
class Something (line 312) | class Something
method ToString (line 314) | public override string ToString()
FILE: src/UriTemplateTests/UriTemplateTableTests.cs
class UriTemplateTableTests (line 11) | public class UriTemplateTableTests
method FindPathTemplates (line 18) | [Theory,
method FindTemplatesInGamesApi (line 51) | [Theory,
method FindTemplatesWithQueryStrings (line 81) | [Theory,
method FindTemplatesWithArrayQueryParameters (line 111) | [Fact]
method MatchTemplateWithDifferentOrderOfQueryStringParameters (line 129) | [Fact]
FILE: src/UriTemplateTests/UsageTests.cs
class UsageTests (line 11) | public class UsageTests
method TestHexEscape (line 13) | [Fact]
method ShouldAllowUriTemplateWithPathSegmentParameter (line 24) | [Fact]
method ShouldAllowUriTemplateWithMultiplePathSegmentParameter (line 34) | [Fact]
method ShouldResolveUriTemplateWithNonStringParameter (line 44) | [Fact]
method ShouldResolveMatrixParameter (line 60) | [Fact]
method ShouldAllowUriTemplateWithQueryParamsButNoValues (line 75) | [Fact]
method ShouldAllowPartialUriTemplateWithQueryParamsButNoValues (line 85) | [Fact]
method ShouldAllowUriTemplateToRemoveParameter (line 95) | [Fact]
method ShouldAllowUriTemplateWithQueryParamsWithOneValue (line 108) | [Fact]
method LabelExpansionWithDotPrefixAndEmptyKeys (line 119) | [Fact]
method QueryParametersFromDictionary (line 128) | [Fact]
method ShouldAllowListAndSingleValueInQueryParam (line 141) | [Fact]
method ShouldHandleUriEncoding (line 153) | [Fact]
method ShouldHandleEncodingAParametersThatIsAUriWithAUriAsAParameter (line 162) | [Fact]
method ShouldThrowWhenExpressionIsNotClosed (line 172) | [Fact]
method ShouldThrowWhenTemplateExpressionIsEmpty (line 191) | [Fact]
method Query_param_with_exploded_array (line 210) | [Fact]
method Query_param_with_list_array (line 222) | [Fact]
method Query_param_with_empty_array (line 234) | [Fact]
method ResolveOptionalAndRequiredQueryParameters (line 246) | [Fact]
method ReservedCharacterExpansion (line 258) | [Fact]
method PreserveReservedCharacterExpansion (line 270) | [Fact(Skip = "Unit tests should not require internet access!!")]
method ShouldSupportUnicodeCharacters (line 282) | [Fact]
method ShouldSupportUnicodeCharacters2 (line 295) | [Fact]
method Remove_a_query_parameters2 (line 312) | [Fact]
method EncodingTest1 (line 326) | [Fact]
method EncodingTest2 (line 338) | [Fact]
method EncodingTest3 (line 372) | [Fact]
method EncodingTest4 (line 392) | [Fact]
method EncodingTest5 (line 407) | [Fact]
method InvalidSpace (line 420) | [Fact]
FILE: src/UriTemplates/OperatorInfo.cs
class OperatorInfo (line 3) | public class OperatorInfo
FILE: src/UriTemplates/QueryStringParameterOrder.cs
type QueryStringParameterOrder (line 3) | public enum QueryStringParameterOrder
FILE: src/UriTemplates/Result.cs
class Result (line 8) | public class Result
method Result (line 17) | public Result()
method Append (line 21) | public StringBuilder Append(char value)
method Append (line 25) | public StringBuilder Append(string value)
method ToString (line 31) | public override string ToString()
method AppendName (line 35) | public void AppendName(string variable, OperatorInfo op, bool valueIsE...
method AppendList (line 42) | public void AppendList(OperatorInfo op, bool explode, string variable,...
method AppendDictionary (line 61) | public void AppendDictionary(OperatorInfo op, bool explode, IDictionar...
method AppendValue (line 84) | public void AppendValue(string value, int prefixLength, bool allowRese...
method Encode (line 100) | private static string Encode(string p, bool allowReserved)
method HexEscape (line 129) | public static string HexEscape(byte i)
method HexEscape (line 137) | public static string HexEscape(char c)
FILE: src/UriTemplates/UriTemplate.cs
class UriTemplate (line 18) | #if TYPE_CONVERTER
type States (line 38) | private enum States { CopyingLiterals, ParsingExpression }
method UriTemplate (line 43) | public UriTemplate(string template, bool resolvePartially = false, boo...
method ToString (line 52) | public override string ToString()
method SetParameter (line 57) | public void SetParameter(string name, object value)
method ClearParameter (line 62) | public void ClearParameter(string name)
method SetParameter (line 67) | public void SetParameter(string name, string value)
method SetParameter (line 72) | public void SetParameter(string name, IEnumerable<string> value)
method SetParameter (line 77) | public void SetParameter(string name, IDictionary<string, string> value)
method GetParameterNames (line 82) | public IEnumerable<string> GetParameterNames()
method Resolve (line 88) | public string Resolve()
method ResolveResult (line 94) | private Result ResolveResult()
method ProcessExpression (line 148) | private void ProcessExpression(StringBuilder currentExpression, Result...
method ProcessVariable (line 214) | private bool ProcessVariable(VarSpec varSpec, Result result, bool mult...
method IsVarNameChar (line 314) | private static bool IsVarNameChar(char c)
method GetOperator (line 323) | private static OperatorInfo GetOperator(char operatorIndicator)
method GetParameters (line 357) | public IDictionary<string, object> GetParameters(Uri uri, QueryStringP...
method CreateMatchingRegex (line 416) | public static string CreateMatchingRegex(string uriTemplate)
method CreateMatchingRegex2 (line 447) | public static string CreateMatchingRegex2(string uriTemplate)
method GetQueryExpression (line 479) | private static string GetQueryExpression(List<String> paramNames, stri...
method GetExpression (line 504) | private static string GetExpression(List<String> paramNames, string pr...
FILE: src/UriTemplates/UriTemplateConverter.cs
class UriTemplateConverter (line 11) | public sealed class UriTemplateConverter
method CanConvertFrom (line 15) | public override bool CanConvertFrom(ITypeDescriptorContext context, Ty...
method ConvertFrom (line 21) | public override object ConvertFrom(
FILE: src/UriTemplates/UriTemplateExtensions.cs
class UriTemplateExtensions (line 9) | public static class UriTemplateExtensions
method AddParameter (line 11) | public static UriTemplate AddParameter(this UriTemplate template, stri...
method AddParameters (line 18) | public static UriTemplate AddParameters(this UriTemplate template, obj...
method AddParameters (line 39) | public static UriTemplate AddParameters(this UriTemplate uriTemplate, ...
class UriExtensions (line 52) | public static class UriExtensions
method MakeTemplate (line 54) | public static UriTemplate MakeTemplate(this Uri uri)
method MakeTemplate (line 61) | public static UriTemplate MakeTemplate(this Uri uri, IDictionary<strin...
method GetQueryStringParameters (line 72) | public static Dictionary<string, object> GetQueryStringParameters(this...
FILE: src/UriTemplates/UriTemplateTable.cs
class UriTemplateTable (line 8) | public class UriTemplateTable
method Add (line 12) | public void Add(string key, UriTemplate template)
method Match (line 17) | public TemplateMatch Match(Uri url, QueryStringParameterOrder order = ...
class TemplateMatch (line 48) | public class TemplateMatch
FILE: src/UriTemplates/VarSpec.cs
class VarSpec (line 5) | public class VarSpec
method VarSpec (line 13) | public VarSpec(OperatorInfo operatorInfo)
method ToString (line 23) | public override string ToString()
Condensed preview — 33 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (121K chars).
[
{
"path": ".gitattributes",
"chars": 780,
"preview": "*.doc diff=astextplain\r\n*.DOC\tdiff=astextplain\r\n*.docx\tdiff=astextplain\r\n*.DOCX\tdiff=astextplain\r\n*.dot\tdiff=astextplai"
},
{
"path": ".github/CODEOWNERS",
"chars": 16,
"preview": "* @darrelmiller\n"
},
{
"path": ".github/dependabot.yml",
"chars": 357,
"preview": "version: 2\nupdates:\n- package-ecosystem: nuget\n directory: \"/\"\n schedule:\n interval: daily\n open-pull-requests-lim"
},
{
"path": ".github/workflows/auto-merge-dependabot.yml",
"chars": 792,
"preview": "name: Auto-merge dependabot updates\n\non:\n pull_request:\n branches: [ main ]\n\npermissions:\n pull-requests: write\n c"
},
{
"path": ".github/workflows/buildAndDeploy.yml",
"chars": 1956,
"preview": "name: Build and Test\n\non:\n workflow_dispatch:\n push:\n branches: [ main ]\n pull_request:\n branches: [ main ]\n\njo"
},
{
"path": ".github/workflows/codeql-analysis.yml",
"chars": 2739,
"preview": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# Y"
},
{
"path": ".gitignore",
"chars": 472,
"preview": "\n#ignore thumbnails created by windows\nThumbs.db\n#Ignore files build by Visual Studio\n*.obj\n*.exe\n*.pdb\n*.user\n*.aps\n*.p"
},
{
"path": ".gitmodules",
"chars": 125,
"preview": "[submodule \"lib/uritemplate-test\"]\n\tpath = lib/uritemplate-test\n\turl = https://github.com/uri-templates/uritemplate-test"
},
{
"path": ".travis.yml",
"chars": 199,
"preview": "language: csharp\nsudo: false # use the new container-based Travis infrastructure\ninstall:\n - nuget restore UriTemplat"
},
{
"path": "CHANGELOG.md",
"chars": 2454,
"preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Change"
},
{
"path": "LICENSE",
"chars": 10765,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 3859,
"preview": "# Uri Templates # \n\n[\nmd artifacts\ndotnet build --configuration %config% -"
},
{
"path": "src/UriTemplateTests/App.config",
"chars": 164,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<configuration>\r\n <uri>\r\n <idn enabled=\"All\" />\r\n <iriParsing ena"
},
{
"path": "src/UriTemplateTests/ParameterMatchingTests.cs",
"chars": 8673,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Text.RegularExpressi"
},
{
"path": "src/UriTemplateTests/SpecTests.cs",
"chars": 9136,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing Newtonsoft.Json.Linq;\nusing T"
},
{
"path": "src/UriTemplateTests/UriExtensionTests.cs",
"chars": 2678,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing Tavis.UriTemplates;\nusing X"
},
{
"path": "src/UriTemplateTests/UriTemplateConverterTests.cs",
"chars": 710,
"preview": "using System.ComponentModel;\nusing Tavis.UriTemplates;\nusing Xunit;\n\nnamespace UriTemplateTests\n{\n public class UriT"
},
{
"path": "src/UriTemplateTests/UriTemplateExtensionsTests.cs",
"chars": 9619,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing Tavis;\nusing Tavis.UriTempl"
},
{
"path": "src/UriTemplateTests/UriTemplateTableTests.cs",
"chars": 4794,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing Tavis.UriTemplates;\nusing X"
},
{
"path": "src/UriTemplateTests/UriTemplateTests.csproj",
"chars": 991,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n <PropertyGroup>\n <TargetFramework>net6.0</TargetFramework>\n\n <IsPackable>fals"
},
{
"path": "src/UriTemplateTests/UsageTests.cs",
"chars": 14582,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Net.Http;\nusing System.Text;\nusing Tavi"
},
{
"path": "src/UriTemplates/OperatorInfo.cs",
"chars": 335,
"preview": "namespace Tavis.UriTemplates\n{\n public class OperatorInfo\n {\n public bool Default { get; set; }\n pub"
},
{
"path": "src/UriTemplates/QueryStringParameterOrder.cs",
"chars": 114,
"preview": "namespace Tavis.UriTemplates\n{\n public enum QueryStringParameterOrder\n {\n Strict,\n Any\n }\n}"
},
{
"path": "src/UriTemplates/Result.cs",
"chars": 4477,
"preview": "using System.Collections;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\n\nnamespace Tavis.UriTe"
},
{
"path": "src/UriTemplates/UriTemplate.cs",
"chars": 20716,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Text.RegularExpressi"
},
{
"path": "src/UriTemplates/UriTemplateConverter.cs",
"chars": 1189,
"preview": "using System;\nusing System.ComponentModel;\nusing System.Globalization;\n\nnamespace Tavis.UriTemplates\n{\n#if TYPE_CONVERT"
},
{
"path": "src/UriTemplates/UriTemplateExtensions.cs",
"chars": 3021,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Reflection;\nusing System.Text.RegularEx"
},
{
"path": "src/UriTemplates/UriTemplateTable.cs",
"chars": 1465,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\n\nnamespace Tavis.UriTemplates\n{\n "
},
{
"path": "src/UriTemplates/UriTemplates.csproj",
"chars": 2239,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n <PropertyGroup>\n <TargetFrameworks>netstandard2.0;netstandard2.1;</TargetFramewor"
},
{
"path": "src/UriTemplates/VarSpec.cs",
"chars": 794,
"preview": "using System.Text;\n\nnamespace Tavis.UriTemplates\n{\n public class VarSpec\n {\n private readonly OperatorInfo "
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the tavis-software/Tavis.UriTemplates GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 33 files (111.4 KB), approximately 25.6k tokens, and a symbol index with 155 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.