From 8be8f8cd7ac333d08e690c10f88f8bac5993fa0b Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 30 May 2022 15:09:30 -0400 Subject: [PATCH 1/7] - adds an additional parameter for mime types - refactors default values managements Signed-off-by: Vincent Biret --- src/Kiota.Builder/CodeDOM/CodeMethod.cs | 4 +- .../Extensions/OpenApiOperationExtensions.cs | 1 + src/Kiota.Builder/GenerationConfiguration.cs | 17 +++++++- .../Refiners/CommonLanguageRefiner.cs | 29 +++++-------- src/Kiota.Builder/Refiners/GoRefiner.cs | 13 ++++-- src/Kiota.Builder/Refiners/JavaRefiner.cs | 13 ++++-- src/Kiota.Builder/Refiners/PhpRefiner.cs | 13 ++++-- src/Kiota.Builder/Refiners/RubyRefiner.cs | 13 +++++- .../Refiners/TypeScriptRefiner.cs | 15 +++++-- .../Writers/CSharp/CodeMethodWriter.cs | 2 +- .../Writers/Go/CodeMethodWriter.cs | 2 +- .../Writers/Java/CodeMethodWriter.cs | 2 +- .../Writers/Php/CodeMethodWriter.cs | 2 +- .../Writers/TypeScript/CodeMethodWriter.cs | 2 +- src/kiota/KiotaHost.cs | 42 +++++++++++-------- 15 files changed, 106 insertions(+), 64 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeMethod.cs b/src/Kiota.Builder/CodeDOM/CodeMethod.cs index 2debd2ce08..1dac60ff1c 100644 --- a/src/Kiota.Builder/CodeDOM/CodeMethod.cs +++ b/src/Kiota.Builder/CodeDOM/CodeMethod.cs @@ -131,8 +131,8 @@ public void AddPathQueryOrHeaderParameter(params CodeParameter[] parameters) public bool IsAccessor { get => IsOfKind(CodeMethodKind.Getter, CodeMethodKind.Setter); } - public List SerializerModules { get; set; } - public List DeserializerModules { get; set; } + public HashSet SerializerModules { get; set; } + public HashSet DeserializerModules { get; set; } /// /// Indicates whether this method is an overload for another method. /// diff --git a/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs index 9b070b07c4..729ab83a3b 100644 --- a/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs @@ -7,6 +7,7 @@ namespace Kiota.Builder.Extensions { public static class OpenApiOperationExtensions { private static readonly HashSet successCodes = new(StringComparer.OrdinalIgnoreCase) {"200", "201", "202"}; //204 excluded as it won't have a schema + [Obsolete("pass the value from configuration instead")] private static readonly HashSet structuredMimeTypes = new (StringComparer.OrdinalIgnoreCase) { "application/json", "application/xml", diff --git a/src/Kiota.Builder/GenerationConfiguration.cs b/src/Kiota.Builder/GenerationConfiguration.cs index 0fd03a6b77..8a92e3f08b 100644 --- a/src/Kiota.Builder/GenerationConfiguration.cs +++ b/src/Kiota.Builder/GenerationConfiguration.cs @@ -13,8 +13,14 @@ public class GenerationConfiguration { public string ApiRootUrl { get; set; } public string[] PropertiesPrefixToStrip { get; set; } = new string[] { "@odata."}; public bool UsesBackingStore { get; set; } - public List Serializers { get; set; } = new(); - public List Deserializers { get; set; } = new(); + public HashSet Serializers { get; set; } = new(StringComparer.OrdinalIgnoreCase){ + "Microsoft.Kiota.Serialization.Json.JsonSerializationWriterFactory", + "Microsoft.Kiota.Serialization.Text.TextSerializationWriterFactory" + }; + public HashSet Deserializers { get; set; } = new(StringComparer.OrdinalIgnoreCase) { + "Microsoft.Kiota.Serialization.Json.JsonParseNodeFactory", + "Microsoft.Kiota.Serialization.Text.TextParseNodeFactory" + }; public bool ShouldWriteNamespaceIndices { get { return BarreledLanguages.Contains(Language); } } public bool ShouldWriteBarrelsIfClassExists { get { return BarreledLanguagesWithConstantFileName.Contains(Language); } } public bool ShouldRenderMethodsOutsideOfClasses { get { return MethodOutsideOfClassesLanguages.Contains(Language); } } @@ -30,5 +36,12 @@ public class GenerationConfiguration { GenerationLanguage.TypeScript }; public bool CleanOutput { get; set;} + public HashSet StructuredMimeTypes { get; set; } = new(StringComparer.OrdinalIgnoreCase) { + "application/json", + "application/xml", + "text/plain", + "text/xml", + "text/yaml", + }; } } diff --git a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs index e9b55e2e47..543197009e 100644 --- a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs +++ b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs @@ -29,8 +29,8 @@ currentMethod.Parent is CodeClass currentClass && .Union(parseNodeFactoryInterfaceAndRegistrationFullName) .Where(x => !string.IsNullOrEmpty(x)) .ToList(); - currentMethod.DeserializerModules = currentMethod.DeserializerModules.Select(x => x.Split(separator).Last()).ToList(); - currentMethod.SerializerModules = currentMethod.SerializerModules.Select(x => x.Split(separator).Last()).ToList(); + currentMethod.DeserializerModules = currentMethod.DeserializerModules.Select(x => x.Split(separator).Last()).ToHashSet(StringComparer.OrdinalIgnoreCase); + currentMethod.SerializerModules = currentMethod.SerializerModules.Select(x => x.Split(separator).Last()).ToHashSet(StringComparer.OrdinalIgnoreCase); declaration.AddUsings(cumulatedSymbols.Select(x => new CodeUsing { Name = x.Split(separator).Last(), Declaration = new CodeType { @@ -42,32 +42,23 @@ currentMethod.Parent is CodeClass currentClass && } CrawlTree(generatedCode, x => AddSerializationModulesImport(x, serializationWriterFactoryInterfaceAndRegistrationFullName, parseNodeFactoryInterfaceAndRegistrationFullName, separator)); } - protected static void ReplaceDefaultSerializationModules(CodeElement generatedCode, params string[] moduleNames) { - var defaultValues = new HashSet(StringComparer.OrdinalIgnoreCase) { - "Microsoft.Kiota.Serialization.Json.JsonSerializationWriterFactory", - "Microsoft.Kiota.Serialization.Text.TextSerializationWriterFactory", - }; - if(ReplaceSerializationModules(generatedCode, x => x.SerializerModules, defaultValues, moduleNames)) + protected static void ReplaceDefaultSerializationModules(CodeElement generatedCode, HashSet defaultValues, HashSet newModuleNames) { + if(ReplaceSerializationModules(generatedCode, x => x.SerializerModules, (x, y) => x.SerializerModules = y, defaultValues, newModuleNames)) return; - CrawlTree(generatedCode, (x) => ReplaceDefaultSerializationModules(x, moduleNames)); + CrawlTree(generatedCode, (x) => ReplaceDefaultSerializationModules(x, defaultValues, newModuleNames)); } - protected static void ReplaceDefaultDeserializationModules(CodeElement generatedCode, params string[] moduleNames) { - var defaultValues = new HashSet(StringComparer.OrdinalIgnoreCase) { - "Microsoft.Kiota.Serialization.Json.JsonParseNodeFactory", - "Microsoft.Kiota.Serialization.Text.TextParseNodeFactory", - }; - if(ReplaceSerializationModules(generatedCode, x => x.DeserializerModules, defaultValues, moduleNames)) + protected static void ReplaceDefaultDeserializationModules(CodeElement generatedCode, HashSet defaultValues, HashSet newModuleNames) { + if(ReplaceSerializationModules(generatedCode, x => x.DeserializerModules, (x, y) => x.DeserializerModules = y, defaultValues, newModuleNames)) return; - CrawlTree(generatedCode, (x) => ReplaceDefaultDeserializationModules(x, moduleNames)); + CrawlTree(generatedCode, (x) => ReplaceDefaultDeserializationModules(x, defaultValues, newModuleNames)); } - private static bool ReplaceSerializationModules(CodeElement generatedCode, Func> propertyGetter, HashSet initialNames, params string[] moduleNames) { + private static bool ReplaceSerializationModules(CodeElement generatedCode, Func> propertyGetter, Action> propertySetter, HashSet initialNames, HashSet moduleNames) { if(generatedCode is CodeMethod currentMethod && currentMethod.IsOfKind(CodeMethodKind.ClientConstructor)) { var modules = propertyGetter.Invoke(currentMethod); if(modules.Count == initialNames.Count && modules.All(x => initialNames.Contains(x))) { - modules.Clear(); - modules.AddRange(moduleNames); + propertySetter.Invoke(currentMethod, moduleNames); return true; } } diff --git a/src/Kiota.Builder/Refiners/GoRefiner.cs b/src/Kiota.Builder/Refiners/GoRefiner.cs index aa2f0e308b..9f8c8b1626 100644 --- a/src/Kiota.Builder/Refiners/GoRefiner.cs +++ b/src/Kiota.Builder/Refiners/GoRefiner.cs @@ -76,14 +76,19 @@ public override void Refine(CodeNamespace generatedCode) generatedCode); AddErrorImportForEnums( generatedCode); + var defaultConfiguration = new GenerationConfiguration(); ReplaceDefaultSerializationModules( generatedCode, - "github.com/microsoft/kiota-serialization-json-go.JsonSerializationWriterFactory", - "github.com/microsoft/kiota-serialization-text-go.TextSerializationWriterFactory"); + defaultConfiguration.Serializers, + new (StringComparer.OrdinalIgnoreCase) { + "github.com/microsoft/kiota-serialization-json-go.JsonSerializationWriterFactory", + "github.com/microsoft/kiota-serialization-text-go.TextSerializationWriterFactory"}); ReplaceDefaultDeserializationModules( generatedCode, - "github.com/microsoft/kiota-serialization-json-go.JsonParseNodeFactory", - "github.com/microsoft/kiota-serialization-text-go.TextParseNodeFactory"); + defaultConfiguration.Deserializers, + new (StringComparer.OrdinalIgnoreCase) { + "github.com/microsoft/kiota-serialization-json-go.JsonParseNodeFactory", + "github.com/microsoft/kiota-serialization-text-go.TextParseNodeFactory"}); AddSerializationModulesImport( generatedCode, new string[] {"github.com/microsoft/kiota-abstractions-go/serialization.SerializationWriterFactory", "github.com/microsoft/kiota-abstractions-go.RegisterDefaultSerializer"}, diff --git a/src/Kiota.Builder/Refiners/JavaRefiner.cs b/src/Kiota.Builder/Refiners/JavaRefiner.cs index b963f25a66..1b34bd8863 100644 --- a/src/Kiota.Builder/Refiners/JavaRefiner.cs +++ b/src/Kiota.Builder/Refiners/JavaRefiner.cs @@ -38,15 +38,20 @@ public override void Refine(CodeNamespace generatedCode) SetSetterParametersToNullable(generatedCode, new Tuple(CodeMethodKind.Setter, CodePropertyKind.AdditionalData)); AddConstructorsForDefaultValues(generatedCode, true); CorrectCoreTypesForBackingStore(generatedCode, "BackingStoreFactorySingleton.instance.createBackingStore()"); + var defaultConfiguration = new GenerationConfiguration(); ReplaceDefaultSerializationModules( generatedCode, - "com.microsoft.kiota.serialization.JsonSerializationWriterFactory", - "com.microsoft.kiota.serialization.TextSerializationWriterFactory" + defaultConfiguration.Serializers, + new (StringComparer.OrdinalIgnoreCase) { + "com.microsoft.kiota.serialization.JsonSerializationWriterFactory", + "com.microsoft.kiota.serialization.TextSerializationWriterFactory"} ); ReplaceDefaultDeserializationModules( generatedCode, - "com.microsoft.kiota.serialization.JsonParseNodeFactory", - "com.microsoft.kiota.serialization.TextParseNodeFactory" + defaultConfiguration.Deserializers, + new (StringComparer.OrdinalIgnoreCase) { + "com.microsoft.kiota.serialization.JsonParseNodeFactory", + "com.microsoft.kiota.serialization.TextParseNodeFactory"} ); AddSerializationModulesImport(generatedCode, new [] { "com.microsoft.kiota.ApiClientBuilder", diff --git a/src/Kiota.Builder/Refiners/PhpRefiner.cs b/src/Kiota.Builder/Refiners/PhpRefiner.cs index 9b85119814..619ecdbc76 100644 --- a/src/Kiota.Builder/Refiners/PhpRefiner.cs +++ b/src/Kiota.Builder/Refiners/PhpRefiner.cs @@ -31,13 +31,18 @@ public override void Refine(CodeNamespace generatedCode) MakeModelPropertiesNullable(generatedCode); ReplaceIndexersByMethodsWithParameter(generatedCode, generatedCode, false, "ById"); AddPropertiesAndMethodTypesImports(generatedCode, true, false, true); + var defaultConfiguration = new GenerationConfiguration(); ReplaceDefaultSerializationModules(generatedCode, - "Microsoft\\Kiota\\Serialization\\Json\\JsonSerializationWriterFactory", - "Microsoft\\Kiota\\Serialization\\Text\\TextSerializationWriterFactory" + defaultConfiguration.Serializers, + new (StringComparer.OrdinalIgnoreCase) { + "Microsoft\\Kiota\\Serialization\\Json\\JsonSerializationWriterFactory", + "Microsoft\\Kiota\\Serialization\\Text\\TextSerializationWriterFactory"} ); ReplaceDefaultDeserializationModules(generatedCode, - "Microsoft\\Kiota\\Serialization\\Json\\JsonParseNodeFactory", - "Microsoft\\Kiota\\Serialization\\Text\\TextParseNodeFactory" + defaultConfiguration.Deserializers, + new (StringComparer.OrdinalIgnoreCase) { + "Microsoft\\Kiota\\Serialization\\Json\\JsonParseNodeFactory", + "Microsoft\\Kiota\\Serialization\\Text\\TextParseNodeFactory"} ); AliasUsingWithSameSymbol(generatedCode); AddSerializationModulesImport(generatedCode, new []{"Microsoft\\Kiota\\Abstractions\\ApiClientBuilder"}, null, '\\'); diff --git a/src/Kiota.Builder/Refiners/RubyRefiner.cs b/src/Kiota.Builder/Refiners/RubyRefiner.cs index 572319160d..975f36fd5f 100644 --- a/src/Kiota.Builder/Refiners/RubyRefiner.cs +++ b/src/Kiota.Builder/Refiners/RubyRefiner.cs @@ -29,8 +29,17 @@ public override void Refine(CodeNamespace generatedCode) ReplaceReservedNames(generatedCode, new RubyReservedNamesProvider(), x => $"{x}_escaped"); AddNamespaceModuleImports(generatedCode , _configuration.ClientNamespaceName); FixInheritedEntityType(generatedCode); - ReplaceDefaultSerializationModules(generatedCode, "microsoft_kiota_serialization.JsonSerializationWriterFactory"); - ReplaceDefaultDeserializationModules(generatedCode, "microsoft_kiota_serialization.JsonParseNodeFactory"); + var defaultConfiguration = new GenerationConfiguration(); + ReplaceDefaultSerializationModules( + generatedCode, + defaultConfiguration.Serializers, + new (StringComparer.OrdinalIgnoreCase) { + "microsoft_kiota_serialization.JsonSerializationWriterFactory"}); + ReplaceDefaultDeserializationModules( + generatedCode, + defaultConfiguration.Deserializers, + new (StringComparer.OrdinalIgnoreCase) { + "microsoft_kiota_serialization.JsonParseNodeFactory"}); AddSerializationModulesImport(generatedCode, new [] { "microsoft_kiota_abstractions.ApiClientBuilder", "microsoft_kiota_abstractions.SerializationWriterFactoryRegistry" }, diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 9e92e25f39..e6a2eeec9a 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -37,15 +37,22 @@ public override void Refine(CodeNamespace generatedCode) string.Empty, string.Empty); AddConstructorsForDefaultValues(generatedCode, true); + var defaultConfiguration = new GenerationConfiguration(); ReplaceDefaultSerializationModules( generatedCode, - "@microsoft/kiota-serialization-json.JsonSerializationWriterFactory", - "@microsoft/kiota-serialization-text.TextSerializationWriterFactory" + defaultConfiguration.Serializers, + new (StringComparer.OrdinalIgnoreCase) { + "@microsoft/kiota-serialization-json.JsonSerializationWriterFactory", + "@microsoft/kiota-serialization-text.TextSerializationWriterFactory" + } ); ReplaceDefaultDeserializationModules( generatedCode, - "@microsoft/kiota-serialization-json.JsonParseNodeFactory", - "@microsoft/kiota-serialization-text.TextParseNodeFactory" + defaultConfiguration.Deserializers, + new (StringComparer.OrdinalIgnoreCase) { + "@microsoft/kiota-serialization-json.JsonParseNodeFactory", + "@microsoft/kiota-serialization-text.TextParseNodeFactory" + } ); AddSerializationModulesImport(generatedCode, new[] { $"{AbstractionsPackageName}.registerDefaultSerializer", diff --git a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs index cb69b125dd..2b2d2e57c0 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs @@ -118,7 +118,7 @@ private static void WriteApiConstructorBody(CodeClass parentClass, CodeMethod me if (backingStoreParameter != null) writer.WriteLine($"{requestAdapterPropertyName}.EnableBackingStore({backingStoreParameter.Name});"); } - private static void WriteSerializationRegistration(List serializationClassNames, LanguageWriter writer, string methodName) + private static void WriteSerializationRegistration(HashSet serializationClassNames, LanguageWriter writer, string methodName) { if (serializationClassNames != null) foreach (var serializationClassName in serializationClassNames) diff --git a/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs index 2fcd1fccb9..9b00b7762e 100644 --- a/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs @@ -226,7 +226,7 @@ private void WriteApiConstructorBody(CodeClass parentClass, CodeMethod method, L if(backingStoreParameter != null) writer.WriteLine($"m.{requestAdapterPropertyName}.EnableBackingStore({backingStoreParameter.Name});"); } - private void WriteSerializationRegistration(List serializationModules, LanguageWriter writer, CodeClass parentClass, string methodName, string interfaceName) { + private void WriteSerializationRegistration(HashSet serializationModules, LanguageWriter writer, CodeClass parentClass, string methodName, string interfaceName) { var interfaceImportSymbol = conventions.GetTypeString(new CodeType { Name = interfaceName, IsExternal = true }, parentClass, false, false); var methodImportSymbol = conventions.GetTypeString(new CodeType { Name = methodName, IsExternal = true }, parentClass, false, false); if(serializationModules != null) diff --git a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs index c25ff0ea48..5bc44fa97c 100644 --- a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs @@ -129,7 +129,7 @@ private static void WriteApiConstructorBody(CodeClass parentClass, CodeMethod me if(backingStoreParameter != null) writer.WriteLine($"this.{requestAdapterPropertyName}.enableBackingStore({backingStoreParameter.Name});"); } - private static void WriteSerializationRegistration(List serializationModules, LanguageWriter writer, string methodName) { + private static void WriteSerializationRegistration(HashSet serializationModules, LanguageWriter writer, string methodName) { if(serializationModules != null) foreach(var module in serializationModules) writer.WriteLine($"ApiClientBuilder.{methodName}({module}.class);"); diff --git a/src/Kiota.Builder/Writers/Php/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Php/CodeMethodWriter.cs index 78d8dcb5b1..b0503d74d9 100644 --- a/src/Kiota.Builder/Writers/Php/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Php/CodeMethodWriter.cs @@ -491,7 +491,7 @@ private static void WriteApiConstructorBody(CodeClass parentClass, CodeMethod co writer.CloseBlock(); } - private static void WriteSerializationRegistration(List serializationModules, LanguageWriter writer, string methodName) { + private static void WriteSerializationRegistration(HashSet serializationModules, LanguageWriter writer, string methodName) { if(serializationModules != null) foreach(var module in serializationModules) writer.WriteLine($"ApiClientBuilder::{methodName}({module}::class);"); diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs index be1ac88178..0c164c8d6c 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs @@ -126,7 +126,7 @@ private static void WriteApiConstructorBody(CodeClass parentClass, CodeMethod me if(backingStoreParameter != null) writer.WriteLine($"this.{requestAdapterPropertyName}.enableBackingStore({backingStoreParameter.Name});"); } - private static void WriteSerializationRegistration(List serializationModules, LanguageWriter writer, string methodName) { + private static void WriteSerializationRegistration(HashSet serializationModules, LanguageWriter writer, string methodName) { if(serializationModules != null) foreach(var module in serializationModules) writer.WriteLine($"{methodName}({module});"); diff --git a/src/kiota/KiotaHost.cs b/src/kiota/KiotaHost.cs index 6d6a2230b8..a4e776d29a 100644 --- a/src/kiota/KiotaHost.cs +++ b/src/kiota/KiotaHost.cs @@ -17,16 +17,17 @@ public class KiotaHost { public RootCommand GetRootCommand() { var kiotaInContainerRaw = Environment.GetEnvironmentVariable("KIOTA_CONTAINER"); + var defaultConfiguration = new GenerationConfiguration(); var runsInContainer = !string.IsNullOrEmpty(kiotaInContainerRaw) && bool.TryParse(kiotaInContainerRaw, out var kiotaInContainer) && kiotaInContainer; var descriptionOption = new Option("--openapi", "The path to the OpenAPI description file used to generate the code files."); if(runsInContainer) - descriptionOption.SetDefaultValue("openapi.yaml"); + descriptionOption.SetDefaultValue(defaultConfiguration.OpenAPIFilePath); else descriptionOption.IsRequired = true; descriptionOption.AddAlias("-d"); descriptionOption.ArgumentHelpName = "path"; - var outputOption = new Option("--output", () => "./output", "The output directory path for the generated code files."); + var outputOption = new Option("--output", () => defaultConfiguration.OutputPath, "The output directory path for the generated code files."); outputOption.AddAlias("-o"); outputOption.ArgumentHelpName = "path"; @@ -35,12 +36,12 @@ public RootCommand GetRootCommand() languageOption.IsRequired = true; AddEnumValidator(languageOption, "language"); - var classOption = new Option("--class-name", () => "ApiClient", "The class name to use for the core client class."); + var classOption = new Option("--class-name", () => defaultConfiguration.ClientClassName, "The class name to use for the core client class."); classOption.AddAlias("-c"); classOption.ArgumentHelpName = "name"; AddStringRegexValidator(classOption, @"^[a-zA-Z_][\w_-]+", "class name"); - var namespaceOption = new Option("--namespace-name", () => "ApiSdk", "The namespace to use for the core client class specified with the --class-name option."); + var namespaceOption = new Option("--namespace-name", () => defaultConfiguration.ClientNamespaceName, "The namespace to use for the core client class specified with the --class-name option."); namespaceOption.AddAlias("-n"); namespaceOption.ArgumentHelpName = "name"; AddStringRegexValidator(namespaceOption, @"^[\w][\w\._-]+", "namespace name"); @@ -49,32 +50,32 @@ public RootCommand GetRootCommand() logLevelOption.AddAlias("--ll"); AddEnumValidator(logLevelOption, "log level"); - var backingStoreOption = new Option("--backing-store", () => false, "Enables backing store for models."); + var backingStoreOption = new Option("--backing-store", () => defaultConfiguration.UsesBackingStore, "Enables backing store for models."); backingStoreOption.AddAlias("-b"); var serializerOption = new Option>( "--serializer", - () => new List { - "Microsoft.Kiota.Serialization.Json.JsonSerializationWriterFactory", - "Microsoft.Kiota.Serialization.Text.TextSerializationWriterFactory" - }, + () => defaultConfiguration.Serializers.ToList(), "The fully qualified class names for serializers. Accepts multiple values."); serializerOption.AddAlias("-s"); serializerOption.ArgumentHelpName = "classes"; var deserializerOption = new Option>( "--deserializer", - () => new List { - "Microsoft.Kiota.Serialization.Json.JsonParseNodeFactory", - "Microsoft.Kiota.Serialization.Text.TextParseNodeFactory" - }, + () => defaultConfiguration.Deserializers.ToList(), "The fully qualified class names for deserializers. Accepts multiple values."); deserializerOption.AddAlias("--ds"); deserializerOption.ArgumentHelpName = "classes"; - var cleanOutputOption = new Option("--clean-output", () => false, "Removes all files from the output directory before generating the code files."); + var cleanOutputOption = new Option("--clean-output", () => defaultConfiguration.CleanOutput, "Removes all files from the output directory before generating the code files."); cleanOutputOption.AddAlias("--co"); + var structuredMimeTypesOption = new Option>( + "--structured-mime-types", + () => defaultConfiguration.StructuredMimeTypes.ToList(), + "The MIME types to use for structured data model generation. Accepts multiple values."); + structuredMimeTypesOption.AddAlias("-m"); + var command = new RootCommand { descriptionOption, outputOption, @@ -86,16 +87,17 @@ public RootCommand GetRootCommand() serializerOption, deserializerOption, cleanOutputOption, + structuredMimeTypesOption }; command.Description = "OpenAPI-based HTTP Client SDK code generator"; - command.SetHandler, List, bool, CancellationToken>(HandleCommandCall, outputOption, languageOption, descriptionOption, backingStoreOption, classOption, logLevelOption, namespaceOption, serializerOption, deserializerOption, cleanOutputOption); + command.SetHandler, List, bool, List, CancellationToken>(HandleCommandCall, outputOption, languageOption, descriptionOption, backingStoreOption, classOption, logLevelOption, namespaceOption, serializerOption, deserializerOption, cleanOutputOption, structuredMimeTypesOption); return command; } private void AssignIfNotNullOrEmpty(string input, Action assignment) { if (!string.IsNullOrEmpty(input)) assignment.Invoke(Configuration, input); } - private async Task HandleCommandCall(string output, GenerationLanguage language, string openapi, bool backingstore, string classname, LogLevel loglevel, string namespacename, List serializer, List deserializer, bool cleanOutput, CancellationToken cancellationToken) { + private async Task HandleCommandCall(string output, GenerationLanguage language, string openapi, bool backingstore, string classname, LogLevel loglevel, string namespacename, List serializer, List deserializer, bool cleanOutput, List structuredMimeTypes, CancellationToken cancellationToken) { AssignIfNotNullOrEmpty(output, (c, s) => c.OutputPath = s); AssignIfNotNullOrEmpty(openapi, (c, s) => c.OpenAPIFilePath = s); AssignIfNotNullOrEmpty(classname, (c, s) => c.ClientClassName = s); @@ -103,9 +105,13 @@ private async Task HandleCommandCall(string output, GenerationLanguage lang Configuration.UsesBackingStore = backingstore; Configuration.Language = language; if(serializer?.Any() ?? false) - Configuration.Serializers.AddRange(serializer.Select(x => x.TrimQuotes())); + Configuration.Serializers = serializer.Select(x => x.TrimQuotes()).ToHashSet(StringComparer.OrdinalIgnoreCase); if(deserializer?.Any() ?? false) - Configuration.Deserializers.AddRange(deserializer.Select(x => x.TrimQuotes())); + Configuration.Deserializers = deserializer.Select(x => x.TrimQuotes()).ToHashSet(StringComparer.OrdinalIgnoreCase); + if(structuredMimeTypes?.Any() ?? false) + Configuration.StructuredMimeTypes = structuredMimeTypes.SelectMany(x => x.Split(new char[] {' ', ';'})) + .Select(x => x.TrimQuotes()) + .ToHashSet(StringComparer.OrdinalIgnoreCase); #if DEBUG loglevel = loglevel > LogLevel.Debug ? LogLevel.Debug : loglevel; From 6a15549665438134ee3aa3a1f276b48890b2f79e Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 30 May 2022 15:22:14 -0400 Subject: [PATCH 2/7] - adds documentaion for the new mime parameter Signed-off-by: Vincent Biret --- docs/using.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/using.md b/docs/using.md index dfdd89041b..8525e65d7e 100644 --- a/docs/using.md +++ b/docs/using.md @@ -22,6 +22,7 @@ kiota (--openapi | -d) [(--serializer | -s) ] [(--deserializer | --ds) ] [--clean-output | --co] + [(--structured-mime-types | -m) ] ``` ## Mandatory parameters @@ -170,6 +171,24 @@ One or more module names that implements `ISerializationWriterFactory`. kiota --serializer Contoso.Json.CustomSerializer ``` +### `--structured-mime-types (-m)` + +The MIME types to use for structured data model generation. Accepts multiple values. + +Default values : + +- `application/json` +- `application/xml` +- `text/plain` +- `text/xml` +- `text/yaml` + +> Note: Only request body types or response types with a defined schema will generate models, other entries will default back to stream/byte array. + +#### Accepted values + +Any valid MIME type which will match a request body type or a response type in the OpenAPI description. + ## Examples ```shell From 2640ede91d71d8f9fe09773f1ad637b054eb0c28 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 30 May 2022 15:37:03 -0400 Subject: [PATCH 3/7] - passed the structured types parameter to all the layers Signed-off-by: Vincent Biret --- .../Extensions/OpenApiOperationExtensions.cs | 20 +++++++------------ .../OpenApiUrlTreeNodeExtensions.cs | 6 +++--- src/Kiota.Builder/KiotaBuilder.cs | 16 +++++++-------- .../OpenApiOperationExtensionsTests.cs | 7 ++++--- 4 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs index 729ab83a3b..d9dfd7e4dd 100644 --- a/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs @@ -7,28 +7,22 @@ namespace Kiota.Builder.Extensions { public static class OpenApiOperationExtensions { private static readonly HashSet successCodes = new(StringComparer.OrdinalIgnoreCase) {"200", "201", "202"}; //204 excluded as it won't have a schema - [Obsolete("pass the value from configuration instead")] - private static readonly HashSet structuredMimeTypes = new (StringComparer.OrdinalIgnoreCase) { - "application/json", - "application/xml", - "text/plain", - "text/xml", - "text/yaml", - }; /// /// cleans application/vnd.github.mercy-preview+json to application/json /// private static readonly Regex vendorSpecificCleanup = new(@"[^/]+\+", RegexOptions.Compiled); - public static OpenApiSchema GetResponseSchema(this OpenApiOperation operation) + public static OpenApiSchema GetResponseSchema(this OpenApiOperation operation, HashSet structuredMimeTypes) { // Return Schema that represents all the possible success responses! var schemas = operation.Responses.Where(r => successCodes.Contains(r.Key)) - .SelectMany(re => re.Value.GetResponseSchemas()); + .SelectMany(re => re.Value.GetResponseSchemas(structuredMimeTypes)); return schemas.FirstOrDefault(); } - public static IEnumerable GetResponseSchemas(this OpenApiResponse response) + public static IEnumerable GetResponseSchemas(this OpenApiResponse response, HashSet structuredMimeTypes) { + if(!(structuredMimeTypes?.Any() ?? true)) + throw new ArgumentNullException(nameof(structuredMimeTypes)); var schemas = response.Content .Where(c => !string.IsNullOrEmpty(c.Key)) .Select(c => (Key: c.Key.Split(';', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(), c.Value)) @@ -38,9 +32,9 @@ public static IEnumerable GetResponseSchemas(this OpenApiResponse return schemas; } - public static OpenApiSchema GetResponseSchema(this OpenApiResponse response) + public static OpenApiSchema GetResponseSchema(this OpenApiResponse response, HashSet structuredMimeTypes) { - return response.GetResponseSchemas().FirstOrDefault(); + return response.GetResponseSchemas(structuredMimeTypes).FirstOrDefault(); } } } diff --git a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs index be65f5ba93..9cc964b4ee 100644 --- a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs @@ -64,10 +64,10 @@ public static IEnumerable GetPathParametersForCurrentSegment(t /// /// Returns the class name for the node with more or less precision depending on the provided arguments /// - public static string GetClassName(this OpenApiUrlTreeNode currentNode, string suffix = default, string prefix = default, OpenApiOperation operation = default, OpenApiResponse response = default, OpenApiSchema schema = default) { + public static string GetClassName(this OpenApiUrlTreeNode currentNode, HashSet structuredMimeTypes, string suffix = default, string prefix = default, OpenApiOperation operation = default, OpenApiResponse response = default, OpenApiSchema schema = default) { var rawClassName = schema?.Reference?.GetClassName() ?? - response?.GetResponseSchema()?.Reference?.GetClassName() ?? - operation?.GetResponseSchema()?.Reference?.GetClassName() ?? + response?.GetResponseSchema(structuredMimeTypes)?.Reference?.GetClassName() ?? + operation?.GetResponseSchema(structuredMimeTypes)?.Reference?.GetClassName() ?? CleanupParametersFromPath(currentNode.Segment)?.ReplaceValueIdentifier(); if((currentNode?.DoesNodeBelongToItemSubnamespace() ?? false) && idClassNameCleanup.IsMatch(rawClassName)) { rawClassName = idClassNameCleanup.Replace(rawClassName, string.Empty); diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index d954deb6d0..ebf7609bc6 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -302,7 +302,7 @@ private void CreateRequestBuilderClass(CodeNamespace currentNamespace, OpenApiUr else { var targetNS = currentNode.DoesNodeBelongToItemSubnamespace() ? currentNamespace.EnsureItemNamespace() : currentNamespace; - var className = currentNode.DoesNodeBelongToItemSubnamespace() ? currentNode.GetClassName(itemRequestBuilderSuffix) :currentNode.GetClassName(requestBuilderSuffix); + var className = currentNode.DoesNodeBelongToItemSubnamespace() ? currentNode.GetClassName(config.StructuredMimeTypes, itemRequestBuilderSuffix) :currentNode.GetClassName(config.StructuredMimeTypes, requestBuilderSuffix); codeClass = targetNS.AddClass(new CodeClass { Name = className.CleanupSymbolName(), Kind = CodeClassKind.RequestBuilder, @@ -315,7 +315,7 @@ private void CreateRequestBuilderClass(CodeNamespace currentNamespace, OpenApiUr // Add properties for children foreach (var child in currentNode.Children) { - var propIdentifier = child.Value.GetClassName(); + var propIdentifier = child.Value.GetClassName(config.StructuredMimeTypes); var propType = child.Value.DoesNodeBelongToItemSubnamespace() ? propIdentifier + itemRequestBuilderSuffix : propIdentifier + requestBuilderSuffix; if (child.Value.IsPathSegmentWithSingleSimpleParameter()) { @@ -629,7 +629,7 @@ private static CodeType GetPrimitiveType(OpenApiSchema typeSchema, string childT private void AddErrorMappingsForExecutorMethod(OpenApiUrlTreeNode currentNode, OpenApiOperation operation, CodeMethod executorMethod) { foreach(var response in operation.Responses.Where(x => errorStatusCodes.Contains(x.Key))) { var errorCode = response.Key.ToUpperInvariant(); - var errorSchema = response.Value.GetResponseSchema(); + var errorSchema = response.Value.GetResponseSchema(config.StructuredMimeTypes); if(errorSchema != null) { var parentElement = string.IsNullOrEmpty(response.Value.Reference?.Id) && string.IsNullOrEmpty(errorSchema?.Reference?.Id) ? executorMethod as CodeElement @@ -654,7 +654,7 @@ private void CreateOperationMethods(OpenApiUrlTreeNode currentNode, OperationTyp Description = "Configuration for the request such as headers, query parameters, and middleware options.", }).First(); - var schema = operation.GetResponseSchema(); + var schema = operation.GetResponseSchema(config.StructuredMimeTypes); var method = (HttpMethod)Enum.Parse(typeof(HttpMethod), operationType.ToString()); var executorMethod = parentClass.AddMethod(new CodeMethod { Name = operationType.ToString(), @@ -817,7 +817,7 @@ private string GetModelsNamespaceNameFromReferenceId(string referenceId) { return $"{modelsNamespace.Name}{namespaceSuffix}"; } private CodeType CreateModelDeclarationAndType(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeNamespace codeNamespace, string classNameSuffix = "", OpenApiResponse response = default, string typeNameForInlineSchema = "") { - var className = string.IsNullOrEmpty(typeNameForInlineSchema) ? currentNode.GetClassName(operation: operation, suffix: classNameSuffix, response: response, schema: schema).CleanupSymbolName() : typeNameForInlineSchema; + var className = string.IsNullOrEmpty(typeNameForInlineSchema) ? currentNode.GetClassName(config.StructuredMimeTypes, operation: operation, suffix: classNameSuffix, response: response, schema: schema).CleanupSymbolName() : typeNameForInlineSchema; var codeDeclaration = AddModelDeclarationIfDoesntExist(currentNode, schema, className, codeNamespace); return new CodeType { TypeDefinition = codeDeclaration, @@ -835,7 +835,7 @@ private CodeTypeBase CreateInheritedModelDeclaration(OpenApiUrlTreeNode currentN var shortestNamespace = string.IsNullOrEmpty(referenceId) ? codeNamespaceFromParent : rootNamespace.FindNamespaceByName(shortestNamespaceName); if(shortestNamespace == null) shortestNamespace = rootNamespace.AddNamespace(shortestNamespaceName); - className = (currentSchema.GetSchemaName() ?? currentNode.GetClassName(operation: operation, schema: schema)).CleanupSymbolName(); + className = (currentSchema.GetSchemaName() ?? currentNode.GetClassName(config.StructuredMimeTypes, operation: operation, schema: schema)).CleanupSymbolName(); codeDeclaration = AddModelDeclarationIfDoesntExist(currentNode, currentSchema, className, shortestNamespace, codeDeclaration as CodeClass); } @@ -862,7 +862,7 @@ private static string GetReferenceIdFromOriginalSchema(OpenApiSchema schema, Ope ?.Reference?.Id; } private CodeTypeBase CreateComposedModelDeclaration(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, string suffixForInlineSchema, CodeNamespace codeNamespace) { - var typeName = currentNode.GetClassName(operation: operation, suffix: suffixForInlineSchema, schema: schema).CleanupSymbolName(); + var typeName = currentNode.GetClassName(config.StructuredMimeTypes, operation: operation, suffix: suffixForInlineSchema, schema: schema).CleanupSymbolName(); var (unionType, schemas) = (schema.IsOneOf(), schema.IsAnyOf()) switch { (true, false) => (new CodeExclusionType { Name = typeName, @@ -1032,7 +1032,7 @@ private CodeTypeBase GetCodeTypeForMapping(OpenApiUrlTreeNode currentNode, strin logger.LogWarning("Discriminator {componentKey} not found in the OpenAPI document.", componentKey); return null; } - var className = currentNode.GetClassName(schema: discriminatorSchema).CleanupSymbolName(); + var className = currentNode.GetClassName(config.StructuredMimeTypes, schema: discriminatorSchema).CleanupSymbolName(); var shouldInherit = discriminatorSchema.AllOf.Any(x => currentSchema.Reference?.Id.Equals(x.Reference?.Id, StringComparison.OrdinalIgnoreCase) ?? false); var codeClass = AddModelDeclarationIfDoesntExist(currentNode, discriminatorSchema, className, currentNamespace, shouldInherit ? currentClass : null); return new CodeType { diff --git a/tests/Kiota.Builder.Tests/Extensions/OpenApiOperationExtensionsTests.cs b/tests/Kiota.Builder.Tests/Extensions/OpenApiOperationExtensionsTests.cs index 39a86025df..8e0b78b5e4 100644 --- a/tests/Kiota.Builder.Tests/Extensions/OpenApiOperationExtensionsTests.cs +++ b/tests/Kiota.Builder.Tests/Extensions/OpenApiOperationExtensionsTests.cs @@ -39,9 +39,10 @@ public void GetsResponseSchema() { }} } }; - Assert.NotNull(operation.GetResponseSchema()); - Assert.Null(operation2.GetResponseSchema()); - Assert.Null(operation3.GetResponseSchema()); + var defaultConfiguration = new GenerationConfiguration(); + Assert.NotNull(operation.GetResponseSchema(defaultConfiguration.StructuredMimeTypes)); + Assert.Null(operation2.GetResponseSchema(defaultConfiguration.StructuredMimeTypes)); + Assert.Null(operation3.GetResponseSchema(defaultConfiguration.StructuredMimeTypes)); } } From 6e68759c216debbf07cf0145da4133adc42c421b Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 30 May 2022 16:01:40 -0400 Subject: [PATCH 4/7] - aligns request body schema selection on response Signed-off-by: Vincent Biret --- .../Extensions/OpenApiOperationExtensions.cs | 12 ++++++++++-- src/Kiota.Builder/KiotaBuilder.cs | 9 +++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs index d9dfd7e4dd..780fe5170c 100644 --- a/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs @@ -19,11 +19,11 @@ public static OpenApiSchema GetResponseSchema(this OpenApiOperation operation, H return schemas.FirstOrDefault(); } - public static IEnumerable GetResponseSchemas(this OpenApiResponse response, HashSet structuredMimeTypes) + public static IEnumerable GetValidSchemas(this IDictionary source, HashSet structuredMimeTypes) { if(!(structuredMimeTypes?.Any() ?? true)) throw new ArgumentNullException(nameof(structuredMimeTypes)); - var schemas = response.Content + var schemas = source .Where(c => !string.IsNullOrEmpty(c.Key)) .Select(c => (Key: c.Key.Split(';', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(), c.Value)) .Where(c => structuredMimeTypes.Contains(c.Key) || structuredMimeTypes.Contains(vendorSpecificCleanup.Replace(c.Key, string.Empty))) @@ -32,6 +32,14 @@ public static IEnumerable GetResponseSchemas(this OpenApiResponse return schemas; } + public static IEnumerable GetResponseSchemas(this OpenApiResponse response, HashSet structuredMimeTypes) + { + return response.Content.GetValidSchemas(structuredMimeTypes); + } + public static IEnumerable GetRequestSchemas(this OpenApiRequestBody requestBody, HashSet structuredMimeTypes) + { + return requestBody.Content.GetValidSchemas(structuredMimeTypes); + } public static OpenApiSchema GetResponseSchema(this OpenApiResponse response, HashSet structuredMimeTypes) { return response.GetResponseSchemas(structuredMimeTypes).FirstOrDefault(); diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index ebf7609bc6..70d7cc32af 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -620,7 +620,6 @@ private static CodeType GetPrimitiveType(OpenApiSchema typeSchema, string childT IsExternal = isExternal, }; } - private const string RequestBodyBinaryContentType = "application/octet-stream"; private const string RequestBodyPlainTextContentType = "text/plain"; private static readonly HashSet noContentStatusCodes = new() { "201", "202", "204" }; private static readonly HashSet errorStatusCodes = new(Enumerable.Range(400, 599).Select(x => x.ToString()) @@ -748,10 +747,8 @@ private static void SetPathAndQueryParameters(CodeMethod target, OpenApiUrlTreeN } private void AddRequestBuilderMethodParameters(OpenApiUrlTreeNode currentNode, OperationType operationType, OpenApiOperation operation, CodeClass parameterClass, CodeClass requestConfigClass, CodeMethod method) { - var nonBinaryRequestBody = operation.RequestBody?.Content?.FirstOrDefault(x => !RequestBodyBinaryContentType.Equals(x.Key, StringComparison.OrdinalIgnoreCase)); - if (nonBinaryRequestBody.HasValue && nonBinaryRequestBody.Value.Value != null) + if (operation.RequestBody?.Content?.GetValidSchemas(config.StructuredMimeTypes)?.FirstOrDefault() is OpenApiSchema requestBodySchema) { - var requestBodySchema = nonBinaryRequestBody.Value.Value.Schema; var requestBodyType = CreateModelDeclarations(currentNode, requestBodySchema, operation, method, $"{operationType}RequestBody"); method.AddParameter(new CodeParameter { Name = "body", @@ -760,8 +757,8 @@ private void AddRequestBuilderMethodParameters(OpenApiUrlTreeNode currentNode, O Kind = CodeParameterKind.RequestBody, Description = requestBodySchema.Description.CleanupDescription() }); - method.ContentType = nonBinaryRequestBody.Value.Key; - } else if (operation.RequestBody?.Content?.ContainsKey(RequestBodyBinaryContentType) ?? false) { + method.ContentType = operation.RequestBody.Content.First(x => x.Value.Schema == requestBodySchema).Key; + } else if (operation.RequestBody?.Content?.Any() ?? false) { var nParam = new CodeParameter { Name = "body", Optional = false, From 398f6c158ab54e36492198f28160f45b8d184f03 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 30 May 2022 16:04:13 -0400 Subject: [PATCH 5/7] - code linting Signed-off-by: Vincent Biret --- .../Extensions/OpenApiOperationExtensions.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs index 780fe5170c..31f5a261a9 100644 --- a/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs @@ -15,7 +15,7 @@ public static OpenApiSchema GetResponseSchema(this OpenApiOperation operation, H { // Return Schema that represents all the possible success responses! var schemas = operation.Responses.Where(r => successCodes.Contains(r.Key)) - .SelectMany(re => re.Value.GetResponseSchemas(structuredMimeTypes)); + .SelectMany(re => re.Value.Content.GetValidSchemas(structuredMimeTypes)); return schemas.FirstOrDefault(); } @@ -32,17 +32,9 @@ public static IEnumerable GetValidSchemas(this IDictionary GetResponseSchemas(this OpenApiResponse response, HashSet structuredMimeTypes) - { - return response.Content.GetValidSchemas(structuredMimeTypes); - } - public static IEnumerable GetRequestSchemas(this OpenApiRequestBody requestBody, HashSet structuredMimeTypes) - { - return requestBody.Content.GetValidSchemas(structuredMimeTypes); - } public static OpenApiSchema GetResponseSchema(this OpenApiResponse response, HashSet structuredMimeTypes) { - return response.GetResponseSchemas(structuredMimeTypes).FirstOrDefault(); + return response.Content.GetValidSchemas(structuredMimeTypes).FirstOrDefault(); } } } From dc903722b6c9c7ea53151940e3b36e9504577549 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 31 May 2022 10:07:12 -0400 Subject: [PATCH 6/7] - adds unit tests for request body normalization Signed-off-by: Vincent Biret --- .../Extensions/OpenApiOperationExtensions.cs | 2 +- src/kiota/KiotaHost.cs | 2 +- .../OpenApiOperationExtensionsTests.cs | 92 +++++----- .../Kiota.Builder.Tests/KiotaBuilderTests.cs | 171 ++++++++++++++---- 4 files changed, 188 insertions(+), 79 deletions(-) diff --git a/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs index 31f5a261a9..29228bea88 100644 --- a/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs @@ -21,7 +21,7 @@ public static OpenApiSchema GetResponseSchema(this OpenApiOperation operation, H } public static IEnumerable GetValidSchemas(this IDictionary source, HashSet structuredMimeTypes) { - if(!(structuredMimeTypes?.Any() ?? true)) + if(!(structuredMimeTypes?.Any() ?? false)) throw new ArgumentNullException(nameof(structuredMimeTypes)); var schemas = source .Where(c => !string.IsNullOrEmpty(c.Key)) diff --git a/src/kiota/KiotaHost.cs b/src/kiota/KiotaHost.cs index a4e776d29a..e8f5caee12 100644 --- a/src/kiota/KiotaHost.cs +++ b/src/kiota/KiotaHost.cs @@ -109,7 +109,7 @@ private async Task HandleCommandCall(string output, GenerationLanguage lang if(deserializer?.Any() ?? false) Configuration.Deserializers = deserializer.Select(x => x.TrimQuotes()).ToHashSet(StringComparer.OrdinalIgnoreCase); if(structuredMimeTypes?.Any() ?? false) - Configuration.StructuredMimeTypes = structuredMimeTypes.SelectMany(x => x.Split(new char[] {' ', ';'})) + Configuration.StructuredMimeTypes = structuredMimeTypes.SelectMany(x => x.Split(new char[] {' '})) .Select(x => x.TrimQuotes()) .ToHashSet(StringComparer.OrdinalIgnoreCase); diff --git a/tests/Kiota.Builder.Tests/Extensions/OpenApiOperationExtensionsTests.cs b/tests/Kiota.Builder.Tests/Extensions/OpenApiOperationExtensionsTests.cs index 8e0b78b5e4..9b11767438 100644 --- a/tests/Kiota.Builder.Tests/Extensions/OpenApiOperationExtensionsTests.cs +++ b/tests/Kiota.Builder.Tests/Extensions/OpenApiOperationExtensionsTests.cs @@ -1,49 +1,55 @@ +using System; using System.Collections.Generic; using Microsoft.OpenApi.Models; using Xunit; -namespace Kiota.Builder.Extensions.Tests { - public class OpenApiOperationExtensionsTests { - [Fact] - public void GetsResponseSchema() { - var operation = new OpenApiOperation{ - Responses = new() { - { "200", new() { - Content = new Dictionary { - {"application/json", new() { - Schema = new() - }} - } - }} - } - }; - var operation2 = new OpenApiOperation{ - Responses = new() { - { "400", new() { - Content = new Dictionary { - {"application/json", new() { - Schema = new() - }} - } - }} - } - }; - var operation3 = new OpenApiOperation{ - Responses = new() { - { "200", new() { - Content = new Dictionary { - {"application/invalid", new() { - Schema = new() - }} - } - }} - } - }; - var defaultConfiguration = new GenerationConfiguration(); - Assert.NotNull(operation.GetResponseSchema(defaultConfiguration.StructuredMimeTypes)); - Assert.Null(operation2.GetResponseSchema(defaultConfiguration.StructuredMimeTypes)); - Assert.Null(operation3.GetResponseSchema(defaultConfiguration.StructuredMimeTypes)); - } - +namespace Kiota.Builder.Extensions.Tests; +public class OpenApiOperationExtensionsTests { + [Fact] + public void GetsResponseSchema() { + var operation = new OpenApiOperation{ + Responses = new() { + { "200", new() { + Content = new Dictionary { + {"application/json", new() { + Schema = new() + }} + } + }} + } + }; + var operation2 = new OpenApiOperation{ + Responses = new() { + { "400", new() { + Content = new Dictionary { + {"application/json", new() { + Schema = new() + }} + } + }} + } + }; + var operation3 = new OpenApiOperation{ + Responses = new() { + { "200", new() { + Content = new Dictionary { + {"application/invalid", new() { + Schema = new() + }} + } + }} + } + }; + var defaultConfiguration = new GenerationConfiguration(); + Assert.NotNull(operation.GetResponseSchema(defaultConfiguration.StructuredMimeTypes)); + Assert.Null(operation2.GetResponseSchema(defaultConfiguration.StructuredMimeTypes)); + Assert.Null(operation3.GetResponseSchema(defaultConfiguration.StructuredMimeTypes)); + } + [Fact] + public void Defensive() { + var source = new Dictionary(); + Assert.Empty(source.GetValidSchemas(new HashSet() { "application/json" })); + Assert.Throws(() => source.GetValidSchemas(new HashSet())); + Assert.Throws(() => source.GetValidSchemas(null)); } } diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs index 31e9f70520..ccd6e8d8a8 100644 --- a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs +++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs @@ -2056,40 +2056,48 @@ public void ModelsUseDescriptionWhenAvailable(){ Assert.NotNull(responseClass); Assert.Equal("some description", responseClass.Description); } - [InlineData("application/json", "204", true, "void")] - [InlineData("application/json", "204", false, "void")] - [InlineData("application/json", "200", true, "Myobject")] - [InlineData("application/json", "200", false, "binary")] - [InlineData("application/xml", "204", true, "void")] - [InlineData("application/xml", "204", false, "void")] - [InlineData("application/xml", "200", true, "Myobject")] - [InlineData("application/xml", "200", false, "binary")] - [InlineData("text/xml", "204", true, "void")] - [InlineData("text/xml", "204", false, "void")] - [InlineData("text/xml", "200", true, "Myobject")] - [InlineData("text/xml", "200", false, "binary")] - [InlineData("text/yaml", "204", true, "void")] - [InlineData("text/yaml", "204", false, "void")] - [InlineData("text/yaml", "200", true, "Myobject")] - [InlineData("text/yaml", "200", false, "binary")] - [InlineData("application/octet-stream", "204", true, "void")] - [InlineData("application/octet-stream", "204", false, "void")] - [InlineData("application/octet-stream", "200", true, "binary")] - [InlineData("application/octet-stream", "200", false, "binary")] - [InlineData("text/html", "204", true, "void")] - [InlineData("text/html", "204", false, "void")] - [InlineData("text/html", "200", true, "binary")] - [InlineData("text/html", "200", false, "binary")] - [InlineData("*/*", "204", true, "void")] - [InlineData("*/*", "204", false, "void")] - [InlineData("*/*", "200", true, "binary")] - [InlineData("*/*", "200", false, "binary")] - [InlineData("text/plain", "204", true, "void")] - [InlineData("text/plain", "204", false, "void")] - [InlineData("text/plain", "200", true, "Myobject")] - [InlineData("text/plain", "200", false, "string")] + [InlineData("application/json", "204", true, "default", "void")] + [InlineData("application/json", "204", false, "default", "void")] + [InlineData("application/json", "200", true, "default", "Myobject")] + [InlineData("application/json", "200", false, "default", "binary")] + [InlineData("application/xml", "204", true, "default", "void")] + [InlineData("application/xml", "204", false, "default", "void")] + [InlineData("application/xml", "200", true, "default", "Myobject")] + [InlineData("application/xml", "200", false, "default", "binary")] + [InlineData("text/xml", "204", true, "default", "void")] + [InlineData("text/xml", "204", false, "default", "void")] + [InlineData("text/xml", "200", true, "default", "Myobject")] + [InlineData("text/xml", "200", false, "default", "binary")] + [InlineData("text/yaml", "204", true, "default", "void")] + [InlineData("text/yaml", "204", false, "default", "void")] + [InlineData("text/yaml", "200", true, "default", "Myobject")] + [InlineData("text/yaml", "200", false, "default", "binary")] + [InlineData("application/octet-stream", "204", true, "default", "void")] + [InlineData("application/octet-stream", "204", false, "default", "void")] + [InlineData("application/octet-stream", "200", true, "default", "binary")] + [InlineData("application/octet-stream", "200", false, "default", "binary")] + [InlineData("text/html", "204", true, "default", "void")] + [InlineData("text/html", "204", false, "default", "void")] + [InlineData("text/html", "200", true, "default", "binary")] + [InlineData("text/html", "200", false, "default", "binary")] + [InlineData("*/*", "204", true, "default", "void")] + [InlineData("*/*", "204", false, "default", "void")] + [InlineData("*/*", "200", true, "default", "binary")] + [InlineData("*/*", "200", false, "default", "binary")] + [InlineData("text/plain", "204", true, "default", "void")] + [InlineData("text/plain", "204", false, "default", "void")] + [InlineData("text/plain", "200", true, "default", "Myobject")] + [InlineData("text/plain", "200", false, "default", "string")] + [InlineData("text/plain", "204", true, "application/json", "void")] + [InlineData("text/plain", "204", false, "application/json", "void")] + [InlineData("text/plain", "200", true, "application/json", "string")] + [InlineData("text/plain", "200", false, "application/json", "string")] + [InlineData("text/yaml", "204", true, "application/json", "void")] + [InlineData("text/yaml", "204", false, "application/json", "void")] + [InlineData("text/yaml", "200", true, "application/json", "binary")] + [InlineData("text/yaml", "200", false, "application/json", "binary")] [Theory] - public void GeneratesTheRightReturnTypeBasedOnContentAndStatus(string contentType, string statusCode, bool addModel, string returnType) { + public void GeneratesTheRightReturnTypeBasedOnContentAndStatus(string contentType, string statusCode, bool addModel, string acceptedContentType, string returnType) { var myObjectSchema = new OpenApiSchema { Type = "object", Properties = new Dictionary { @@ -2133,7 +2141,16 @@ public void GeneratesTheRightReturnTypeBasedOnContentAndStatus(string contentTyp } }; var mockLogger = new Mock>(); - var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration() { ClientClassName = "TestClient", ClientNamespaceName = "TestSdk", ApiRootUrl = "https://localhost" }); + var builder = new KiotaBuilder( + mockLogger.Object, + new GenerationConfiguration() { + ClientClassName = "TestClient", + ClientNamespaceName = "TestSdk", + ApiRootUrl = "https://localhost", + StructuredMimeTypes = acceptedContentType.Equals("default", StringComparison.OrdinalIgnoreCase) ? + new GenerationConfiguration().StructuredMimeTypes: + new (StringComparer.OrdinalIgnoreCase) { acceptedContentType } + }); var node = builder.CreateUriSpace(document); var codeModel = builder.CreateSourceModel(node); var rbNS = codeModel.FindNamespaceByName("TestSdk.Answer"); @@ -2144,6 +2161,92 @@ public void GeneratesTheRightReturnTypeBasedOnContentAndStatus(string contentTyp Assert.NotNull(executor); Assert.Equal(returnType, executor.ReturnType.Name); } + [InlineData("application/json", true, "default", "Myobject")] + [InlineData("application/json", false, "default", "binary")] + [InlineData("application/xml", false, "default", "binary")] + [InlineData("application/xml", true, "default", "Myobject")] + [InlineData("text/xml", false, "default", "binary")] + [InlineData("text/xml", true, "default", "Myobject")] + [InlineData("text/yaml", false, "default", "binary")] + [InlineData("text/yaml", true, "default", "Myobject")] + [InlineData("application/octet-stream", true, "default", "binary")] + [InlineData("application/octet-stream", false, "default", "binary")] + [InlineData("text/html", true, "default", "binary")] + [InlineData("text/html", false, "default", "binary")] + [InlineData("*/*", true, "default", "binary")] + [InlineData("*/*", false, "default", "binary")] + [InlineData("text/plain", false, "default", "binary")] + [InlineData("text/plain", true, "default", "Myobject")] + [InlineData("text/plain", true, "application/json", "binary")] + [InlineData("text/plain", false, "application/json", "binary")] + [InlineData("text/yaml", true, "application/json", "binary")] + [InlineData("text/yaml", false, "application/json", "binary")] + [Theory] + public void GeneratesTheRightParameterTypeBasedOnContentAndStatus(string contentType, bool addModel, string acceptedContentType, string parameterType) { + var myObjectSchema = new OpenApiSchema { + Type = "object", + Properties = new Dictionary { + { + "id", new OpenApiSchema { + Type = "string", + } + } + }, + Reference = new OpenApiReference { + Id = "myobject", + Type = ReferenceType.Schema + }, + UnresolvedReference = false + }; + var document = new OpenApiDocument() { + Paths = new OpenApiPaths() { + ["answer"] = new OpenApiPathItem() { + Operations = { + [OperationType.Post] = new OpenApiOperation() { + RequestBody = new OpenApiRequestBody { + Content = { + [contentType] = new OpenApiMediaType { + Schema = addModel ? myObjectSchema : null + } + } + }, + Responses = new OpenApiResponses + { + ["204"] = new OpenApiResponse {}, + } + } + } + } + }, + Components = new() { + Schemas = new Dictionary { + { + "myobject", myObjectSchema + } + } + } + }; + var mockLogger = new Mock>(); + var builder = new KiotaBuilder( + mockLogger.Object, + new GenerationConfiguration() { + ClientClassName = "TestClient", + ClientNamespaceName = "TestSdk", + ApiRootUrl = "https://localhost", + StructuredMimeTypes = acceptedContentType.Equals("default", StringComparison.OrdinalIgnoreCase) ? + new GenerationConfiguration().StructuredMimeTypes: + new (StringComparer.OrdinalIgnoreCase) { acceptedContentType } + }); + var node = builder.CreateUriSpace(document); + var codeModel = builder.CreateSourceModel(node); + var rbNS = codeModel.FindNamespaceByName("TestSdk.Answer"); + Assert.NotNull(rbNS); + var rbClass = rbNS.Classes.FirstOrDefault(x => x.IsOfKind(CodeClassKind.RequestBuilder)); + Assert.NotNull(rbClass); + var executor = rbClass.Methods.FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestExecutor)); + Assert.NotNull(executor); + Assert.Equal(parameterType, executor.Parameters.OfKind(CodeParameterKind.RequestBody).Type.Name); + } [Fact] public void DoesntGenerateVoidExecutorOnMixed204(){ var myObjectSchema = new OpenApiSchema { From 1704c85c0040f08928fca5eba16b9a583085f03f Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 31 May 2022 10:09:15 -0400 Subject: [PATCH 7/7] - adds changelog entry for mime types evaluation Signed-off-by: Vincent Biret --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e210a4351..f3a9b1b599 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added a parameter to specify why mime types to evaluate for models. [#134](https://github.com/microsoft/kiota/issues/134) - Added an explicit error message for external references in the schema. [#1580](https://github.com/microsoft/kiota/issues/1580) ### Changed +- Aligned mime types model generation behaviour for request bodies on response content. [#134](https://github.com/microsoft/kiota/issues/134) + ## [0.2.1] - 2022-05-30 ### Added