Skip to content

Commit 422bb99

Browse files
Support string host param (microsoft#7607)
For services that are defined like the below, the constructor parameter should be a string. ``` @server( "https://{fullyQualifiedNamespace}", "The Schema Registry service endpoint.", { @doc("The Schema Registry service endpoint, for example 'my-namespace.servicebus.windows.net'.") fullyQualifiedNamespace: string, } ) ````
1 parent 9e19291 commit 422bb99

File tree

77 files changed

+1074
-617
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+1074
-617
lines changed

docs/samples/client/csharp/SampleService/main.tsp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import "@azure-tools/typespec-azure-core";
1010
"{sampleTypeSpecUrl}",
1111
"Endpoint Service",
1212
{
13-
sampleTypeSpecUrl: string,
13+
sampleTypeSpecUrl: url,
1414
}
1515
)
1616
@useAuth(ApiKeyAuth<ApiKeyLocation.header, "my-api-key">)

packages/http-client-csharp/emitter/src/lib/client-converter.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,10 @@ function fromSdkClient(
108108
const isEndpoint = parameter.name === endpointVariableName;
109109
const parameterType: InputType = isEndpoint
110110
? {
111-
kind: "url",
112-
name: "url",
113-
crossLanguageDefinitionId: "TypeSpec.url",
111+
kind: parameter.type.kind === "string" ? "string" : "url",
112+
name: "endpoint",
113+
crossLanguageDefinitionId:
114+
parameter.type.kind === "string" ? "TypeSpec.string" : "TypeSpec.url",
114115
}
115116
: fromSdkType(sdkContext, parameter.type); // TODO: consolidate with converter.fromSdkEndpointType
116117
parameters.push({
@@ -132,6 +133,7 @@ function fromSdkClient(
132133
parameter.clientDefaultValue,
133134
parameterType,
134135
),
136+
serverUrlTemplate: type.serverUrl,
135137
});
136138
}
137139
return parameters;

packages/http-client-csharp/emitter/src/type/input-parameter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ export interface InputParameter {
2626
arraySerializationDelimiter?: string;
2727
headerCollectionPrefix?: string;
2828
decorators?: DecoratorInfo[];
29+
serverUrlTemplate?: string;
2930
}

packages/http-client-csharp/emitter/test/Unit/input-parameter.test.ts

Lines changed: 89 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -626,11 +626,18 @@ describe("Test Cookie Parameters", () => {
626626
);
627627
});
628628
});
629+
});
629630

630-
describe("Unsupported endpoint url", () => {
631-
it("cookie parameter is not supported", async () => {
632-
const program = await typeSpecCompile(
633-
`
631+
describe("Endpoint parameters", () => {
632+
let runner: TestHost;
633+
634+
beforeEach(async () => {
635+
runner = await createEmitterTestHost();
636+
});
637+
638+
it("Multiple parameters are not supported", async () => {
639+
const program = await typeSpecCompile(
640+
`
634641
@service(#{
635642
title: "Azure Csharp emitter Testing",
636643
})
@@ -645,23 +652,85 @@ describe("Test Cookie Parameters", () => {
645652
646653
op test() : void;
647654
`,
648-
runner,
649-
{ IsNamespaceNeeded: false },
650-
);
651-
const context = createEmitterContext(program);
652-
const sdkContext = await createCSharpSdkContext(context);
653-
const diagnostics = context.program.diagnostics;
654-
createModel(sdkContext);
655+
runner,
656+
{ IsNamespaceNeeded: false },
657+
);
658+
const context = createEmitterContext(program);
659+
const sdkContext = await createCSharpSdkContext(context);
660+
const diagnostics = context.program.diagnostics;
661+
createModel(sdkContext);
655662

656-
const unsupportedCookie = diagnostics.find(
657-
(d) => d.code === "@typespec/http-client-csharp/unsupported-endpoint-url",
658-
);
659-
ok(unsupportedCookie);
660-
strictEqual(
661-
unsupportedCookie.message,
662-
"Unsupported server endpoint URL: https://{param1}{param2}/",
663-
);
664-
});
663+
const unsupportedCookie = diagnostics.find(
664+
(d) => d.code === "@typespec/http-client-csharp/unsupported-endpoint-url",
665+
);
666+
ok(unsupportedCookie);
667+
strictEqual(
668+
unsupportedCookie.message,
669+
"Unsupported server endpoint URL: https://{param1}{param2}/",
670+
);
671+
});
672+
it("String endpoint parameter has correct type", async () => {
673+
const program = await typeSpecCompile(
674+
`
675+
@service(#{
676+
title: "Azure Csharp emitter Testing",
677+
})
678+
@server(
679+
"https://{param1}",
680+
"Test endpoint",
681+
{
682+
param1: string,
683+
})
684+
namespace Test;
685+
686+
op test() : void;
687+
`,
688+
runner,
689+
{ IsNamespaceNeeded: false },
690+
);
691+
const context = createEmitterContext(program);
692+
const sdkContext = await createCSharpSdkContext(context);
693+
const codeModel = createModel(sdkContext);
694+
const client = codeModel.clients[0];
695+
ok(client);
696+
ok(client.parameters);
697+
const endpointParameter = client.parameters.find((p) => p.isEndpoint);
698+
ok(endpointParameter);
699+
strictEqual(endpointParameter.type.kind, "string");
700+
strictEqual(endpointParameter.type.crossLanguageDefinitionId, "TypeSpec.string");
701+
strictEqual(endpointParameter.serverUrlTemplate, "https://{param1}");
702+
});
703+
704+
it("URL endpoint parameter has correct type", async () => {
705+
const program = await typeSpecCompile(
706+
`
707+
@service(#{
708+
title: "Azure Csharp emitter Testing",
709+
})
710+
@server(
711+
"{param1}",
712+
"Test endpoint",
713+
{
714+
param1: url,
715+
})
716+
namespace Test;
717+
718+
op test() : void;
719+
`,
720+
runner,
721+
{ IsNamespaceNeeded: false },
722+
);
723+
const context = createEmitterContext(program);
724+
const sdkContext = await createCSharpSdkContext(context);
725+
const codeModel = createModel(sdkContext);
726+
const client = codeModel.clients[0];
727+
ok(client);
728+
ok(client.parameters);
729+
const endpointParameter = client.parameters.find((p) => p.isEndpoint);
730+
ok(endpointParameter);
731+
strictEqual(endpointParameter.type.kind, "url");
732+
strictEqual(endpointParameter.type.crossLanguageDefinitionId, "TypeSpec.url");
733+
strictEqual(endpointParameter.serverUrlTemplate, "{param1}");
665734
});
666735
});
667736

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientProvider.cs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ private IReadOnlyList<ParameterProvider> GetClientParameters()
255255
}
256256

