-
Notifications
You must be signed in to change notification settings - Fork 299
Description
What are you generating using Kiota, clients or plugins?
API Client/SDK
In what context or format are you using Kiota?
Nuget tool
Client library/SDK language
Csharp
Describe the bug
Consider this case:
GET /api/resource(no required query params)DELETE /api/resource?confirm={confirm}(confirm is required)
Currently generates: {+baseurl}/api/resource?confirm={confirm} making confirm appear required for GET when it's not.
Expected behavior
Provide per operations uri template overrides
How to reproduce
Generate a client for this OpenAPI spec and see that it requires a confirm parameter in the uri template used for the GET operation.
Open API description file
openapi: 3.0.1
info:
title: URI Template Test API
description: Minimal spec to demonstrate per-operation URI template requirements
version: 1.0.0
servers:
- url: https://api.example.com/v1
paths:
/resources/{id}:
parameters:
- name: id
in: path
required: true
description: Resource identifier
schema:
type: string
get:
summary: Get a resource
description: Retrieves a resource by ID (no query parameters required)
operationId: getResource
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/Resource'
'404':
description: Resource not found
delete:
summary: Delete a resource
description: Deletes a resource by ID (requires confirmation parameter)
operationId: deleteResource
parameters:
- name: confirm
in: query
required: true
description: Confirmation token - must be set to 'yes' to confirm deletion
schema:
type: string
enum:
- 'yes'
responses:
'204':
description: Resource successfully deleted
'400':
description: Missing or invalid confirmation parameter
'404':
description: Resource not found
components:
schemas:
Resource:
type: object
required:
- id
- name
properties:
id:
type: string
description: Unique identifier
name:
type: string
description: Resource name
createdAt:
type: string
format: date-time
description: Creation timestampKiota Version
1.29.0+c21ebceb977bc33def3d8a9e5237b798a7b962b6
Latest Kiota version known to work for scenario above?(Not required)
No response
Known Workarounds
No response
Configuration
No response
Debug output
Click to expand log
<log output here>
Other information
Plan: Per-Operation URI Template Support in Kiota
Executive Summary
This plan addresses the limitation where each request builder class uses a single URI template shared across all HTTP operations (GET, POST, DELETE, etc.), creating issues when operations on the same path have different required URL parameters.
Problem Example:
GET /api/resource(no required query params)DELETE /api/resource?confirm={confirm}(confirm is required)
Currently generates: {+baseurl}/api/resource?confirm={confirm} making confirm appear required for GET when it's not.
🎯 Key Findings
Excellent News: The infrastructure for per-operation URI templates already exists in Kiota!
- ✅
CodeMethod.UrlTemplateOverrideproperty is implemented - ✅ 8 out of 9 language generators already support it (C#, Java, TypeScript, Python, PHP, Go, Ruby, Dart)
- ✅ Test coverage exists for most languages
What needs to be done:
- Phase 1: Generate operation-specific templates and set
UrlTemplateOverrideinKiotaBuilder.cs - Phase 2: Keep current class-level template strategy
- Phase 3: Add HTTP writer support + testing
Implementation Strategy
Recommended Approach (Phases 1-3):
- Non-breaking changes
- Leverages existing infrastructure
- Works across all languages immediately
- Backward compatible
Visual Overview
Current State (Problem):
┌────────────────────────────────────┐
│ ItemRequestBuilder Class │
│ │
│ urlTemplate (shared by all): │
│ "{+baseurl}/items?confirm={...}" │ ← Merged from ALL operations
│ │
│ ├─ GET() → Uses urlTemplate │ ❌ Incorrectly includes 'confirm'
│ ├─ POST() → Uses urlTemplate │ ❌ Incorrectly includes 'confirm'
│ └─ DELETE() → Uses urlTemplate │ ✅ Correctly includes 'confirm'
└────────────────────────────────────┘
Proposed Solution (Phase 1-3):
┌────────────────────────────────────┐
│ ItemRequestBuilder Class │
│ │
│ urlTemplate (base): │
│ "{+baseurl}/items{?confirm}" │ ← All params optional by default
│ │
│ ├─ GET() → Uses urlTemplate │ ✅ confirm is optional
│ ├─ POST() → Uses urlTemplate │ ✅ confirm is optional
│ └─ DELETE() │
│ ├─ UrlTemplateOverride = │ ✅ confirm is REQUIRED
│ │ "{+baseurl}/items? │
│ │ confirm={confirm}" │
│ └─ Uses override │
└────────────────────────────────────┘
Code Generation Impact:
┌─────────────────────────────────────────────────┐
│ C#, Java, Python, PHP, Go, Ruby, Dart: │
│ ✅ Already support UrlTemplateOverride │
│ → No changes needed to language generators │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ TypeScript: │
│ ✅ Already supports via RequestsMetadata |
│ → No changes needed │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ HTTP Writer: │
│ ⚠️ Needs update to check UrlTemplateOverride │
└─────────────────────────────────────────────────┘
Current Implementation Analysis
URI Template Storage
Location: KiotaBuilder.cs:878
private void CreateUrlManagement(CodeClass currentClass, OpenApiUrlTreeNode currentNode, bool isApiClientClass)
{
var pathProperty = new CodeProperty
{
Access = AccessModifier.Private,
Name = "urlTemplate",
DefaultValue = $"\"{currentNode.GetUrlTemplate()}\"", // ❌ No operationType passed!
ReadOnly = true,
Kind = CodePropertyKind.UrlTemplate,
Type = new CodeType { Name = "string", IsNullable = false, IsExternal = true },
};
currentClass.AddProperty(pathProperty);
}Issue: GetUrlTemplate() is called WITHOUT an operationType parameter, causing it to merge parameters from ALL operations.
Existing Infrastructure (Good News!)
CodeMethod already supports per-operation templates:
public class CodeMethod : CodeTerminalWithKind<CodeMethodKind>
{
public string UrlTemplateOverride { get; set; } = string.Empty;
public bool HasUrlTemplateOverride => !string.IsNullOrEmpty(UrlTemplateOverride);
}Code writers already support it:
CSharp/CodeMethodWriter.cs:431
var urlTemplateValue = codeElement.HasUrlTemplateOverride
? $"\"{codeElement.UrlTemplateOverride}\"" // ✅ Already implemented!
: GetPropertyCall(urlTemplateProperty, "string.Empty");GetUrlTemplate already accepts operationType:
OpenApiUrlTreeNodeExtensions.cs:220
public static string GetUrlTemplate(
this OpenApiUrlTreeNode currentNode,
HttpMethod? operationType = null, // ✅ Parameter exists!
bool includeQueryParameters = true,
bool includeBaseUrl = true)
{
// Line 229: Calls GetParameters with operationType
var parameters = pathItem.GetParameters(operationType);
// ...
}Proposed Solution
Architecture Overview
Enable each HTTP operation method to have its own URI template while maintaining backward compatibility by leveraging the existing UrlTemplateOverride infrastructure.
Implementation Phases
Phase 1: Generate Operation-Specific URI Templates ⭐ (Core Fix)
Priority: HIGH - This solves the immediate problem
File: KiotaBuilder.cs (around line 1393-1414)
Current code (where CodeMethod is created):
var executorMethod = new CodeMethod
{
Name = operationType.Method.ToLowerInvariant().ToFirstCharacterUpperCase(),
Kind = CodeMethodKind.RequestExecutor,
HttpMethod = method,
Parent = parentClass,
Documentation = new() { DescriptionTemplate = ... },
ReturnType = returnTypes.Item1,
Deprecation = deprecationInformation,
};Proposed change:
var executorMethod = new CodeMethod
{
Name = operationType.Method.ToLowerInvariant().ToFirstCharacterUpperCase(),
Kind = CodeMethodKind.RequestExecutor,
HttpMethod = method,
Parent = parentClass,
// ✨ NEW: Generate operation-specific URI template
UrlTemplateOverride = GenerateOperationSpecificUrlTemplate(currentNode, method),
Documentation = new() { DescriptionTemplate = ... },
ReturnType = returnTypes.Item1,
Deprecation = deprecationInformation,
};New helper method to add:
/// <summary>
/// Generates an operation-specific URL template if it differs from the class-level template.
/// Returns empty string if the operation can use the class-level template.
/// </summary>
private static string GenerateOperationSpecificUrlTemplate(
OpenApiUrlTreeNode node,
DomHttpMethod httpMethod)
{
// Get template for THIS specific operation only
var operationTemplate = node.GetUrlTemplate(
operationType: httpMethod,
includeQueryParameters: true,
includeBaseUrl: true);
// Get the generic template (all operations merged)
var genericTemplate = node.GetUrlTemplate(
operationType: null,
includeQueryParameters: true,
includeBaseUrl: true);
// Only override if operation-specific differs from generic
return operationTemplate != genericTemplate
? operationTemplate
: string.Empty; // Empty = use class-level template
}Impact:
- ✅ Minimal code changes
- ✅ Leverages existing infrastructure
- ✅ Backward compatible
⚠️ Slightly increases generated code size (only for operations that differ)
Phase 2: Refine Generic URI Template Strategy
Priority: MEDIUM - Improves developer experience
File: KiotaBuilder.cs:878
Decision Point: How should the class-level URI template be generated?
Option A: Conservative (Recommended) ✅
Keep current behavior - merge ALL operation parameters, making operation-specific params appear optional at the class level.
DefaultValue = $"\"{currentNode.GetUrlTemplate()}\"", // Keep current behavior
// Operations with stricter requirements use UrlTemplateOverridePros:
- ✅ Zero breaking changes
- ✅ Simple implementation
- ✅ Backward compatible
Cons:
⚠️ Class-level template is "loosest" (includes all params as optional)
Result:
- Class template:
{+baseurl}/api/resource{?confirm,search,filter} - DELETE override:
{+baseurl}/api/resource?confirm={confirm} - GET uses class template (all params optional)
Option B: Aggressive
Use intersection of parameters (only params required by ALL operations).
DefaultValue = $"\"{GenerateIntersectionUrlTemplate(currentNode)}\"",New helper method:
private static string GenerateIntersectionUrlTemplate(OpenApiUrlTreeNode node)
{
var operations = node.PathItems[Constants.DefaultOpenApiLabel].Operations;
if (operations == null || operations.Count <= 1)
return node.GetUrlTemplate(); // Single operation, use as-is
// Find parameters required by ALL operations
var allOperationParams = operations.Values
.Select(op => op.Parameters?
.Where(p => p.Required && p.In == ParameterLocation.Query)
.Select(p => p.Name)
.ToHashSet(StringComparer.OrdinalIgnoreCase)
?? new HashSet<string>(StringComparer.OrdinalIgnoreCase))
.ToList();
if (allOperationParams.Count == 0)
return node.GetUrlTemplate(includeQueryParameters: false);
var commonRequiredParams = allOperationParams
.Aggregate((a, b) => { a.IntersectWith(b); return a; });
// Build template with only common required params
// Would need to call GetUrlTemplate with filtered parameter list
// Implementation details depend on modifying GetUrlTemplate or post-processing
}Pros:
- ✅ Cleaner templates (minimal parameters)
- ✅ More accurate at class level
Cons:
⚠️ Potentially breaking (operations might not have overrides when they should)⚠️ More complex logic⚠️ Harder to maintain
Recommendation: Use Option A for safety and backward compatibility.
Phase 3: Verify Language Generators ✅ VERIFIED
Priority: HIGH - Required for multi-language support
Status: ✅ ALL LANGUAGES ALREADY SUPPORT UrlTemplateOverride
Investigation Results: All language generators already implement UrlTemplateOverride support! This is excellent news - Phase 1 implementation will work across all languages immediately.
Detailed Language Support Matrix
| Language | Status | File Location | Line | Implementation Pattern |
|---|---|---|---|---|
| C# | ✅ READY | CSharp/CodeMethodWriter.cs | 431 | HasUrlTemplateOverride ternary |
| Java | ✅ READY | Java/CodeMethodWriter.cs | 589 | HasUrlTemplateOverride ternary |
| TypeScript | ✅ READY | TypeScript/CodeConstantWriter.cs | 109 | HasUrlTemplateOverride ternary |
| Python | ✅ READY | Python/CodeMethodWriter.cs | 633 | HasUrlTemplateOverride ternary |
| PHP | ✅ READY | Php/CodeMethodWriter.cs | 580 | HasUrlTemplateOverride ternary |
| Go | ✅ READY | Go/CodeMethodWriter.cs | 899 | HasUrlTemplateOverride ternary |
| Ruby | ✅ READY | Ruby/CodeMethodWriter.cs | 320 | HasUrlTemplateOverride ternary |
| Dart | ✅ READY | Dart/CodeMethodWriter.cs | 510 | HasUrlTemplateOverride ternary |
| HTTP | HTTP/CodeClassDeclarationWriter.cs | 239 | Uses class-level template only |
Implementation Details by Language
1. C# (Reference Implementation)
File: CSharp/CodeMethodWriter.cs:426-432
private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams requestParams, CodeClass currentClass, LanguageWriter writer)
{
if (codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null");
if (currentClass.GetPropertyOfKind(CodePropertyKind.PathParameters) is not CodeProperty urlTemplateParamsProperty)
throw new InvalidOperationException("path parameters property cannot be null");
if (currentClass.GetPropertyOfKind(CodePropertyKind.UrlTemplate) is not CodeProperty urlTemplateProperty)
throw new InvalidOperationException("url template property cannot be null");
var operationName = codeElement.HttpMethod.ToString();
var urlTemplateValue = codeElement.HasUrlTemplateOverride
? $"\"{codeElement.UrlTemplateOverride}\""
: GetPropertyCall(urlTemplateProperty, "string.Empty");
writer.WriteLine($"var {RequestInfoVarName} = new RequestInformation(Method.{operationName?.ToUpperInvariant()}, {urlTemplateValue}, {GetPropertyCall(urlTemplateParamsProperty, "string.Empty")});");
// ... rest of method
}Generated Code Example:
// With override
var requestInfo = new RequestInformation(Method.DELETE, "{+baseurl}/api/resource?confirm={confirm}", PathParameters);
// Without override (uses class property)
var requestInfo = new RequestInformation(Method.GET, urlTemplate, PathParameters);Test Coverage: ✅ CodeMethodWriterTests.cs:1127-1140
[Fact]
public void WritesRequestGeneratorBodyWhenUrlTemplateIsOverrode()
{
setup();
method.Kind = CodeMethodKind.RequestGenerator;
method.HttpMethod = HttpMethod.Get;
AddRequestProperties();
AddRequestBodyParameters(true);
method.AcceptedResponseTypes.Add("application/json");
method.UrlTemplateOverride = "{baseurl+}/foo/bar";
writer.Write(method);
var result = tw.ToString();
Assert.Contains("var requestInfo = new RequestInformation(Method.GET, \"{baseurl+}/foo/bar\", PathParameters)", result);
}2. Java
File: Java/CodeMethodWriter.cs:584-590
private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams requestParams, CodeClass currentClass, LanguageWriter writer)
{
if (codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null");
if (currentClass.GetPropertyOfKind(CodePropertyKind.PathParameters) is not CodeProperty urlTemplateParamsProperty)
throw new InvalidOperationException("url template params property cannot be null");
if (currentClass.GetPropertyOfKind(CodePropertyKind.UrlTemplate) is not CodeProperty urlTemplateProperty)
throw new InvalidOperationException("url template property cannot be null");
var urlTemplateValue = codeElement.HasUrlTemplateOverride
? $"\"{codeElement.UrlTemplateOverride}\""
: GetPropertyCall(urlTemplateProperty, "\"\"");
writer.WriteLine($"final RequestInformation {RequestInfoVarName} = new RequestInformation(HttpMethod.{codeElement.HttpMethod.ToString()?.ToUpperInvariant()}, {urlTemplateValue}, {GetPropertyCall(urlTemplateParamsProperty, "null")});");
// ... rest of method
}Generated Code Example:
// With override
final RequestInformation requestInfo = new RequestInformation(HttpMethod.DELETE, "{+baseurl}/api/resource?confirm={confirm}", pathParameters);
// Without override
final RequestInformation requestInfo = new RequestInformation(HttpMethod.GET, this.urlTemplate, pathParameters);Test Coverage: ✅ Has test in Java/CodeMethodWriterTests.cs
3. TypeScript (Unique Pattern - Uses Metadata Constants)
File: TypeScript/CodeConstantWriter.cs:95-110
TypeScript uses a different architecture - it generates RequestsMetadata constants instead of request generator methods.
private void WriteRequestsMetadata(CodeConstant codeElement, LanguageWriter writer)
{
var uriTemplateConstant = codeElement.Parent is CodeFile parentFile &&
parentFile.Constants.FirstOrDefault(static x => x.Kind is CodeConstantKind.UriTemplate) is CodeConstant tplct ?
tplct : throw new InvalidOperationException("Couldn't find the associated uri template constant");
writer.StartBlock($"export const {codeElement.Name.ToFirstCharacterUpperCase()}: RequestsMetadata = {{");
foreach (var executorMethod in executorMethods)
{
writer.StartBlock($"{executorMethod.Name.ToFirstCharacterLowerCase()}: {{");
var urlTemplateValue = executorMethod.HasUrlTemplateOverride
? $"\"{executorMethod.UrlTemplateOverride}\""
: uriTemplateConstant.Name.ToFirstCharacterUpperCase();
writer.WriteLine($"uriTemplate: {urlTemplateValue},");
// ... rest of metadata
}
}Generated Code Example:
export const ItemRequestBuilderRequestsMetadata: RequestsMetadata = {
delete: {
uriTemplate: "{+baseurl}/api/resource?confirm={confirm}", // Override
adapterMethodName: "send",
},
get: {
uriTemplate: ItemUriTemplate, // Uses constant
adapterMethodName: "send",
},
};Test Coverage: UrlTemplateOverride test found in TypeScript tests (but infrastructure exists)
4. Python
File: Python/CodeMethodWriter.cs:628-634
private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams requestParams, CodeClass currentClass, LanguageWriter writer)
{
if (codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null");
if (currentClass.GetPropertyOfKind(CodePropertyKind.PathParameters) is not CodeProperty urlTemplateParamsProperty)
throw new InvalidOperationException("path parameters cannot be null");
if (currentClass.GetPropertyOfKind(CodePropertyKind.UrlTemplate) is not CodeProperty urlTemplateProperty)
throw new InvalidOperationException("url template cannot be null");
var urlTemplateValue = codeElement.HasUrlTemplateOverride
? $"'{codeElement.UrlTemplateOverride}'"
: GetPropertyCall(urlTemplateProperty, "''");
writer.WriteLine($"{RequestInfoVarName} = RequestInformation(Method.{codeElement.HttpMethod.Value.ToString().ToUpperInvariant()}, {urlTemplateValue}, {GetPropertyCall(urlTemplateParamsProperty, "''")})");
// ... rest of method
}Generated Code Example:
# With override
request_info = RequestInformation(Method.DELETE, '{+baseurl}/api/resource?confirm={confirm}', self.path_parameters)
# Without override
request_info = RequestInformation(Method.GET, self.url_template, self.path_parameters)Test Coverage: ✅ Has test in Python/CodeMethodWriterTests.cs
5. PHP
File: Php/CodeMethodWriter.cs:575-582
private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams requestParams, CodeClass currentClass, LanguageWriter writer)
{
var requestInformationClass = "RequestInformation";
writer.WriteLine($"{RequestInfoVarName} = new {requestInformationClass}();");
if (currentClass.GetPropertyOfKind(CodePropertyKind.PathParameters) is CodeProperty pathParametersProperty &&
currentClass.GetPropertyOfKind(CodePropertyKind.UrlTemplate) is CodeProperty urlTemplateProperty)
{
var urlTemplateValue = codeElement.HasUrlTemplateOverride
? $"'{codeElement.UrlTemplateOverride.SanitizeSingleQuote()}'"
: GetPropertyCall(urlTemplateProperty, "''");
writer.WriteLines($"{RequestInfoVarName}->urlTemplate = {urlTemplateValue};",
$"{RequestInfoVarName}->pathParameters = {GetPropertyCall(pathParametersProperty, "''")};");
}
writer.WriteLine($"{RequestInfoVarName}->httpMethod = HttpMethod::{codeElement.HttpMethod.Value.ToString().ToUpperInvariant()};");
// ... rest of method
}Generated Code Example:
// With override
$requestInfo = new RequestInformation();
$requestInfo->urlTemplate = '{+baseurl}/api/resource?confirm={confirm}';
$requestInfo->pathParameters = $this->pathParameters;
$requestInfo->httpMethod = HttpMethod::DELETE;
// Without override
$requestInfo = new RequestInformation();
$requestInfo->urlTemplate = $this->urlTemplate;
$requestInfo->pathParameters = $this->pathParameters;
$requestInfo->httpMethod = HttpMethod::GET;Test Coverage: ✅ Has test in Php/CodeMethodWriterTests.cs
6. Go
File: Go/CodeMethodWriter.cs:894-900
private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams requestParams, CodeClass parentClass, LanguageWriter writer)
{
if (parentClass.GetPropertyOfKind(CodePropertyKind.UrlTemplate) is not CodeProperty urlTemplateProperty)
throw new InvalidOperationException("url template property cannot be null");
if (parentClass.GetPropertyOfKind(CodePropertyKind.PathParameters) is not CodeProperty urlTemplateParamsProperty)
throw new InvalidOperationException("url template params property cannot be null");
var requestAdapterPropertyName = BaseRequestBuilderVarName + "." +
parentClass.GetPropertyOfKind(CodePropertyKind.RequestAdapter)?.Name.ToFirstCharacterUpperCase();
var contextParameterName = codeElement.Parameters.OfKind(CodeParameterKind.Cancellation)?.Name.ToFirstCharacterLowerCase();
var urlTemplateValue = codeElement.HasUrlTemplateOverride
? $"\"{codeElement.UrlTemplateOverride}\""
: GetPropertyCall(urlTemplateProperty, "\"\"");
writer.WriteLine($"{RequestInfoVarName} := {conventions.AbstractionsHash}.NewRequestInformationWithMethodAndUrlTemplateAndPathParameters({conventions.AbstractionsHash}.{codeElement.HttpMethod.Value.ToString().ToUpperInvariant()}, {urlTemplateValue}, {GetPropertyCall(urlTemplateParamsProperty, "\"\"")})");
// ... rest of method
}Generated Code Example:
// With override
requestInfo := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewRequestInformationWithMethodAndUrlTemplateAndPathParameters(
i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.DELETE,
"{+baseurl}/api/resource?confirm={confirm}",
m.BaseRequestBuilder.PathParameters)
// Without override
requestInfo := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewRequestInformationWithMethodAndUrlTemplateAndPathParameters(
i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.GET,
m.BaseRequestBuilder.urlTemplate,
m.BaseRequestBuilder.PathParameters)Test Coverage: ✅ Has test in Go/CodeMethodWriterTests.cs
7. Ruby
File: Ruby/CodeMethodWriter.cs:315-323
private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams requestParams, CodeClass parentClass, LanguageWriter writer)
{
writer.WriteLine("request_info = MicrosoftKiotaAbstractions::RequestInformation.new()");
if (parentClass.GetPropertyOfKind(CodePropertyKind.PathParameters) is CodeProperty urlTemplateParamsProperty &&
parentClass.GetPropertyOfKind(CodePropertyKind.UrlTemplate) is CodeProperty urlTemplateProperty)
{
var urlTemplateValue = codeElement.HasUrlTemplateOverride
? $"'{codeElement.UrlTemplateOverride}'"
: GetPropertyCall(urlTemplateProperty, "''");
writer.WriteLines($"request_info.url_template = {urlTemplateValue}",
$"request_info.path_parameters = {GetPropertyCall(urlTemplateParamsProperty, "''")}");
}
writer.WriteLine($"request_info.http_method = :{codeElement.HttpMethod.Value.ToString().ToUpperInvariant()}");
// ... rest of method
}Generated Code Example:
# With override
request_info = MicrosoftKiotaAbstractions::RequestInformation.new()
request_info.url_template = '{+baseurl}/api/resource?confirm={confirm}'
request_info.path_parameters = @path_parameters
request_info.http_method = :DELETE
# Without override
request_info = MicrosoftKiotaAbstractions::RequestInformation.new()
request_info.url_template = @url_template
request_info.path_parameters = @path_parameters
request_info.http_method = :GETTest Coverage: ✅ Has test in Ruby/CodeMethodWriterTests.cs
8. Dart
File: Dart/CodeMethodWriter.cs:505-511
private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams requestParams, CodeClass currentClass, LanguageWriter writer)
{
if (codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null");
if (currentClass.GetPropertyOfKind(CodePropertyKind.PathParameters) is not CodeProperty urlTemplateParamsProperty)
throw new InvalidOperationException("path parameters property cannot be null");
if (currentClass.GetPropertyOfKind(CodePropertyKind.UrlTemplate) is not CodeProperty urlTemplateProperty)
throw new InvalidOperationException("url template property cannot be null");
var operationName = codeElement.HttpMethod.ToString();
var urlTemplateValue = codeElement.HasUrlTemplateOverride
? $"'{codeElement.UrlTemplateOverride}'"
: GetPropertyCall(urlTemplateProperty, "''");
writer.WriteLine($"var {RequestInfoVarName} = RequestInformation(httpMethod : HttpMethod.{operationName?.ToLowerInvariant()}, {urlTemplateProperty.Name} : {urlTemplateValue}, {urlTemplateParamsProperty.Name} : {GetPropertyCall(urlTemplateParamsProperty, "string.Empty")});");
// ... rest of method
}Generated Code Example:
// With override
var requestInfo = RequestInformation(
httpMethod : HttpMethod.delete,
urlTemplate : '{+baseurl}/api/resource?confirm={confirm}',
pathParameters : pathParameters);
// Without override
var requestInfo = RequestInformation(
httpMethod : HttpMethod.get,
urlTemplate : urlTemplate,
pathParameters : pathParameters);Test Coverage: ✅ Has test in Dart/CodeMethodWriterTests.cs
9. HTTP Writer (Special Case)
File: HTTP/CodeClassDeclarationWriter.cs:225-244
Status: UrlTemplateOverride - Always uses class-level template
private static void WriteHttpMethods(
CodeClass requestBuilderClass,
LanguageWriter writer,
CodeProperty[] queryParameters,
CodeProperty[] pathParameters,
CodeProperty urlTemplateProperty,
CodeMethod method,
string? baseUrl)
{
writer.WriteLine($"# {method.Documentation.DescriptionTemplate}");
// ⚠️ Always uses class-level urlTemplateProperty.DefaultValue
var url = BuildUrlStringFromTemplate(
urlTemplateProperty.DefaultValue, // No override check!
queryParameters,
pathParameters,
baseUrl
);
writer.WriteLine($"{method.Name.ToUpperInvariant()} {url} {Constants.HttpVersion}");
// ... rest of method
}Impact: HTTP output format (for REST client tools) will NOT benefit from per-operation templates unless we add support.
Recommendation: Add UrlTemplateOverride support to HTTP writer in Phase 3:
var urlTemplateString = method.HasUrlTemplateOverride
? method.UrlTemplateOverride
: urlTemplateProperty.DefaultValue;
var url = BuildUrlStringFromTemplate(
urlTemplateString,
queryParameters,
pathParameters,
baseUrl
);Summary & Action Items
Excellent News: ✅ 8 out of 9 language generators already support UrlTemplateOverride!
Test Coverage Status:
- ✅ C# - Has explicit
UrlTemplateOverridetest - ✅ Java, Python, PHP, Go, Ruby, Dart - Have tests (verified in test files)
⚠️ TypeScript - Infrastructure exists but no explicit override test found- ❌ HTTP - Does not support overrides
Action Items for Phase 3:
-
Add HTTP Writer Support (Required)
- File: HTTP/CodeClassDeclarationWriter.cs:239
- Add
UrlTemplateOverridecheck before callingBuildUrlStringFromTemplate
-
Add TypeScript Test (Optional but recommended)
- File: TypeScript/CodeConstantWriterTests.cs
- Add test similar to C# test for
UrlTemplateOverridein metadata constants
-
Integration Tests (Required)
- Create OpenAPI spec with mixed required parameters (e.g., DELETE with required query param, GET without)
- Generate code for all languages
- Verify correct
UrlTemplateOverridevalues in generated code
Impact Analysis
Pros
- ✅ Type safety: Operations only use parameters they need
- ✅ Better IntelliSense: Developers see relevant parameters only
- ✅ Correctness: Required parameters enforced per operation
- ✅ Backward compatible: No breaking changes
- ✅ Leverages existing infrastructure: Minimal changes needed
Cons
⚠️ Larger generated code: Each unique operation gets its own template string⚠️ Complexity: More generation logic to maintain⚠️ Testing burden: Verify across all language generators
Tradeoffs & Recommendations
| Aspect | Conservative Approach (Recommended) | Aggressive Approach |
|---|---|---|
| Class URI Template | Keep current (merged) ✅ | Use intersection only |
| Code size | Minimal increase | Potentially larger |
| Type safety | Good | Requires additional work |
| Breaking changes | None ✅ | Possible |
Recommended Implementation Order
- Phase 1 (Core fix) - Generate operation-specific URI templates
- Phase 2 (Keep current template strategy) - Use conservative approach
- Phase 3 (Language support verification) - Add HTTP writer support and testing
Testing Strategy
Unit Tests
- ✅ Verify
GetUrlTemplate(operationType)returns correct templates per operation - ✅ Verify
GenerateOperationSpecificUrlTemplate()only returns override when needed - ✅ Test with various parameter combinations:
- No parameters
- Shared parameters across operations
- Operation-specific required parameters
- Mix of required and optional parameters
Integration Tests
- Generate code from OpenAPI specs with mixed required params
- Verify generated request builder classes have correct:
- Class-level URI template
- Operation-specific overrides (when applicable)
- Test actual HTTP requests are built correctly
Language-Specific Tests
- ✅ Verify all language generators produce syntactically valid code
- ✅ Verify URI templates are correctly used at runtime
- ✅ Test with actual API calls if possible
Regression Tests
- ✅ Ensure existing APIs without parameter conflicts continue to work
- ✅ Verify no changes in generated code for simple cases
- ✅ Performance testing (generation time shouldn't increase significantly)
Migration Path for Consumers
Phases 1-3 (Non-Breaking)
No action required from consumers. Regenerated clients will automatically benefit from more accurate URI templates.
Example change:
// Before (incorrect)
await client.Items[id].DeleteAsync(config => {
// Confirm parameter wasn't obviously required
});
// After (correct - IDE shows required parameter in URI)
await client.Items[id].DeleteAsync(config => {
config.QueryParameters.Confirm = "yes"; // Now clearly required
});Phase 4 (Breaking if enabled)
If opt-in flag is used, consumers need to update query parameter references:
// Before
var config = new ItemRequestBuilderRequestConfiguration {
QueryParameters = new ItemRequestBuilderQueryParameters {
Search = "test"
}
};
// After (with --operation-specific-parameters flag)
var config = new ItemRequestBuilderGetRequestConfiguration {
QueryParameters = new ItemRequestBuilderGetQueryParameters {
Search = "test"
}
};Files Requiring Changes
Core Logic (Phase 1 & 2)
- KiotaBuilder.cs:878 - URI template generation
- KiotaBuilder.cs:1393-1414 - Method creation with override
- OpenApiUrlTreeNodeExtensions.cs:220-256 - Already supports operationType ✅
Code Writers (Phase 3)
- HTTP/CodeClassDeclarationWriter.cs:239 - Needs update
- CSharp/CodeMethodWriter.cs:431 - ✅ Already supports
- Java/CodeMethodWriter.cs:589 - ✅ Already supports
- TypeScript/CodeConstantWriter.cs:109 - ✅ Already supports
- Python/CodeMethodWriter.cs:633 - ✅ Already supports
- PHP/CodeMethodWriter.cs:580 - ✅ Already supports
- Go/CodeMethodWriter.cs:899 - ✅ Already supports
- Ruby/CodeMethodWriter.cs:320 - ✅ Already supports
- Dart/CodeMethodWriter.cs:510 - ✅ Already supports
Implementation Phases
| Phase | Scope | Notes |
|---|---|---|
| Phase 1 | Core URI template fix | Add GenerateOperationSpecificUrlTemplate() method in KiotaBuilder.cs |
| Phase 2 | Template strategy | Keep current behavior (conservative approach) |
| Phase 3 | Language support | Add HTTP writer support; all other languages already ready ✅ |
| Testing | All phases | Unit tests + integration tests across all languages |
Key Finding: All major language generators already support UrlTemplateOverride, making implementation straightforward.
Success Criteria
- ✅ Operations with unique required parameters get
UrlTemplateOverride - ✅ Operations sharing parameters use class-level template
- ✅ All existing tests pass
- ✅ New tests cover parameter conflict scenarios
- ✅ Generated code compiles in all supported languages
- ✅ No breaking changes to existing consumers
- ✅ HTTP writer supports per-operation templates
Risks & Mitigations
| Risk | Impact | Mitigation |
|---|---|---|
| Language generators don't support UrlTemplateOverride | High | Phase 3: Verify all generators, add support if missing |
| Generated code size increases significantly | Medium | Only generate overrides when needed (empty string otherwise) |
| Breaking changes for consumers | High | Keep Phase 4 opt-in, default to current behavior |
| Performance regression in generation | Low | Profile generation time, optimize if needed |
| Edge cases with complex parameter combinations | Medium | Comprehensive test suite with real-world OpenAPI specs |
Open Questions
-
Should we generate warnings for parameter conflicts?
- Recommendation: Add diagnostic warnings when operations have conflicting parameter types or requirements
-
Versioning strategy?
- Recommendation: Minor version (backward compatible changes only)
Next Steps
- ✅ Review and approve this plan
- Implement Phase 1 (core URI template fix)
- Implement Phase 2 (keep current template strategy)
- Implement Phase 3 (add HTTP writer support)
- Write comprehensive tests
- Create PR with changes
References
- RFC 6570: URI Template Specification
- OpenAPI 3.x Specification: Parameter Objects
- Kiota Documentation
Quick Reference: Language Support Status
| Language | Support Status | File | Test Coverage |
|---|---|---|---|
| C# | ✅ Ready | CodeMethodWriter.cs:431 | ✅ Has test |
| Java | ✅ Ready | CodeMethodWriter.cs:589 | ✅ Has test |
| TypeScript | ✅ Ready | CodeConstantWriter.cs:109 | |
| Python | ✅ Ready | CodeMethodWriter.cs:633 | ✅ Has test |
| PHP | ✅ Ready | CodeMethodWriter.cs:580 | ✅ Has test |
| Go | ✅ Ready | CodeMethodWriter.cs:899 | ✅ Has test |
| Ruby | ✅ Ready | CodeMethodWriter.cs:320 | ✅ Has test |
| Dart | ✅ Ready | CodeMethodWriter.cs:510 | ✅ Has test |
| HTTP | CodeClassDeclarationWriter.cs:239 | ❌ Not implemented |
Conclusion
This plan demonstrates that implementing per-operation URI templates in Kiota is much simpler than initially expected because:
- Infrastructure already exists -
UrlTemplateOverrideis a first-class feature - Language support is widespread - 8 out of 9 generators already handle it
- Test coverage is good - Most languages have existing tests
- Implementation is straightforward - Primarily changes to
KiotaBuilder.cs
The implementation is straightforward with minimal risk and zero breaking changes, making it an excellent candidate for immediate implementation.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status