Skip to content

Commit b529102

Browse files
committed
typescript: allow mixed types for anyOf
1 parent 5fb1a91 commit b529102

File tree

2 files changed

+189
-1
lines changed

2 files changed

+189
-1
lines changed

src/Kiota.Builder/KiotaBuilder.cs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1107,7 +1107,7 @@ private CodeParameter GetIndexerParameter(OpenApiUrlTreeNode currentNode)
11071107
var type = parameter switch
11081108
{
11091109
null => DefaultIndexerParameterType,
1110-
_ => GetPrimitiveType(parameter.Schema),
1110+
_ => GetPathParameterType(parameter.Schema),
11111111
} ?? DefaultIndexerParameterType;
11121112
type.IsNullable = false;
11131113
var segment = currentNode.DeduplicatedSegment();
@@ -1253,6 +1253,55 @@ openApiExtension is OpenApiPrimaryErrorMessageExtension primaryErrorMessageExten
12531253
(_, _) => null,
12541254
};
12551255
}
1256+
1257+
private CodeTypeBase? GetPathParameterType(IOpenApiSchema? typeSchema)
1258+
{
1259+
// Check if it's a union type with mixed primitives (anyOf or oneOf) first
1260+
if (typeSchema != null && (typeSchema.AnyOf?.Count > 0 || typeSchema.OneOf?.Count > 0))
1261+
{
1262+
var schemas = typeSchema.AnyOf ?? typeSchema.OneOf;
1263+
if (schemas != null)
1264+
{
1265+
var primitiveTypes = new List<CodeType>();
1266+
1267+
foreach (var schema in schemas)
1268+
{
1269+
if (GetPrimitiveType(schema) is CodeType schemaType)
1270+
{
1271+
primitiveTypes.Add(schemaType);
1272+
}
1273+
}
1274+
1275+
// If we found multiple primitive types, create a union type
1276+
if (primitiveTypes.Count > 1)
1277+
{
1278+
var unionType = new CodeUnionType
1279+
{
1280+
Name = "PathParameterUnion"
1281+
};
1282+
1283+
foreach (var primitiveType in primitiveTypes)
1284+
{
1285+
if (!unionType.ContainsType(primitiveType))
1286+
{
1287+
unionType.AddType(primitiveType);
1288+
}
1289+
}
1290+
1291+
return unionType;
1292+
}
1293+
// If we only found one primitive type, return it
1294+
else if (primitiveTypes.Count == 1)
1295+
{
1296+
return primitiveTypes[0];
1297+
}
1298+
}
1299+
}
1300+
1301+
// Fall back to regular primitive type handling
1302+
return GetPrimitiveType(typeSchema);
1303+
}
1304+
12561305
private const string RequestBodyPlainTextContentType = "text/plain";
12571306
private const string RequestBodyOctetStreamContentType = "application/octet-stream";
12581307
private const string DefaultResponseIndicator = "default";

