Skip to content

Commit

Permalink
- adds support for enum types in query parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
baywet committed Oct 12, 2023
1 parent f952038 commit 85d5858
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 30 deletions.
108 changes: 79 additions & 29 deletions src/Kiota.Builder/KiotaBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1223,7 +1223,7 @@ openApiExtension is OpenApiPrimaryErrorMessageExtension primaryErrorMessageExten
};
}
private const string RequestBodyPlainTextContentType = "text/plain";
private static readonly HashSet<string> noContentStatusCodes = new() { "201", "202", "204", "205" };
private static readonly HashSet<string> noContentStatusCodes = new(StringComparer.OrdinalIgnoreCase) { "201", "202", "204", "205" };
private static readonly HashSet<string> errorStatusCodes = new(Enumerable.Range(400, 599).Select(static x => x.ToString(CultureInfo.InvariantCulture))
.Concat(new[] { FourXXError, FiveXXError }), StringComparer.OrdinalIgnoreCase);
private const string FourXXError = "4XX";
Expand Down Expand Up @@ -1793,35 +1793,49 @@ private CodeElement AddModelDeclarationIfDoesntExist(OpenApiUrlTreeNode currentN
{
if (GetExistingDeclaration(currentNamespace, currentNode, declarationName) is not CodeElement existingDeclaration) // we can find it in the components
{
if (schema.IsEnum())
{
var schemaDescription = schema.Description.CleanupDescription();
OpenApiEnumFlagsExtension? enumFlagsExtension = null;
if (schema.Extensions.TryGetValue(OpenApiEnumFlagsExtension.Name, out var rawExtension) &&
rawExtension is OpenApiEnumFlagsExtension flagsExtension)
{
enumFlagsExtension = flagsExtension;
}
var newEnum = new CodeEnum
{
Name = declarationName,
Flags = enumFlagsExtension?.IsFlags ?? false,
Documentation = new()
{
Description = !string.IsNullOrEmpty(schemaDescription) || !string.IsNullOrEmpty(schema.Reference?.Id) ?
schemaDescription : // if it's a referenced component, we shouldn't use the path item description as it makes it indeterministic
currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel),
},
Deprecation = schema.GetDeprecationInformation(),
};
SetEnumOptions(schema, newEnum);
return currentNamespace.AddEnum(newEnum).First();
}
if (AddEnumDeclaration(currentNode, schema, declarationName, currentNamespace) is CodeEnum enumDeclaration)
return enumDeclaration;

return AddModelClass(currentNode, schema, declarationName, currentNamespace, inheritsFrom);
}
return existingDeclaration;
}
private CodeEnum? AddEnumDeclarationIfDoesntExist(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, string declarationName, CodeNamespace currentNamespace)
{
if (GetExistingDeclaration(currentNamespace, currentNode, declarationName) is not CodeEnum existingDeclaration) // we can find it in the components
{
return AddEnumDeclaration(currentNode, schema, declarationName, currentNamespace);
}
return existingDeclaration;
}
private CodeEnum? AddEnumDeclaration(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, string declarationName, CodeNamespace currentNamespace)
{
if (schema.IsEnum())
{
var schemaDescription = schema.Description.CleanupDescription();
OpenApiEnumFlagsExtension? enumFlagsExtension = null;
if (schema.Extensions.TryGetValue(OpenApiEnumFlagsExtension.Name, out var rawExtension) &&
rawExtension is OpenApiEnumFlagsExtension flagsExtension)
{
enumFlagsExtension = flagsExtension;
}
var newEnum = new CodeEnum
{
Name = declarationName,
Flags = enumFlagsExtension?.IsFlags ?? false,
Documentation = new()
{
Description = !string.IsNullOrEmpty(schemaDescription) || !string.IsNullOrEmpty(schema.Reference?.Id) ?
schemaDescription : // if it's a referenced component, we shouldn't use the path item description as it makes it indeterministic
currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel),
},
Deprecation = schema.GetDeprecationInformation(),
};
SetEnumOptions(schema, newEnum);
return currentNamespace.AddEnum(newEnum).First();
}
return default;
}
private static void SetEnumOptions(OpenApiSchema schema, CodeEnum target)
{
OpenApiEnumValuesDescriptionExtension? extensionInformation = null;
Expand Down Expand Up @@ -2278,16 +2292,39 @@ internal static void AddSerializationMembers(CodeClass model, bool includeAdditi
},
}).First();
foreach (var parameter in parameters)
AddPropertyForQueryParameter(parameter, parameterClass);
AddPropertyForQueryParameter(node, operationType, parameter, parameterClass);

return parameterClass;
}