257257
private Lazy<string?> _endpointParameterName;
258+
private InputParameter? _inputEndpointParam;
258259
internal string? EndpointParameterName => _endpointParameterName.Value;
259260

260261
private string? GetEndpointParameterName()
@@ -482,11 +483,22 @@ private MethodBodyStatement[] BuildPrimaryConstructorBody(IReadOnlyList<Paramete
482483
{
483484
return [MethodBodyStatement.Empty];
484485
}
485-
486+
AssignmentExpression endpointAssignment;
487+
if (_endpointParameter.Type.Equals(typeof(string)))
488+
{
489+
var serverTemplate = _inputEndpointParam!.ServerUrlTemplate;
490+
endpointAssignment = EndpointField.Assign(
491+
New.Instance(typeof(Uri),
492+
new FormattableStringExpression(serverTemplate!, [_endpointParameter])));
493+
}
494+
else
495+
{
496+
endpointAssignment = EndpointField.Assign(_endpointParameter);
497+
}
486498
List<MethodBodyStatement> body = [
487499
ClientOptionsParameter.Assign(ClientOptionsParameter.InitializationValue!, nullCoalesce: true).Terminate(),
488500
MethodBodyStatement.EmptyLine,
489-
EndpointField.Assign(_endpointParameter).Terminate()
501+
endpointAssignment.Terminate()
490502
];
491503

492504
// add other parameter assignments to their corresponding fields
@@ -643,14 +655,28 @@ protected override ScmMethodProvider[] BuildMethods()
643655

644656
private ParameterProvider BuildClientEndpointParameter()
645657
{
646-
var endpointParam = _inputClient.Parameters.FirstOrDefault(p => p.IsEndpoint);
647-
if (endpointParam == null)
658+
_inputEndpointParam = _inputClient.Parameters.FirstOrDefault(p => p.IsEndpoint);
659+
if (_inputEndpointParam == null)
660+
{
661+
return KnownParameters.Endpoint;
662+
}
663+
664+
var endpointParamType = ScmCodeModelGenerator.Instance.TypeFactory.CreateCSharpType(_inputEndpointParam.Type);
665+
if (endpointParamType == null)
666+
{
648667
return KnownParameters.Endpoint;
668+
}
649669

650-
ValueExpression? initializationValue = endpointParam.DefaultValue != null
651-
? New.Instance(KnownParameters.Endpoint.Type, Literal(endpointParam.DefaultValue.Value))
670+
ValueExpression? initializationValue = _inputEndpointParam.DefaultValue != null
671+
? New.Instance(endpointParamType, Literal(_inputEndpointParam.DefaultValue.Value))
652672
: null;
653673

674+
if (endpointParamType.Equals(typeof(string)) && _inputEndpointParam.ServerUrlTemplate != null)
675+
{
676+
return new(_inputEndpointParam);
677+
}
678+
679+
// Must be a URI endpoint parameter
654680
return new(
655681
KnownParameters.Endpoint.Name,
656682
KnownParameters.Endpoint.Description,

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
using Microsoft.TypeSpec.Generator.Input;
1313
using Microsoft.TypeSpec.Generator.Primitives;
1414
using Microsoft.TypeSpec.Generator.Providers;
15-
using Microsoft.TypeSpec.Generator.Snippets;
1615
using Microsoft.TypeSpec.Generator.Statements;
1716
using Microsoft.TypeSpec.Generator.Tests.Common;
1817
using NUnit.Framework;
@@ -960,6 +959,46 @@ public void XmlDocsAreWritten()
960959
Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
961960
}
962961

962+
[Test]
963+
public void EndpointFieldAssignedFromUriParameter()
964+
{
965+
MockHelpers.LoadMockGenerator();
966+
var client = InputFactory.Client(
967+
TestClientName,
968+
parameters: [InputFactory.Parameter(
969+
"endpoint",
970+
InputPrimitiveType.Url,
971+
isRequired: true,
972+
kind: InputParameterKind.Client,
973+
isEndpoint: true)]);
974+
var clientProvider = new ClientProvider(client);
975+
var constructor = clientProvider.Constructors.FirstOrDefault(
976+
c => c.Signature.Initializer == null && c.Signature?.Modifiers == MethodSignatureModifiers.Public);
977+
978+
StringAssert.Contains("_endpoint = endpoint;", constructor?.BodyStatements?.ToDisplayString());
979+
}
980+
981+
[TestCase("{endpoint}", "endpoint")]
982+
[TestCase("https://{hostName}", "hostName")]
983+
public void EndpointFieldAssignedFromStringParameter(string serverTemplate, string parameterName)
984+
{
985+
MockHelpers.LoadMockGenerator();
986+
var client = InputFactory.Client(
987+
TestClientName,
988+
parameters: [InputFactory.Parameter(
989+
parameterName,
990+
InputPrimitiveType.String,
991+
isRequired: true,
992+
kind: InputParameterKind.Client,
993+
serverUrlTemplate: serverTemplate,
994+
isEndpoint: true)]);
995+
var clientProvider = new ClientProvider(client);
996+
var constructor = clientProvider.Constructors.FirstOrDefault(
997+
c => c.Signature.Initializer == null && c.Signature?.Modifiers == MethodSignatureModifiers.Public);
998+
999+
StringAssert.Contains($"_endpoint = new global::System.Uri($\"{serverTemplate}\");", constructor?.BodyStatements?.ToDisplayString());
1000+
}
1001+
9631002
private static InputClient GetEnumQueryParamClient()
9641003
{
9651004
return InputFactory.Client(

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputParameter.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ public InputParameter(
2525
bool skipUrlEncoding,
2626
bool explode,
2727
string? arraySerializationDelimiter,
28-
string? headerCollectionPrefix)
28+
string? headerCollectionPrefix,
29+
string? serverUrlTemplate)
2930
{
3031
Name = name;
3132
NameInRequest = nameInRequest;
@@ -43,6 +44,7 @@ public InputParameter(
4344
Explode = explode;
4445
ArraySerializationDelimiter = arraySerializationDelimiter;
4546
HeaderCollectionPrefix = headerCollectionPrefix;
47+
ServerUrlTemplate = serverUrlTemplate;
4648
}
4749

4850
public string Name { get; }
@@ -62,6 +64,7 @@ public InputParameter(
6264
public string? ArraySerializationDelimiter { get; }
6365
public string? HeaderCollectionPrefix { get; }
6466
public IReadOnlyList<InputDecoratorInfo> Decorators { get; internal set; } = new List<InputDecoratorInfo>();
67+
public string? ServerUrlTemplate { get; }
6568

6669
/// <summary>
6770
/// Update the instance with given parameters.

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputParameterConverter.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public static InputParameter CreateInputParameter(ref Utf8JsonReader reader, str
4444
bool explode = false;
4545
string? arraySerializationDelimiter = null;
4646
string? headerCollectionPrefix = null;
47+
string? serverUrlTemplate = null;
4748
IReadOnlyList<InputDecoratorInfo>? decorators = null;
4849
while (reader.TokenType != JsonTokenType.EndObject)
4950
{
@@ -64,6 +65,7 @@ public static InputParameter CreateInputParameter(ref Utf8JsonReader reader, str
6465
|| reader.TryReadBoolean("explode", ref explode)
6566
|| reader.TryReadString("arraySerializationDelimiter", ref arraySerializationDelimiter)
6667
|| reader.TryReadString("headerCollectionPrefix", ref headerCollectionPrefix)
68+
|| reader.TryReadString("serverUrlTemplate", ref serverUrlTemplate)
6769
|| reader.TryReadComplexType("decorators", options, ref decorators);
6870

6971
if (!isKnownProperty)
@@ -109,7 +111,8 @@ public static InputParameter CreateInputParameter(ref Utf8JsonReader reader, str
109111
skipUrlEncoding: skipUrlEncoding,
110112
explode: explode,
111113
arraySerializationDelimiter: arraySerializationDelimiter,
112-
headerCollectionPrefix: headerCollectionPrefix)
114+
headerCollectionPrefix: headerCollectionPrefix,
115+
serverUrlTemplate: serverUrlTemplate)
113116
{
114117
Decorators = decorators ?? []
115118
};

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/common/InputFactory.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ public static InputParameter Parameter(
7878
bool isContentType = false,
7979
bool isApiVersion = false,
8080
bool explode = false,
81-
string? delimiter = null)
81+
string? delimiter = null,
82+
string? serverUrlTemplate = null)
8283
{
8384
return new InputParameter(
8485
name,
@@ -96,7 +97,8 @@ public static InputParameter Parameter(
9697
false,
9798
explode,
9899
delimiter,
99-
null);
100+
null,
101+
serverUrlTemplate);
100102
}
101103

102104
public static InputNamespace Namespace(

packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/tspCodeModel.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6807,7 +6807,7 @@
68076807
"type": {
68086808
"$id": "660",
68096809
"kind": "url",
6810-
"name": "url",
6810+
"name": "endpoint",
68116811
"crossLanguageDefinitionId": "TypeSpec.url"
68126812
},
68136813
"location": "Uri",
@@ -6817,7 +6817,8 @@
68176817
"isEndpoint": true,
68186818
"skipUrlEncoding": false,
68196819
"explode": false,
6820-
"kind": "Client"
6820+
"kind": "Client",
6821+
"serverUrlTemplate": "{sampleTypeSpecUrl}"
68216822
}
68226823
],
68236824
"decorators": [],

0 commit comments

Comments
 (0)