tests/Kiota.Builder.Tests/KiotaBuilderTests.cs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6687,6 +6687,145 @@ public async Task IndexerTypeIsAccurateAndBackwardCompatibleIndexersAreAddedAsyn
66876687
var actorsItemRequestBuilder = actorsItemRequestBuilderNamespace.FindChildByName<CodeClass>("actorItemRequestBuilder");
66886688
Assert.Equal(actorsCollectionIndexer.ReturnType.Name, actorsItemRequestBuilder.Name);
66896689
}
6690+
6691+
[Fact]
6692+
public async Task IndexerSupportsUnionOfPrimitiveTypesForPathParametersAsync()
6693+
{
6694+
var tempFilePath = Path.GetTempFileName();
6695+
await using var fs = await GetDocumentStreamAsync(@"openapi: 3.0.0
6696+
info:
6697+
title: Test API with Union Path Parameters
6698+
version: 1.0.0
6699+
servers:
6700+
- url: https://api.example.com/v1
6701+
paths:
6702+
/keys/{ssh_key_identifier}:
6703+
get:
6704+
parameters:
6705+
- name: ssh_key_identifier
6706+
in: path
6707+
required: true
6708+
description: Either the ID or the fingerprint of an existing SSH key.
6709+
schema:
6710+
anyOf:
6711+
- type: string
6712+
- type: integer
6713+
example: 512189
6714+
responses:
6715+
200:
6716+
description: Success!
6717+
content:
6718+
application/json:
6719+
schema:
6720+
$ref: '#/components/schemas/SSHKey'
6721+
components:
6722+
schemas:
6723+
SSHKey:
6724+
type: object
6725+
properties:
6726+
id:
6727+
type: integer
6728+
fingerprint:
6729+
type: string
6730+
key:
6731+
type: string");
6732+
var mockLogger = new Mock<ILogger<KiotaBuilder>>();
6733+
var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "TestClient", OpenAPIFilePath = tempFilePath }, _httpClient);
6734+
var document = await builder.CreateOpenApiDocumentAsync(fs);
6735+
var node = builder.CreateUriSpace(document!);
6736+
var codeModel = builder.CreateSourceModel(node);
6737+
6738+
var keysCollectionRequestBuilderNamespace = codeModel.FindNamespaceByName("ApiSdk.keys");
6739+
Assert.NotNull(keysCollectionRequestBuilderNamespace);
6740+
var keysCollectionRequestBuilder = keysCollectionRequestBuilderNamespace.FindChildByName<CodeClass>("keysRequestBuilder");
6741+
var keysCollectionIndexer = keysCollectionRequestBuilder.Indexer;
6742+
Assert.NotNull(keysCollectionIndexer);
6743+
6744+
// Check that the indexer parameter type is a union type containing both string and integer
6745+
var parameterType = keysCollectionIndexer.IndexParameter.Type;
6746+
Assert.IsType<CodeUnionType>(parameterType);
6747+
var unionType = (CodeUnionType)keysCollectionIndexer.IndexParameter.Type;
6748+
Assert.Equal(2, unionType.Types.Count());
6749+
6750+
// Verify both types are present in the union
6751+
Assert.Contains(unionType.Types, t => t.Name.Equals("string", StringComparison.OrdinalIgnoreCase));
6752+
Assert.Contains(unionType.Types, t => t.Name.Equals("integer", StringComparison.OrdinalIgnoreCase));
6753+
6754+
// Verify description
6755+
Assert.Equal("Either the ID or the fingerprint of an existing SSH key.", keysCollectionIndexer.IndexParameter.Documentation.DescriptionTemplate);
6756+
Assert.False(keysCollectionIndexer.IndexParameter.Type.IsNullable);
6757+
Assert.False(keysCollectionIndexer.Deprecation.IsDeprecated);
6758+
}
6759+
6760+
[Fact]
6761+
public async Task IndexerSupportsUnionOfPrimitiveTypesForPathParametersWithOneOfAsync()
6762+
{
6763+
var tempFilePath = Path.GetTempFileName();
6764+
await using var fs = await GetDocumentStreamAsync(@"openapi: 3.0.0
6765+
info:
6766+
title: Test API with OneOf Path Parameters
6767+
version: 1.0.0
6768+
servers:
6769+
- url: https://api.example.com/v1
6770+
paths:
6771+
/keys/{ssh_key_identifier}:
6772+
get:
6773+
parameters:
6774+
- name: ssh_key_identifier
6775+
in: path
6776+
required: true
6777+
description: Either the ID or the fingerprint of an existing SSH key.
6778+
schema:
6779+
oneOf:
6780+
- type: string
6781+
- type: integer
6782+
example: 512189
6783+
responses:
6784+
200:
6785+
description: Success!
6786+
content:
6787+
application/json:
6788+
schema:
6789+
$ref: '#/components/schemas/SSHKey'
6790+
components:
6791+
schemas:
6792+
SSHKey:
6793+
type: object
6794+
properties:
6795+
id:
6796+
type: integer
6797+
fingerprint:
6798+
type: string
6799+
key:
6800+
type: string");
6801+
var mockLogger = new Mock<ILogger<KiotaBuilder>>();
6802+
var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "TestClient", OpenAPIFilePath = tempFilePath }, _httpClient);
6803+
var document = await builder.CreateOpenApiDocumentAsync(fs);
6804+
var node = builder.CreateUriSpace(document!);
6805+
var codeModel = builder.CreateSourceModel(node);
6806+
6807+
var keysCollectionRequestBuilderNamespace = codeModel.FindNamespaceByName("ApiSdk.keys");
6808+
Assert.NotNull(keysCollectionRequestBuilderNamespace);
6809+
var keysCollectionRequestBuilder = keysCollectionRequestBuilderNamespace.FindChildByName<CodeClass>("keysRequestBuilder");
6810+
var keysCollectionIndexer = keysCollectionRequestBuilder.Indexer;
6811+
Assert.NotNull(keysCollectionIndexer);
6812+
6813+
// Check that the indexer parameter type is a union type containing both string and integer
6814+
var parameterType = keysCollectionIndexer.IndexParameter.Type;
6815+
Assert.IsType<CodeUnionType>(parameterType);
6816+
var unionType = (CodeUnionType)keysCollectionIndexer.IndexParameter.Type;
6817+
Assert.Equal(2, unionType.Types.Count());
6818+
6819+
// Verify both types are present in the union
6820+
Assert.Contains(unionType.Types, t => t.Name.Equals("string", StringComparison.OrdinalIgnoreCase));
6821+
Assert.Contains(unionType.Types, t => t.Name.Equals("integer", StringComparison.OrdinalIgnoreCase));
6822+
6823+
// Verify description
6824+
Assert.Equal("Either the ID or the fingerprint of an existing SSH key.", keysCollectionIndexer.IndexParameter.Documentation.DescriptionTemplate);
6825+
Assert.False(keysCollectionIndexer.IndexParameter.Type.IsNullable);
6826+
Assert.False(keysCollectionIndexer.Deprecation.IsDeprecated);
6827+
}
6828+
66906829
[Fact]
66916830
public async Task MapsBooleanEnumToBooleanTypeAsync()
66926831
{

0 commit comments

Comments
 (0)