return null;
}
private void AddPropertyForQueryParameter(OpenApiParameter parameter, CodeClass parameterClass)
private void AddPropertyForQueryParameter(OpenApiUrlTreeNode node, OperationType operationType, OpenApiParameter parameter, CodeClass parameterClass)
{
var resultType = GetPrimitiveType(parameter.Schema) ?? new CodeType()
CodeType? resultType = default;
var addBackwardCompatibleParameter = false;
if (parameter.Schema.IsEnum())
{
var schema = parameter.Schema;
var codeNamespace = schema.IsReferencedSchema() switch
{
true => GetShortestNamespace(parameterClass.GetImmediateParentOfType<CodeNamespace>(), schema), // referenced schema
false => parameterClass.GetImmediateParentOfType<CodeNamespace>(), // Inline schema, i.e. specific to the Operation
};
var shortestNamespace = GetShortestNamespace(codeNamespace, schema);
var enumName = schema.GetSchemaName().CleanupSymbolName();
if (string.IsNullOrEmpty(enumName))
enumName = $"{operationType.ToString().ToFirstCharacterUpperCase()}{parameter.Name.ToFirstCharacterUpperCase()}QueryParameterType";
if (AddEnumDeclarationIfDoesntExist(node, schema, enumName, shortestNamespace) is { } enumDeclaration)
{
resultType = new CodeType
{
TypeDefinition = enumDeclaration,
};
addBackwardCompatibleParameter = true;
}
}
resultType ??= GetPrimitiveType(parameter.Schema) ?? new CodeType()
{
// since its a query parameter default to string if there is no schema
// it also be an object type, but we'd need to create the model in that case and there's no standard on how to serialize those as query parameters
Expand All @@ -2314,7 +2351,20 @@ private void AddPropertyForQueryParameter(OpenApiParameter parameter, CodeClass

if (!parameterClass.ContainsPropertyWithWireName(prop.WireName))
{
parameterClass.AddProperty(prop);
if (addBackwardCompatibleParameter && config.IncludeBackwardCompatible && config.Language is GenerationLanguage.CSharp or GenerationLanguage.Go)
{ //TODO remove for v2
var modernProp = (CodeProperty)prop.Clone();
modernProp.Name = $"{prop.Name}As{modernProp.Type.Name.ToFirstCharacterUpperCase()}";
modernProp.SerializationName = prop.WireName;
prop.Deprecation = new($"This property is deprecated, use {modernProp.Name} instead", IsDeprecated: true);
prop.Type = GetDefaultQueryParameterType();
prop.Type.CollectionKind = modernProp.Type.CollectionKind;
parameterClass.AddProperty(modernProp, prop);
}
else
{
parameterClass.AddProperty(prop);
}
}
else
{
Expand Down
61 changes: 60 additions & 1 deletion tests/Kiota.Builder.Tests/KiotaBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3895,7 +3895,7 @@ public void MapsQueryParameterTypes(string type, string format, string expected)
[OperationType.Get] = new OpenApiOperation
{
Parameters = new List<OpenApiParameter> {
new OpenApiParameter {
new() {
Name = "query",
In = ParameterLocation.Query,
Schema = new OpenApiSchema {
Expand Down Expand Up @@ -3924,6 +3924,65 @@ public void MapsQueryParameterTypes(string type, string format, string expected)
Assert.Equal(expected, property.Type.Name);
Assert.True(property.Type.AllTypes.First().IsExternal);
}
[InlineData(GenerationLanguage.CSharp)]
[InlineData(GenerationLanguage.Java)]
[Theory]
public void MapsEnumQueryParameterType(GenerationLanguage generationLanguage)
{
var document = new OpenApiDocument
{
Paths = new OpenApiPaths
{
["primitive"] = new OpenApiPathItem
{
Operations = {
[OperationType.Get] = new OpenApiOperation
{
Parameters = new List<OpenApiParameter> {
new() {
Name = "query",
In = ParameterLocation.Query,
Schema = new OpenApiSchema {
Type = "string",
Enum = new List<IOpenApiAny> {
new OpenApiString("value1"),
new OpenApiString("value2")
}
}
}
},
Responses = new OpenApiResponses
{
["204"] = new OpenApiResponse()
}
}
}
}
},
};
var mockLogger = new Mock<ILogger<KiotaBuilder>>();
var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Graph", ApiRootUrl = "https://localhost", Language = generationLanguage }, _httpClient);
var node = builder.CreateUriSpace(document);
var codeModel = builder.CreateSourceModel(node);
var queryParameters = codeModel.FindChildByName<CodeClass>("primitiveRequestBuilderGetQueryParameters");
Assert.NotNull(queryParameters);
var backwardCompatibleProperty = queryParameters.Properties.FirstOrDefault(static x => x.Name.Equals("query", StringComparison.OrdinalIgnoreCase));
Assert.NotNull(backwardCompatibleProperty);
if (generationLanguage is GenerationLanguage.CSharp)
{
Assert.Equal("string", backwardCompatibleProperty.Type.Name);
Assert.True(backwardCompatibleProperty.Type.AllTypes.First().IsExternal);
Assert.True(backwardCompatibleProperty.Deprecation.IsDeprecated);
var property = queryParameters.Properties.FirstOrDefault(static x => x.Name.Equals("queryAsGetQueryQueryParameterType", StringComparison.OrdinalIgnoreCase));
Assert.NotNull(property);
Assert.Equal("GetQueryQueryParameterType", property.Type.Name);
}
else
{
Assert.Equal("GetQueryQueryParameterType", backwardCompatibleProperty.Type.Name);
Assert.False(backwardCompatibleProperty.Deprecation.IsDeprecated);
}
}
[InlineData(true)]
[InlineData(false)]
[Theory]
Expand Down

0 comments on commit 85d5858

Please sign in to comment.