using Microsoft.Extensions.DependencyInjection; using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; using System.Text.Json; using System.Text.Json.Serialization; namespace ModelContextProtocol.Tests.Client; public partial class McpClientResourceTemplateTests : ClientServerTestBase { public McpClientResourceTemplateTests(ITestOutputHelper outputHelper) : base(outputHelper) { } protected override void ConfigureServices(ServiceCollection services, IMcpServerBuilder mcpServerBuilder) { mcpServerBuilder.WithReadResourceHandler((request, cancellationToken) => new ValueTask(new ReadResourceResult { Contents = [new TextResourceContents { Text = request.Params.Uri ?? "", Uri = request.Params.Uri ?? "" }] })); } public static IEnumerable UriTemplate_InputsProduceExpectedOutputs_MemberData() { string[] sources = [ SpecExamples, SpecExamplesBySection, ExtendedTests, ]; foreach (var source in sources) { var tests = JsonSerializer.Deserialize(source, JsonContext7.Default.DictionaryStringTestGroup); Assert.NotNull(tests); foreach (var testGroup in tests.Values) { Dictionary variables = []; foreach (var entry in testGroup.Variables) { variables[entry.Key] = entry.Value.ValueKind switch { JsonValueKind.Null => null, JsonValueKind.String => entry.Value.GetString(), JsonValueKind.Number => entry.Value.GetDouble(), JsonValueKind.Array => entry.Value.EnumerateArray().Select(i => i.GetString()).ToArray(), JsonValueKind.Object => entry.Value.EnumerateObject().ToDictionary(p => p.Name, p => p.Value.GetString()), _ => throw new Exception($"Invalid test case format: {entry.Value.ValueKind}") }; } foreach (var testCase in testGroup.TestCases) { string uriTemplate = testCase[0].GetString() ?? throw new Exception("Invalid test case format."); object expected = testCase[1].ValueKind switch { JsonValueKind.String => testCase[1].GetString()!, JsonValueKind.Array => testCase[1].EnumerateArray().Select(i => i.GetString()).ToArray(), JsonValueKind.False => false, _ => throw new Exception("Invalid test case format.") }; yield return new object[] { variables, uriTemplate, expected }; } } } } [Theory] [MemberData(nameof(UriTemplate_InputsProduceExpectedOutputs_MemberData))] public async Task UriTemplate_InputsProduceExpectedOutputs( IReadOnlyDictionary variables, string uriTemplate, object expected) { await using McpClient client = await CreateMcpClientForServer(); var result = await client.ReadResourceAsync(uriTemplate, variables, null, TestContext.Current.CancellationToken); Assert.NotNull(result); var actualUri = Assert.IsType(Assert.Single(result.Contents)).Text; if (expected is string expectedUri) { Assert.Equal(expectedUri, actualUri); } else { Assert.Contains(actualUri, Assert.IsType(expected)); } } public class TestGroup { [JsonPropertyName("level")] public int Level { get; set; } = 4; [JsonPropertyName("variables")] public IDictionary Variables { get; set; } = new Dictionary(); [JsonPropertyName("testcases")] public IList> TestCases { get; set; } = []; } [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower)] [JsonSerializable(typeof(Dictionary))] internal partial class JsonContext7 : JsonSerializerContext; // The following data comes from: // https://github.com/uri-templates/uritemplate-test/tree/1eb27ab4462b9e5819dc47db99044f5fd1fa9bc7 // The JSON from the test case files has been extracted below. // Copyright 2011- The Authors // // 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. private const string SpecExamples = """ { "Level 1 Examples" : { "level": 1, "variables": { "var" : "value", "hello" : "Hello World!" }, "testcases" : [ ["{var}", "value"], ["'{var}'", "'value'"], ["{hello}", "Hello%20World%21"] ] }, "Level 2 Examples" : { "level": 2, "variables": { "var" : "value", "hello" : "Hello World!", "path" : "/foo/bar" }, "testcases" : [ ["{+var}", "value"], ["{+hello}", "Hello%20World!"], ["{+path}/here", "/foo/bar/here"], ["here?ref={+path}", "here?ref=/foo/bar"] ] }, "Level 3 Examples" : { "level": 3, "variables": { "var" : "value", "hello" : "Hello World!", "empty" : "", "path" : "/foo/bar", "x" : "1024", "y" : "768" }, "testcases" : [ ["map?{x,y}", "map?1024,768"], ["{x,hello,y}", "1024,Hello%20World%21,768"], ["{+x,hello,y}", "1024,Hello%20World!,768"], ["{+path,x}/here", "/foo/bar,1024/here"], ["{#x,hello,y}", "#1024,Hello%20World!,768"], ["{#path,x}/here", "#/foo/bar,1024/here"], ["X{.var}", "X.value"], ["X{.x,y}", "X.1024.768"], ["{/var}", "/value"], ["{/var,x}/here", "/value/1024/here"], ["{;x,y}", ";x=1024;y=768"], ["{;x,y,empty}", ";x=1024;y=768;empty"], ["{?x,y}", "?x=1024&y=768"], ["{?x,y,empty}", "?x=1024&y=768&empty="], ["?fixed=yes{&x}", "?fixed=yes&x=1024"], ["{&x,y,empty}", "&x=1024&y=768&empty="] ] }, "Level 4 Examples" : { "level": 4, "variables": { "var": "value", "hello": "Hello World!", "path": "/foo/bar", "list": ["red", "green", "blue"], "keys": {"semi": ";", "dot": ".", "comma":","} }, "testcases": [ ["{var:3}", "val"], ["{var:30}", "value"], ["{list}", "red,green,blue"], ["{list*}", "red,green,blue"], ["{keys}", [ "comma,%2C,dot,.,semi,%3B", "comma,%2C,semi,%3B,dot,.", "dot,.,comma,%2C,semi,%3B", "dot,.,semi,%3B,comma,%2C", "semi,%3B,comma,%2C,dot,.", "semi,%3B,dot,.,comma,%2C" ]], ["{keys*}", [ "comma=%2C,dot=.,semi=%3B", "comma=%2C,semi=%3B,dot=.", "dot=.,comma=%2C,semi=%3B", "dot=.,semi=%3B,comma=%2C", "semi=%3B,comma=%2C,dot=.", "semi=%3B,dot=.,comma=%2C" ]], ["{+path:6}/here", "/foo/b/here"], ["{+list}", "red,green,blue"], ["{+list*}", "red,green,blue"], ["{+keys}", [ "comma,,,dot,.,semi,;", "comma,,,semi,;,dot,.", "dot,.,comma,,,semi,;", "dot,.,semi,;,comma,,", "semi,;,comma,,,dot,.", "semi,;,dot,.,comma,," ]], ["{+keys*}", [ "comma=,,dot=.,semi=;", "comma=,,semi=;,dot=.", "dot=.,comma=,,semi=;", "dot=.,semi=;,comma=,", "semi=;,comma=,,dot=.", "semi=;,dot=.,comma=," ]], ["{#path:6}/here", "#/foo/b/here"], ["{#list}", "#red,green,blue"], ["{#list*}", "#red,green,blue"], ["{#keys}", [ "#comma,,,dot,.,semi,;", "#comma,,,semi,;,dot,.", "#dot,.,comma,,,semi,;", "#dot,.,semi,;,comma,,", "#semi,;,comma,,,dot,.", "#semi,;,dot,.,comma,," ]], ["{#keys*}", [ "#comma=,,dot=.,semi=;", "#comma=,,semi=;,dot=.", "#dot=.,comma=,,semi=;", "#dot=.,semi=;,comma=,", "#semi=;,comma=,,dot=.", "#semi=;,dot=.,comma=," ]], ["X{.var:3}", "X.val"], ["X{.list}", "X.red,green,blue"], ["X{.list*}", "X.red.green.blue"], ["X{.keys}", [ "X.comma,%2C,dot,.,semi,%3B", "X.comma,%2C,semi,%3B,dot,.", "X.dot,.,comma,%2C,semi,%3B", "X.dot,.,semi,%3B,comma,%2C", "X.semi,%3B,comma,%2C,dot,.", "X.semi,%3B,dot,.,comma,%2C" ]], ["{/var:1,var}", "/v/value"], ["{/list}", "/red,green,blue"], ["{/list*}", "/red/green/blue"], ["{/list*,path:4}", "/red/green/blue/%2Ffoo"], ["{/keys}", [ "/comma,%2C,dot,.,semi,%3B", "/comma,%2C,semi,%3B,dot,.", "/dot,.,comma,%2C,semi,%3B", "/dot,.,semi,%3B,comma,%2C", "/semi,%3B,comma,%2C,dot,.", "/semi,%3B,dot,.,comma,%2C" ]], ["{/keys*}", [ "/comma=%2C/dot=./semi=%3B", "/comma=%2C/semi=%3B/dot=.", "/dot=./comma=%2C/semi=%3B", "/dot=./semi=%3B/comma=%2C", "/semi=%3B/comma=%2C/dot=.", "/semi=%3B/dot=./comma=%2C" ]], ["{;hello:5}", ";hello=Hello"], ["{;list}", ";list=red,green,blue"], ["{;list*}", ";list=red;list=green;list=blue"], ["{;keys}", [ ";keys=comma,%2C,dot,.,semi,%3B", ";keys=comma,%2C,semi,%3B,dot,.", ";keys=dot,.,comma,%2C,semi,%3B", ";keys=dot,.,semi,%3B,comma,%2C", ";keys=semi,%3B,comma,%2C,dot,.", ";keys=semi,%3B,dot,.,comma,%2C" ]], ["{;keys*}", [ ";comma=%2C;dot=.;semi=%3B", ";comma=%2C;semi=%3B;dot=.", ";dot=.;comma=%2C;semi=%3B", ";dot=.;semi=%3B;comma=%2C", ";semi=%3B;comma=%2C;dot=.", ";semi=%3B;dot=.;comma=%2C" ]], ["{?var:3}", "?var=val"], ["{?list}", "?list=red,green,blue"], ["{?list*}", "?list=red&list=green&list=blue"], ["{?keys}", [ "?keys=comma,%2C,dot,.,semi,%3B", "?keys=comma,%2C,semi,%3B,dot,.", "?keys=dot,.,comma,%2C,semi,%3B", "?keys=dot,.,semi,%3B,comma,%2C", "?keys=semi,%3B,comma,%2C,dot,.", "?keys=semi,%3B,dot,.,comma,%2C" ]], ["{?keys*}", [ "?comma=%2C&dot=.&semi=%3B", "?comma=%2C&semi=%3B&dot=.", "?dot=.&comma=%2C&semi=%3B", "?dot=.&semi=%3B&comma=%2C", "?semi=%3B&comma=%2C&dot=.", "?semi=%3B&dot=.&comma=%2C" ]], ["{&var:3}", "&var=val"], ["{&list}", "&list=red,green,blue"], ["{&list*}", "&list=red&list=green&list=blue"], ["{&keys}", [ "&keys=comma,%2C,dot,.,semi,%3B", "&keys=comma,%2C,semi,%3B,dot,.", "&keys=dot,.,comma,%2C,semi,%3B", "&keys=dot,.,semi,%3B,comma,%2C", "&keys=semi,%3B,comma,%2C,dot,.", "&keys=semi,%3B,dot,.,comma,%2C" ]], ["{&keys*}", [ "&comma=%2C&dot=.&semi=%3B", "&comma=%2C&semi=%3B&dot=.", "&dot=.&comma=%2C&semi=%3B", "&dot=.&semi=%3B&comma=%2C", "&semi=%3B&comma=%2C&dot=.", "&semi=%3B&dot=.&comma=%2C" ]] ] } } """; private const string SpecExamplesBySection = """ { "2.1 Literals" : { "variables": { "count" : ["one", "two", "three"] }, "testcases" : [ ["'{count}'", "'one,two,three'"] ] }, "3.2.1 Variable Expansion" : { "variables": { "count" : ["one", "two", "three"], "dom" : ["example", "com"], "dub" : "me/too", "hello" : "Hello World!", "half" : "50%", "var" : "value", "who" : "fred", "base" : "http://example.com/home/", "path" : "/foo/bar", "list" : ["red", "green", "blue"], "keys" : { "semi" : ";", "dot" : ".", "comma" : ","}, "v" : "6", "x" : "1024", "y" : "768", "empty" : "", "empty_keys" : {}, "undef" : null }, "testcases" : [ ["{count}", "one,two,three"], ["{count*}", "one,two,three"], ["{/count}", "/one,two,three"], ["{/count*}", "/one/two/three"], ["{;count}", ";count=one,two,three"], ["{;count*}", ";count=one;count=two;count=three"], ["{?count}", "?count=one,two,three"], ["{?count*}", "?count=one&count=two&count=three"], ["{&count*}", "&count=one&count=two&count=three"] ] }, "3.2.2 Simple String Expansion" : { "variables": { "count" : ["one", "two", "three"], "dom" : ["example", "com"], "dub" : "me/too", "hello" : "Hello World!", "half" : "50%", "var" : "value", "who" : "fred", "base" : "http://example.com/home/", "path" : "/foo/bar", "list" : ["red", "green", "blue"], "keys" : { "semi" : ";", "dot" : ".", "comma" : ","}, "v" : "6", "x" : "1024", "y" : "768", "empty" : "", "empty_keys" : {}, "undef" : null }, "testcases" : [ ["{var}", "value"], ["{hello}", "Hello%20World%21"], ["{half}", "50%25"], ["O{empty}X", "OX"], ["O{undef}X", "OX"], ["{x,y}", "1024,768"], ["{x,hello,y}", "1024,Hello%20World%21,768"], ["?{x,empty}", "?1024,"], ["?{x,undef}", "?1024"], ["?{undef,y}", "?768"], ["{var:3}", "val"], ["{var:30}", "value"], ["{list}", "red,green,blue"], ["{list*}", "red,green,blue"], ["{keys}", [ "comma,%2C,dot,.,semi,%3B", "comma,%2C,semi,%3B,dot,.", "dot,.,comma,%2C,semi,%3B", "dot,.,semi,%3B,comma,%2C", "semi,%3B,comma,%2C,dot,.", "semi,%3B,dot,.,comma,%2C" ]], ["{keys*}", [ "comma=%2C,dot=.,semi=%3B", "comma=%2C,semi=%3B,dot=.", "dot=.,comma=%2C,semi=%3B", "dot=.,semi=%3B,comma=%2C", "semi=%3B,comma=%2C,dot=.", "semi=%3B,dot=.,comma=%2C" ]] ] }, "3.2.3 Reserved Expansion" : { "variables": { "count" : ["one", "two", "three"], "dom" : ["example", "com"], "dub" : "me/too", "hello" : "Hello World!", "half" : "50%", "var" : "value", "who" : "fred", "base" : "http://example.com/home/", "path" : "/foo/bar", "list" : ["red", "green", "blue"], "keys" : { "semi" : ";", "dot" : ".", "comma" : ","}, "v" : "6", "x" : "1024", "y" : "768", "empty" : "", "empty_keys" : {}, "undef" : null }, "testcases" : [ ["{+var}", "value"], ["{/var,empty}", "/value/"], ["{/var,undef}", "/value"], ["{+hello}", "Hello%20World!"], ["{+half}", "50%25"], ["{base}index", "http%3A%2F%2Fexample.com%2Fhome%2Findex"], ["{+base}index", "http://example.com/home/index"], ["O{+empty}X", "OX"], ["O{+undef}X", "OX"], ["{+path}/here", "/foo/bar/here"], ["{+path:6}/here", "/foo/b/here"], ["here?ref={+path}", "here?ref=/foo/bar"], ["up{+path}{var}/here", "up/foo/barvalue/here"], ["{+x,hello,y}", "1024,Hello%20World!,768"], ["{+path,x}/here", "/foo/bar,1024/here"], ["{+list}", "red,green,blue"], ["{+list*}", "red,green,blue"], ["{+keys}", [ "comma,,,dot,.,semi,;", "comma,,,semi,;,dot,.", "dot,.,comma,,,semi,;", "dot,.,semi,;,comma,,", "semi,;,comma,,,dot,.", "semi,;,dot,.,comma,," ]], ["{+keys*}", [ "comma=,,dot=.,semi=;", "comma=,,semi=;,dot=.", "dot=.,comma=,,semi=;", "dot=.,semi=;,comma=,", "semi=;,comma=,,dot=.", "semi=;,dot=.,comma=," ]] ] }, "3.2.4 Fragment Expansion" : { "variables": { "count" : ["one", "two", "three"], "dom" : ["example", "com"], "dub" : "me/too", "hello" : "Hello World!", "half" : "50%", "var" : "value", "who" : "fred", "base" : "http://example.com/home/", "path" : "/foo/bar", "list" : ["red", "green", "blue"], "keys" : { "semi" : ";", "dot" : ".", "comma" : ","}, "v" : "6", "x" : "1024", "y" : "768", "empty" : "", "empty_keys" : {}, "undef" : null }, "testcases" : [ ["{#var}", "#value"], ["{#hello}", "#Hello%20World!"], ["{#half}", "#50%25"], ["foo{#empty}", "foo#"], ["foo{#undef}", "foo"], ["{#x,hello,y}", "#1024,Hello%20World!,768"], ["{#path,x}/here", "#/foo/bar,1024/here"], ["{#path:6}/here", "#/foo/b/here"], ["{#list}", "#red,green,blue"], ["{#list*}", "#red,green,blue"], ["{#keys}", [ "#comma,,,dot,.,semi,;", "#comma,,,semi,;,dot,.", "#dot,.,comma,,,semi,;", "#dot,.,semi,;,comma,,", "#semi,;,comma,,,dot,.", "#semi,;,dot,.,comma,," ]] ] }, "3.2.5 Label Expansion with Dot-Prefix" : { "variables": { "count" : ["one", "two", "three"], "dom" : ["example", "com"], "dub" : "me/too", "hello" : "Hello World!", "half" : "50%", "var" : "value", "who" : "fred", "base" : "http://example.com/home/", "path" : "/foo/bar", "list" : ["red", "green", "blue"], "keys" : { "semi" : ";", "dot" : ".", "comma" : ","}, "v" : "6", "x" : "1024", "y" : "768", "empty" : "", "empty_keys" : {}, "undef" : null }, "testcases" : [ ["{.who}", ".fred"], ["{.who,who}", ".fred.fred"], ["{.half,who}", ".50%25.fred"], ["www{.dom*}", "www.example.com"], ["X{.var}", "X.value"], ["X{.var:3}", "X.val"], ["X{.empty}", "X."], ["X{.undef}", "X"], ["X{.list}", "X.red,green,blue"], ["X{.list*}", "X.red.green.blue"], ["{#keys}", [ "#comma,,,dot,.,semi,;", "#comma,,,semi,;,dot,.", "#dot,.,comma,,,semi,;", "#dot,.,semi,;,comma,,", "#semi,;,comma,,,dot,.", "#semi,;,dot,.,comma,," ]], ["{#keys*}", [ "#comma=,,dot=.,semi=;", "#comma=,,semi=;,dot=.", "#dot=.,comma=,,semi=;", "#dot=.,semi=;,comma=,", "#semi=;,comma=,,dot=.", "#semi=;,dot=.,comma=," ]], ["X{.empty_keys}", "X"], ["X{.empty_keys*}", "X"] ] }, "3.2.6 Path Segment Expansion" : { "variables": { "count" : ["one", "two", "three"], "dom" : ["example", "com"], "dub" : "me/too", "hello" : "Hello World!", "half" : "50%", "var" : "value", "who" : "fred", "base" : "http://example.com/home/", "path" : "/foo/bar", "list" : ["red", "green", "blue"], "keys" : { "semi" : ";", "dot" : ".", "comma" : ","}, "v" : "6", "x" : "1024", "y" : "768", "empty" : "", "empty_keys" : {}, "undef" : null }, "testcases" : [ ["{/who}", "/fred"], ["{/who,who}", "/fred/fred"], ["{/half,who}", "/50%25/fred"], ["{/who,dub}", "/fred/me%2Ftoo"], ["{/var}", "/value"], ["{/var,empty}", "/value/"], ["{/var,undef}", "/value"], ["{/var,x}/here", "/value/1024/here"], ["{/var:1,var}", "/v/value"], ["{/list}", "/red,green,blue"], ["{/list*}", "/red/green/blue"], ["{/list*,path:4}", "/red/green/blue/%2Ffoo"], ["{/keys}", [ "/comma,%2C,dot,.,semi,%3B", "/comma,%2C,semi,%3B,dot,.", "/dot,.,comma,%2C,semi,%3B", "/dot,.,semi,%3B,comma,%2C", "/semi,%3B,comma,%2C,dot,.", "/semi,%3B,dot,.,comma,%2C" ]], ["{/keys*}", [ "/comma=%2C/dot=./semi=%3B", "/comma=%2C/semi=%3B/dot=.", "/dot=./comma=%2C/semi=%3B", "/dot=./semi=%3B/comma=%2C", "/semi=%3B/comma=%2C/dot=.", "/semi=%3B/dot=./comma=%2C" ]] ] }, "3.2.7 Path-Style Parameter Expansion" : { "variables": { "count" : ["one", "two", "three"], "dom" : ["example", "com"], "dub" : "me/too", "hello" : "Hello World!", "half" : "50%", "var" : "value", "who" : "fred", "base" : "http://example.com/home/", "path" : "/foo/bar", "list" : ["red", "green", "blue"], "keys" : { "semi" : ";", "dot" : ".", "comma" : ","}, "v" : "6", "x" : "1024", "y" : "768", "empty" : "", "empty_keys" : {}, "undef" : null }, "testcases" : [ ["{;who}", ";who=fred"], ["{;half}", ";half=50%25"], ["{;empty}", ";empty"], ["{;hello:5}", ";hello=Hello"], ["{;v,empty,who}", ";v=6;empty;who=fred"], ["{;v,bar,who}", ";v=6;who=fred"], ["{;x,y}", ";x=1024;y=768"], ["{;x,y,empty}", ";x=1024;y=768;empty"], ["{;x,y,undef}", ";x=1024;y=768"], ["{;list}", ";list=red,green,blue"], ["{;list*}", ";list=red;list=green;list=blue"], ["{;keys}", [ ";keys=comma,%2C,dot,.,semi,%3B", ";keys=comma,%2C,semi,%3B,dot,.", ";keys=dot,.,comma,%2C,semi,%3B", ";keys=dot,.,semi,%3B,comma,%2C", ";keys=semi,%3B,comma,%2C,dot,.", ";keys=semi,%3B,dot,.,comma,%2C" ]], ["{;keys*}", [ ";comma=%2C;dot=.;semi=%3B", ";comma=%2C;semi=%3B;dot=.", ";dot=.;comma=%2C;semi=%3B", ";dot=.;semi=%3B;comma=%2C", ";semi=%3B;comma=%2C;dot=.", ";semi=%3B;dot=.;comma=%2C" ]] ] }, "3.2.8 Form-Style Query Expansion" : { "variables": { "count" : ["one", "two", "three"], "dom" : ["example", "com"], "dub" : "me/too", "hello" : "Hello World!", "half" : "50%", "var" : "value", "who" : "fred", "base" : "http://example.com/home/", "path" : "/foo/bar", "list" : ["red", "green", "blue"], "keys" : { "semi" : ";", "dot" : ".", "comma" : ","}, "v" : "6", "x" : "1024", "y" : "768", "empty" : "", "empty_keys" : {}, "undef" : null }, "testcases" : [ ["{?who}", "?who=fred"], ["{?half}", "?half=50%25"], ["{?x,y}", "?x=1024&y=768"], ["{?x,y,empty}", "?x=1024&y=768&empty="], ["{?x,y,undef}", "?x=1024&y=768"], ["{?var:3}", "?var=val"], ["{?list}", "?list=red,green,blue"], ["{?list*}", "?list=red&list=green&list=blue"], ["{?keys}", [ "?keys=comma,%2C,dot,.,semi,%3B", "?keys=comma,%2C,semi,%3B,dot,.", "?keys=dot,.,comma,%2C,semi,%3B", "?keys=dot,.,semi,%3B,comma,%2C", "?keys=semi,%3B,comma,%2C,dot,.", "?keys=semi,%3B,dot,.,comma,%2C" ]], ["{?keys*}", [ "?comma=%2C&dot=.&semi=%3B", "?comma=%2C&semi=%3B&dot=.", "?dot=.&comma=%2C&semi=%3B", "?dot=.&semi=%3B&comma=%2C", "?semi=%3B&comma=%2C&dot=.", "?semi=%3B&dot=.&comma=%2C" ]] ] }, "3.2.9 Form-Style Query Continuation" : { "variables": { "count" : ["one", "two", "three"], "dom" : ["example", "com"], "dub" : "me/too", "hello" : "Hello World!", "half" : "50%", "var" : "value", "who" : "fred", "base" : "http://example.com/home/", "path" : "/foo/bar", "list" : ["red", "green", "blue"], "keys" : { "semi" : ";", "dot" : ".", "comma" : ","}, "v" : "6", "x" : "1024", "y" : "768", "empty" : "", "empty_keys" : {}, "undef" : null }, "testcases" : [ ["{&who}", "&who=fred"], ["{&half}", "&half=50%25"], ["?fixed=yes{&x}", "?fixed=yes&x=1024"], ["{&var:3}", "&var=val"], ["{&x,y,empty}", "&x=1024&y=768&empty="], ["{&x,y,undef}", "&x=1024&y=768"], ["{&list}", "&list=red,green,blue"], ["{&list*}", "&list=red&list=green&list=blue"], ["{&keys}", [ "&keys=comma,%2C,dot,.,semi,%3B", "&keys=comma,%2C,semi,%3B,dot,.", "&keys=dot,.,comma,%2C,semi,%3B", "&keys=dot,.,semi,%3B,comma,%2C", "&keys=semi,%3B,comma,%2C,dot,.", "&keys=semi,%3B,dot,.,comma,%2C" ]], ["{&keys*}", [ "&comma=%2C&dot=.&semi=%3B", "&comma=%2C&semi=%3B&dot=.", "&dot=.&comma=%2C&semi=%3B", "&dot=.&semi=%3B&comma=%2C", "&semi=%3B&comma=%2C&dot=.", "&semi=%3B&dot=.&comma=%2C" ]] ] } } """; private const string ExtendedTests = """ { "Additional Examples 1":{ "level":4, "variables":{ "id" : "person", "token" : "12345", "fields" : ["id", "name", "picture"], "format" : "json", "q" : "URI Templates", "page" : "5", "lang" : "en", "geocode" : ["37.76","-122.427"], "first_name" : "John", "last.name" : "Doe", "Some%20Thing" : "foo", "number" : 6, "long" : 37.76, "lat" : -122.427, "group_id" : "12345", "query" : "PREFIX dc: SELECT ?book ?who WHERE { ?book dc:creator ?who }", "uri" : "http://example.org/?uri=http%3A%2F%2Fexample.org%2F", "word" : "drücken", "Stra%C3%9Fe" : "Grüner Weg", "random" : "šö䟜ñꀣ¥‡ÑÒÓÔÕÖרÙÚàáâãäåæçÿ", "assoc_special_chars" : { "šö䟜ñꀣ¥‡ÑÒÓÔÕ" : "ÖרÙÚàáâãäåæçÿ" } }, "testcases":[ [ "{/id*}" , "/person" ], [ "{/id*}{?fields,first_name,last.name,token}","/person?fields=id,name,picture&first_name=John&last.name=Doe&token=12345"], ["/search.{format}{?q,geocode,lang,locale,page,result_type}","/search.json?q=URI%20Templates&geocode=37.76,-122.427&lang=en&page=5"], ["/test{/Some%20Thing}", "/test/foo" ], ["/set{?number}", "/set?number=6"], ["/loc{?long,lat}" , "/loc?long=37.76&lat=-122.427"], ["/base{/group_id,first_name}/pages{/page,lang}{?format,q}","/base/12345/John/pages/5/en?format=json&q=URI%20Templates"], ["/sparql{?query}", "/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"], ["/go{?uri}", "/go?uri=http%3A%2F%2Fexample.org%2F%3Furi%3Dhttp%253A%252F%252Fexample.org%252F"], ["/service{?word}", "/service?word=dr%C3%BCcken"], ["/lookup{?Stra%C3%9Fe}", "/lookup?Stra%C3%9Fe=Gr%C3%BCner%20Weg"], ["{random}" , "%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"], ["{?assoc_special_chars*}", "?%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"] ] }, "Additional Examples 2":{ "level":4, "variables":{ "id" : ["person","albums"], "token" : "12345", "fields" : ["id", "name", "picture"], "format" : "atom", "q" : "URI Templates", "page" : "10", "start" : "5", "lang" : "en", "geocode" : ["37.76","-122.427"] }, "testcases":[ [ "{/id*}" , "/person/albums" ], [ "{/id*}{?fields,token}" , "/person/albums?fields=id,name,picture&token=12345" ] ] }, "Additional Examples 3: Empty Variables":{ "variables" : { "empty_list" : [], "empty_assoc" : {} }, "testcases":[ [ "{/empty_list}", [ "" ] ], [ "{/empty_list*}", [ "" ] ], [ "{?empty_list}", [ ""] ], [ "{?empty_list*}", [ "" ] ], [ "{?empty_assoc}", [ "" ] ], [ "{?empty_assoc*}", [ "" ] ] ] }, "Additional Examples 4: Numeric Keys":{ "variables" : { "42" : "The Answer to the Ultimate Question of Life, the Universe, and Everything", "1337" : ["leet", "as","it", "can","be"], "german" : { "11": "elf", "12": "zwölf" } }, "testcases":[ [ "{42}", "The%20Answer%20to%20the%20Ultimate%20Question%20of%20Life%2C%20the%20Universe%2C%20and%20Everything"], [ "{?42}", "?42=The%20Answer%20to%20the%20Ultimate%20Question%20of%20Life%2C%20the%20Universe%2C%20and%20Everything"], [ "{1337}", "leet,as,it,can,be"], [ "{?1337*}", "?1337=leet&1337=as&1337=it&1337=can&1337=be"], [ "{?german*}", [ "?11=elf&12=zw%C3%B6lf", "?12=zw%C3%B6lf&11=elf"] ] ] }, "Additional Examples 5: Explode Combinations":{ "variables" : { "id" : "admin", "token" : "12345", "tab" : "overview", "keys" : { "key1": "val1", "key2": "val2" } }, "testcases":[ [ "{?id,token,keys*}", [ "?id=admin&token=12345&key1=val1&key2=val2", "?id=admin&token=12345&key2=val2&key1=val1"] ], [ "{/id}{?token,keys*}", [ "/admin?token=12345&key1=val1&key2=val2", "/admin?token=12345&key2=val2&key1=val1"] ], [ "{?id,token}{&keys*}", [ "?id=admin&token=12345&key1=val1&key2=val2", "?id=admin&token=12345&key2=val2&key1=val1"] ], [ "/user{/id}{?token,tab}{&keys*}", [ "/user/admin?token=12345&tab=overview&key1=val1&key2=val2", "/user/admin?token=12345&tab=overview&key2=val2&key1=val1"] ] ] }, "Additional Examples 6: Reserved Expansion":{ "variables" : { "id" : "admin%2F", "not_pct" : "%foo", "list" : ["red%25", "%2Fgreen", "blue "], "keys" : { "key1": "val1%2F", "key2": "val2%2F" } }, "testcases": [ ["{+id}", "admin%2F"], ["{#id}", "#admin%2F"], ["{id}", "admin%252F"], ["{+not_pct}", "%25foo"], ["{#not_pct}", "#%25foo"], ["{not_pct}", "%25foo"], ["{+list}", "red%25,%2Fgreen,blue%20"], ["{#list}", "#red%25,%2Fgreen,blue%20"], ["{list}", "red%2525,%252Fgreen,blue%20"], ["{+keys}", "key1,val1%2F,key2,val2%2F"], ["{#keys}", "#key1,val1%2F,key2,val2%2F"], ["{keys}", "key1,val1%252F,key2,val2%252F"] ] } } """; }