diff --git a/src/Kiota.Builder/CodeDOM/CodeClass.cs b/src/Kiota.Builder/CodeDOM/CodeClass.cs
index 9b9ed2a998..3d553f9581 100644
--- a/src/Kiota.Builder/CodeDOM/CodeClass.cs
+++ b/src/Kiota.Builder/CodeDOM/CodeClass.cs
@@ -2,128 +2,129 @@
using System.Collections.Generic;
using System.Linq;
-namespace Kiota.Builder
+namespace Kiota.Builder;
+
+public enum CodeClassKind {
+ Custom,
+ RequestBuilder,
+ Model,
+ QueryParameters,
+ ///
+ /// A single parameter to be provided by the SDK user which will contain query parameters, request body, options, etc.
+ /// Only used for languages that do not support overloads or optional parameters like go.
+ ///
+ ParameterSet,
+}
+///
+/// CodeClass represents an instance of a Class to be generated in source code
+///
+public class CodeClass : CodeBlock, IDocumentedElement, ITypeDefinition
{
- public enum CodeClassKind {
- Custom,
- RequestBuilder,
- Model,
- QueryParameters,
- ///
- /// A single parameter to be provided by the SDK user which will contain query parameters, request body, options, etc.
- /// Only used for languages that do not support overloads or optional parameters like go.
- ///
- ParameterSet,
+ private string name;
+ public CodeClass():base()
+ {
+ StartBlock = new Declaration() { Parent = this};
+ EndBlock = new End() { Parent = this };
}
+ public CodeClassKind ClassKind { get; set; } = CodeClassKind.Custom;
+
+ public bool IsErrorDefinition { get; set; }
+
+ public string Description {get; set;}
///
- /// CodeClass represents an instance of a Class to be generated in source code
+ /// Name of Class
///
- public class CodeClass : CodeBlock, IDocumentedElement, ITypeDefinition
+ public override string Name
{
- private string name;
- public CodeClass():base()
+ get => name;
+ set
{
- StartBlock = new Declaration() { Parent = this};
- EndBlock = new End() { Parent = this };
- }
- public CodeClassKind ClassKind { get; set; } = CodeClassKind.Custom;
-
- public string Description {get; set;}
- ///
- /// Name of Class
- ///
- public override string Name
- {
- get => name;
- set
- {
- name = value;
- StartBlock.Name = name;
- }
+ name = value;
+ StartBlock.Name = name;
}
+ }
- public void SetIndexer(CodeIndexer indexer)
- {
- if(indexer == null)
- throw new ArgumentNullException(nameof(indexer));
- if(InnerChildElements.Values.OfType().Any())
- throw new InvalidOperationException("this class already has an indexer, remove it first");
- AddRange(indexer);
- }
+ public void SetIndexer(CodeIndexer indexer)
+ {
+ if(indexer == null)
+ throw new ArgumentNullException(nameof(indexer));
+ if(InnerChildElements.Values.OfType().Any())
+ throw new InvalidOperationException("this class already has an indexer, remove it first");
+ AddRange(indexer);
+ }
- public IEnumerable AddProperty(params CodeProperty[] properties)
- {
- if(properties == null || properties.Any(x => x == null))
- throw new ArgumentNullException(nameof(properties));
- if(!properties.Any())
- throw new ArgumentOutOfRangeException(nameof(properties));
- return AddRange(properties);
- }
- public CodeProperty GetPropertyOfKind(CodePropertyKind kind) =>
- Properties.FirstOrDefault(x => x.IsOfKind(kind));
- public IEnumerable Properties => InnerChildElements.Values.OfType();
- public IEnumerable Methods => InnerChildElements.Values.OfType();
+ public IEnumerable AddProperty(params CodeProperty[] properties)
+ {
+ if(properties == null || properties.Any(x => x == null))
+ throw new ArgumentNullException(nameof(properties));
+ if(!properties.Any())
+ throw new ArgumentOutOfRangeException(nameof(properties));
+ return AddRange(properties);
+ }
+ public CodeProperty GetPropertyOfKind(CodePropertyKind kind) =>
+ Properties.FirstOrDefault(x => x.IsOfKind(kind));
+ public IEnumerable Properties => InnerChildElements.Values.OfType();
+ public IEnumerable Methods => InnerChildElements.Values.OfType();
- public bool ContainsMember(string name)
- {
- return InnerChildElements.ContainsKey(name);
- }
+ public bool ContainsMember(string name)
+ {
+ return InnerChildElements.ContainsKey(name);
+ }
- public IEnumerable AddMethod(params CodeMethod[] methods)
- {
- if(methods == null || methods.Any(x => x == null))
- throw new ArgumentNullException(nameof(methods));
- if(!methods.Any())
- throw new ArgumentOutOfRangeException(nameof(methods));
- return AddRange(methods);
- }
+ public IEnumerable AddMethod(params CodeMethod[] methods)
+ {
+ if(methods == null || methods.Any(x => x == null))
+ throw new ArgumentNullException(nameof(methods));
+ if(!methods.Any())
+ throw new ArgumentOutOfRangeException(nameof(methods));
+ return AddRange(methods);
+ }
- public IEnumerable AddInnerClass(params CodeClass[] codeClasses)
- {
- if(codeClasses == null || codeClasses.Any(x => x == null))
- throw new ArgumentNullException(nameof(codeClasses));
- if(!codeClasses.Any())
- throw new ArgumentOutOfRangeException(nameof(codeClasses));
- return AddRange(codeClasses);
- }
- public CodeClass GetParentClass() {
- if(StartBlock is Declaration declaration)
- return declaration.Inherits?.TypeDefinition as CodeClass;
- else return null;
- }
-
- public CodeClass GetGreatestGrandparent(CodeClass startClassToSkip = null) {
- var parentClass = GetParentClass();
- if(parentClass == null)
- return startClassToSkip != null && startClassToSkip == this ? null : this;
- // we don't want to return the current class if this is the start node in the inheritance tree and doesn't have parent
- else
- return parentClass.GetGreatestGrandparent(startClassToSkip);
- }
+ public IEnumerable AddInnerClass(params CodeClass[] codeClasses)
+ {
+ if(codeClasses == null || codeClasses.Any(x => x == null))
+ throw new ArgumentNullException(nameof(codeClasses));
+ if(!codeClasses.Any())
+ throw new ArgumentOutOfRangeException(nameof(codeClasses));
+ return AddRange(codeClasses);
+ }
+ public CodeClass GetParentClass() {
+ if(StartBlock is Declaration declaration)
+ return declaration.Inherits?.TypeDefinition as CodeClass;
+ else return null;
+ }
+
+ public CodeClass GetGreatestGrandparent(CodeClass startClassToSkip = null) {
+ var parentClass = GetParentClass();
+ if(parentClass == null)
+ return startClassToSkip != null && startClassToSkip == this ? null : this;
+ // we don't want to return the current class if this is the start node in the inheritance tree and doesn't have parent
+ else
+ return parentClass.GetGreatestGrandparent(startClassToSkip);
+ }
- public bool IsOfKind(params CodeClassKind[] kinds) {
- return kinds?.Contains(ClassKind) ?? false;
- }
+ public bool IsOfKind(params CodeClassKind[] kinds) {
+ return kinds?.Contains(ClassKind) ?? false;
+ }
public class Declaration : BlockDeclaration
- {
- private CodeType inherits;
- public CodeType Inherits { get => inherits; set {
- EnsureElementsAreChildren(value);
- inherits = value;
- } }
- private readonly List implements = new ();
- public void AddImplements(params CodeType[] types) {
- if(types == null || types.Any(x => x == null))
- throw new ArgumentNullException(nameof(types));
- EnsureElementsAreChildren(types);
- implements.AddRange(types);
- }
- public IEnumerable Implements => implements;
+ {
+ private CodeType inherits;
+ public CodeType Inherits { get => inherits; set {
+ EnsureElementsAreChildren(value);
+ inherits = value;
+ } }
+ private readonly List implements = new ();
+ public void AddImplements(params CodeType[] types) {
+ if(types == null || types.Any(x => x == null))
+ throw new ArgumentNullException(nameof(types));
+ EnsureElementsAreChildren(types);
+ implements.AddRange(types);
}
+ public IEnumerable Implements => implements;
+ }
- public class End : BlockEnd
- {
- }
+ public class End : BlockEnd
+ {
}
}
diff --git a/src/Kiota.Builder/CodeDOM/CodeMethod.cs b/src/Kiota.Builder/CodeDOM/CodeMethod.cs
index a281a7fb5d..048b437b30 100644
--- a/src/Kiota.Builder/CodeDOM/CodeMethod.cs
+++ b/src/Kiota.Builder/CodeDOM/CodeMethod.cs
@@ -2,122 +2,126 @@
using System.Collections.Generic;
using System.Linq;
-namespace Kiota.Builder
+namespace Kiota.Builder;
+
+public enum CodeMethodKind
{
- public enum CodeMethodKind
- {
- Custom,
- IndexerBackwardCompatibility,
- RequestExecutor,
- RequestGenerator,
- Serializer,
- Deserializer,
- Constructor,
- Getter,
- Setter,
- ClientConstructor,
- RequestBuilderBackwardCompatibility,
- RequestBuilderWithParameters,
- RawUrlConstructor,
- NullCheck,
+ Custom,
+ IndexerBackwardCompatibility,
+ RequestExecutor,
+ RequestGenerator,
+ Serializer,
+ Deserializer,
+ Constructor,
+ Getter,
+ Setter,
+ ClientConstructor,
+ RequestBuilderBackwardCompatibility,
+ RequestBuilderWithParameters,
+ RawUrlConstructor,
+ NullCheck,
+}
+public enum HttpMethod {
+ Get,
+ Post,
+ Patch,
+ Put,
+ Delete,
+ Options,
+ Connect,
+ Head,
+ Trace
+}
+
+public class CodeMethod : CodeTerminal, ICloneable, IDocumentedElement
+{
+ public HttpMethod? HttpMethod {get;set;}
+ public CodeMethodKind MethodKind {get;set;} = CodeMethodKind.Custom;
+ public string ContentType { get; set; }
+ public AccessModifier Access {get;set;} = AccessModifier.Public;
+ private CodeTypeBase returnType;
+ public CodeTypeBase ReturnType {get => returnType;set {
+ EnsureElementsAreChildren(value);
+ returnType = value;
+ }}
+ private readonly List parameters = new ();
+ public void RemoveParametersByKind(params CodeParameterKind[] kinds) {
+ parameters.RemoveAll(p => p.IsOfKind(kinds));
}
- public enum HttpMethod {
- Get,
- Post,
- Patch,
- Put,
- Delete,
- Options,
- Connect,
- Head,
- Trace
+ public IEnumerable Parameters { get => parameters; }
+ public bool IsStatic {get;set;} = false;
+ public bool IsAsync {get;set;} = true;
+ public string Description {get; set;}
+ ///
+ /// The property this method accesses to when it's a getter or setter.
+ ///
+ public CodeProperty AccessedProperty { get; set; }
+ public bool IsOfKind(params CodeMethodKind[] kinds) {
+ return kinds?.Contains(MethodKind) ?? false;
}
+ public bool IsAccessor {
+ get => IsOfKind(CodeMethodKind.Getter, CodeMethodKind.Setter);
+ }
+ public bool IsSerializationMethod {
+ get => IsOfKind(CodeMethodKind.Serializer, CodeMethodKind.Deserializer);
+ }
+ public List SerializerModules { get; set; }
+ public List DeserializerModules { get; set; }
+ ///
+ /// Indicates whether this method is an overload for another method.
+ ///
+ public bool IsOverload { get { return OriginalMethod != null; } }
+ ///
+ /// Provides a reference to the original method that this method is an overload of.
+ ///
+ public CodeMethod OriginalMethod { get; set; }
+ ///
+ /// The original indexer codedom element this method replaces when it is of kind IndexerBackwardCompatibility.
+ ///
+ public CodeIndexer OriginalIndexer { get; set; }
+ ///
+ /// The base url for every request read from the servers property on the description.
+ /// Only provided for constructor on Api client
+ ///
+ public string BaseUrl { get; set; }
+ ///
+ /// Mapping of the error code and response types for this method.
+ ///
+ public Dictionary ErrorMappings { get; set; } = new ();
- public class CodeMethod : CodeTerminal, ICloneable, IDocumentedElement
+ public object Clone()
{
- public HttpMethod? HttpMethod {get;set;}
- public CodeMethodKind MethodKind {get;set;} = CodeMethodKind.Custom;
- public string ContentType { get; set; }
- public AccessModifier Access {get;set;} = AccessModifier.Public;
- private CodeTypeBase returnType;
- public CodeTypeBase ReturnType {get => returnType;set {
- EnsureElementsAreChildren(value);
- returnType = value;
- }}
- private readonly List parameters = new ();
- public void RemoveParametersByKind(params CodeParameterKind[] kinds) {
- parameters.RemoveAll(p => p.IsOfKind(kinds));
- }
- public IEnumerable Parameters { get => parameters; }
- public bool IsStatic {get;set;} = false;
- public bool IsAsync {get;set;} = true;
- public string Description {get; set;}
- ///
- /// The property this method accesses to when it's a getter or setter.
- ///
- public CodeProperty AccessedProperty { get; set; }
- public bool IsOfKind(params CodeMethodKind[] kinds) {
- return kinds?.Contains(MethodKind) ?? false;
- }
- public bool IsAccessor {
- get => IsOfKind(CodeMethodKind.Getter, CodeMethodKind.Setter);
- }
- public bool IsSerializationMethod {
- get => IsOfKind(CodeMethodKind.Serializer, CodeMethodKind.Deserializer);
- }
- public List SerializerModules { get; set; }
- public List DeserializerModules { get; set; }
- ///
- /// Indicates whether this method is an overload for another method.
- ///
- public bool IsOverload { get { return OriginalMethod != null; } }
- ///
- /// Provides a reference to the original method that this method is an overload of.
- ///
- public CodeMethod OriginalMethod { get; set; }
- ///
- /// The original indexer codedom element this method replaces when it is of kind IndexerBackwardCompatibility.
- ///
- public CodeIndexer OriginalIndexer { get; set; }
- ///
- /// The base url for every request read from the servers property on the description.
- /// Only provided for constructor on Api client
- ///
- public string BaseUrl { get; set; }
-
- public object Clone()
- {
- var method = new CodeMethod {
- MethodKind = MethodKind,
- ReturnType = ReturnType?.Clone() as CodeTypeBase,
- Name = Name.Clone() as string,
- HttpMethod = HttpMethod,
- IsAsync = IsAsync,
- Access = Access,
- IsStatic = IsStatic,
- Description = Description?.Clone() as string,
- ContentType = ContentType?.Clone() as string,
- BaseUrl = BaseUrl?.Clone() as string,
- AccessedProperty = AccessedProperty,
- SerializerModules = SerializerModules == null ? null : new (SerializerModules),
- DeserializerModules = DeserializerModules == null ? null : new (DeserializerModules),
- OriginalMethod = OriginalMethod,
- Parent = Parent,
- OriginalIndexer = OriginalIndexer,
- };
- if(Parameters?.Any() ?? false)
- method.AddParameter(Parameters.Select(x => x.Clone() as CodeParameter).ToArray());
- return method;
- }
+ var method = new CodeMethod {
+ MethodKind = MethodKind,
+ ReturnType = ReturnType?.Clone() as CodeTypeBase,
+ Name = Name.Clone() as string,
+ HttpMethod = HttpMethod,
+ IsAsync = IsAsync,
+ Access = Access,
+ IsStatic = IsStatic,
+ Description = Description?.Clone() as string,
+ ContentType = ContentType?.Clone() as string,
+ BaseUrl = BaseUrl?.Clone() as string,
+ AccessedProperty = AccessedProperty,
+ SerializerModules = SerializerModules == null ? null : new (SerializerModules),
+ DeserializerModules = DeserializerModules == null ? null : new (DeserializerModules),
+ OriginalMethod = OriginalMethod,
+ Parent = Parent,
+ OriginalIndexer = OriginalIndexer,
+ ErrorMappings = ErrorMappings == null ? null : new (ErrorMappings)
+ };
+ if(Parameters?.Any() ?? false)
+ method.AddParameter(Parameters.Select(x => x.Clone() as CodeParameter).ToArray());
+ return method;
+ }
- public void AddParameter(params CodeParameter[] methodParameters)
- {
- if(methodParameters == null || methodParameters.Any(x => x == null))
- throw new ArgumentNullException(nameof(methodParameters));
- if(!methodParameters.Any())
- throw new ArgumentOutOfRangeException(nameof(methodParameters));
- EnsureElementsAreChildren(methodParameters);
- parameters.AddRange(methodParameters);
- }
+ public void AddParameter(params CodeParameter[] methodParameters)
+ {
+ if(methodParameters == null || methodParameters.Any(x => x == null))
+ throw new ArgumentNullException(nameof(methodParameters));
+ if(!methodParameters.Any())
+ throw new ArgumentOutOfRangeException(nameof(methodParameters));
+ EnsureElementsAreChildren(methodParameters);
+ parameters.AddRange(methodParameters);
}
}
diff --git a/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs
index 81daafa970..6de35a4e79 100644
--- a/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs
+++ b/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs
@@ -17,5 +17,15 @@ public static OpenApiSchema GetResponseSchema(this OpenApiOperation operation)
return schemas.FirstOrDefault();
}
+ public static OpenApiSchema GetResponseSchema(this OpenApiResponse response)
+ {
+ // For the moment assume application/json
+ var schemas = response.Content
+ .Where(c => c.Key == "application/json")
+ .Select(co => co.Value.Schema)
+ .Where(s => s is not null);
+
+ return schemas.FirstOrDefault();
+ }
}
}
diff --git a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs
index 54e7f92667..929d30beb9 100644
--- a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs
+++ b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs
@@ -62,8 +62,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) {
- var rawClassName = (operation?.GetResponseSchema()?.Reference?.GetClassName() ??
+ public static string GetClassName(this OpenApiUrlTreeNode currentNode, 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() ??
CleanupParametersFromPath(currentNode.Segment)?.ReplaceValueIdentifier())
.TrimEnd(requestParametersEndChar)
.TrimStart(requestParametersChar)
diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs
index 24cc559418..79a7a73bdc 100644
--- a/src/Kiota.Builder/KiotaBuilder.cs
+++ b/src/Kiota.Builder/KiotaBuilder.cs
@@ -15,955 +15,975 @@
using System.Security;
using Microsoft.OpenApi.Services;
-namespace Kiota.Builder
+namespace Kiota.Builder;
+
+public class KiotaBuilder
{
- public class KiotaBuilder
+ private readonly ILogger logger;
+ private readonly GenerationConfiguration config;
+
+ public KiotaBuilder(ILogger logger, GenerationConfiguration config)
{
- private readonly ILogger logger;
- private readonly GenerationConfiguration config;
+ this.logger = logger;
+ this.config = config;
+ }
- public KiotaBuilder(ILogger logger, GenerationConfiguration config)
- {
- this.logger = logger;
- this.config = config;
- }
+ public async Task GenerateSDK()
+ {
+ var sw = new Stopwatch();
+ // Step 1 - Read input stream
+ string inputPath = config.OpenAPIFilePath;
+
+ try {
+ // doing this verification at the begining to give immediate feedback to the user
+ Directory.CreateDirectory(config.OutputPath);
+ } catch (Exception ex) {
+ logger.LogError($"Could not open/create output directory {config.OutputPath}, reason: {ex.Message}");
+ return;
+ }
+
+ sw.Start();
+ using var input = await LoadStream(inputPath);
+ if(input == null)
+ return;
+ StopLogAndReset(sw, "step 1 - reading the stream - took");
+
+ // Step 2 - Parse OpenAPI
+ sw.Start();
+ var doc = CreateOpenApiDocument(input);
+ StopLogAndReset(sw, "step 2 - parsing the document - took");
+
+ SetApiRootUrl(doc);
+
+ // Step 3 - Create Uri Space of API
+ sw.Start();
+ var openApiTree = CreateUriSpace(doc);
+ StopLogAndReset(sw, "step 3 - create uri space - took");
+
+ // Step 4 - Create Source Model
+ sw.Start();
+ var generatedCode = CreateSourceModel(openApiTree);
+ StopLogAndReset(sw, "step 4 - create source model - took");
+
+ // Step 5 - RefineByLanguage
+ sw.Start();
+ ApplyLanguageRefinement(config, generatedCode);
+ StopLogAndReset(sw, "step 5 - refine by language - took");
+
+ // Step 6 - Write language source
+ sw.Start();
+ await CreateLanguageSourceFilesAsync(config.Language, generatedCode);
+ StopLogAndReset(sw, "step 6 - writing files - took");
+ }
+ private void SetApiRootUrl(OpenApiDocument doc) {
+ config.ApiRootUrl = doc.Servers.FirstOrDefault()?.Url.TrimEnd('/');
+ if(string.IsNullOrEmpty(config.ApiRootUrl))
+ throw new InvalidOperationException("A servers entry (v3) or host + basePath + schems properties (v2) must be present in the OpenAPI description.");
+ }
+ private void StopLogAndReset(Stopwatch sw, string prefix) {
+ sw.Stop();
+ logger.LogDebug($"{prefix} {sw.Elapsed}");
+ sw.Reset();
+ }
- public async Task GenerateSDK()
- {
- var sw = new Stopwatch();
- // Step 1 - Read input stream
- string inputPath = config.OpenAPIFilePath;
+ private async Task LoadStream(string inputPath)
+ {
+ var stopwatch = new Stopwatch();
+ stopwatch.Start();
+
+ Stream input;
+ if (inputPath.StartsWith("http"))
try {
- // doing this verification at the begining to give immediate feedback to the user
- Directory.CreateDirectory(config.OutputPath);
- } catch (Exception ex) {
- logger.LogError($"Could not open/create output directory {config.OutputPath}, reason: {ex.Message}");
- return;
+ using var httpClient = new HttpClient();
+ input = await httpClient.GetStreamAsync(inputPath);
+ } catch (HttpRequestException ex) {
+ logger.LogError($"Could not download the file at {inputPath}, reason: {ex.Message}");
+ return null;
}
-
- sw.Start();
- using var input = await LoadStream(inputPath);
- if(input == null)
- return;
- StopLogAndReset(sw, "step 1 - reading the stream - took");
-
- // Step 2 - Parse OpenAPI
- sw.Start();
- var doc = CreateOpenApiDocument(input);
- StopLogAndReset(sw, "step 2 - parsing the document - took");
-
- SetApiRootUrl(doc);
-
- // Step 3 - Create Uri Space of API
- sw.Start();
- var openApiTree = CreateUriSpace(doc);
- StopLogAndReset(sw, "step 3 - create uri space - took");
-
- // Step 4 - Create Source Model
- sw.Start();
- var generatedCode = CreateSourceModel(openApiTree);
- StopLogAndReset(sw, "step 4 - create source model - took");
-
- // Step 5 - RefineByLanguage
- sw.Start();
- ApplyLanguageRefinement(config, generatedCode);
- StopLogAndReset(sw, "step 5 - refine by language - took");
-
- // Step 6 - Write language source
- sw.Start();
- await CreateLanguageSourceFilesAsync(config.Language, generatedCode);
- StopLogAndReset(sw, "step 6 - writing files - took");
- }
- private void SetApiRootUrl(OpenApiDocument doc) {
- config.ApiRootUrl = doc.Servers.FirstOrDefault()?.Url.TrimEnd('/');
- if(string.IsNullOrEmpty(config.ApiRootUrl))
- throw new InvalidOperationException("A servers entry (v3) or host + basePath + schems properties (v2) must be present in the OpenAPI description.");
- }
- private void StopLogAndReset(Stopwatch sw, string prefix) {
- sw.Stop();
- logger.LogDebug($"{prefix} {sw.Elapsed}");
- sw.Reset();
- }
+ else
+ try {
+ input = new FileStream(inputPath, FileMode.Open);
+ } catch (Exception ex) when (ex is FileNotFoundException ||
+ ex is PathTooLongException ||
+ ex is DirectoryNotFoundException ||
+ ex is IOException ||
+ ex is UnauthorizedAccessException ||
+ ex is SecurityException ||
+ ex is NotSupportedException) {
+ logger.LogError($"Could not open the file at {inputPath}, reason: {ex.Message}");
+ return null;
+ }
+ stopwatch.Stop();
+ logger.LogTrace("{timestamp}ms: Read OpenAPI file {file}", stopwatch.ElapsedMilliseconds, inputPath);
+ return input;
+ }
- private async Task LoadStream(string inputPath)
+ public OpenApiDocument CreateOpenApiDocument(Stream input)
+ {
+ var stopwatch = new Stopwatch();
+ stopwatch.Start();
+ logger.LogTrace("Parsing OpenAPI file");
+ var reader = new OpenApiStreamReader();
+ var doc = reader.Read(input, out var diag);
+ stopwatch.Stop();
+ if (diag.Errors.Count > 0)
{
- var stopwatch = new Stopwatch();
- stopwatch.Start();
-
- Stream input;
- if (inputPath.StartsWith("http"))
- try {
- using var httpClient = new HttpClient();
- input = await httpClient.GetStreamAsync(inputPath);
- } catch (HttpRequestException ex) {
- logger.LogError($"Could not download the file at {inputPath}, reason: {ex.Message}");
- return null;
- }
- else
- try {
- input = new FileStream(inputPath, FileMode.Open);
- } catch (Exception ex) when (ex is FileNotFoundException ||
- ex is PathTooLongException ||
- ex is DirectoryNotFoundException ||
- ex is IOException ||
- ex is UnauthorizedAccessException ||
- ex is SecurityException ||
- ex is NotSupportedException) {
- logger.LogError($"Could not open the file at {inputPath}, reason: {ex.Message}");
- return null;
- }
- stopwatch.Stop();
- logger.LogTrace("{timestamp}ms: Read OpenAPI file {file}", stopwatch.ElapsedMilliseconds, inputPath);
- return input;
+ logger.LogError($"{stopwatch.ElapsedMilliseconds}ms: OpenApi Parsing errors {string.Join(Environment.NewLine, diag.Errors.Select(e => e.Message))}");
}
-
-
- public OpenApiDocument CreateOpenApiDocument(Stream input)
+ else
{
- var stopwatch = new Stopwatch();
- stopwatch.Start();
- logger.LogTrace("Parsing OpenAPI file");
- var reader = new OpenApiStreamReader();
- var doc = reader.Read(input, out var diag);
- stopwatch.Stop();
- if (diag.Errors.Count > 0)
- {
- logger.LogError($"{stopwatch.ElapsedMilliseconds}ms: OpenApi Parsing errors {string.Join(Environment.NewLine, diag.Errors.Select(e => e.Message))}");
- }
- else
- {
- logger.LogTrace("{timestamp}ms: Parsed OpenAPI successfully. {count} paths found.", stopwatch.ElapsedMilliseconds, doc.Paths.Count);
- }
-
- return doc;
+ logger.LogTrace("{timestamp}ms: Parsed OpenAPI successfully. {count} paths found.", stopwatch.ElapsedMilliseconds, doc.Paths.Count);
}
- ///
- /// Translate OpenApi PathItems into a tree structure that will define the classes
- ///
- /// OpenAPI Document of the API to be processed
- /// Root node of the API URI space
- public OpenApiUrlTreeNode CreateUriSpace(OpenApiDocument doc)
- {
- var stopwatch = new Stopwatch();
- stopwatch.Start();
- var node = OpenApiUrlTreeNode.Create(doc, Constants.DefaultOpenApiLabel);
- stopwatch.Stop();
- logger.LogTrace("{timestamp}ms: Created UriSpace tree", stopwatch.ElapsedMilliseconds);
- return node;
- }
- private CodeNamespace rootNamespace;
- private CodeNamespace modelsNamespace;
-
- ///
- /// Convert UriSpace of OpenApiPathItems into conceptual SDK Code model
- ///
- /// Root OpenApiUriSpaceNode of API to be generated
- ///
- public CodeNamespace CreateSourceModel(OpenApiUrlTreeNode root)
- {
- var stopwatch = new Stopwatch();
- stopwatch.Start();
+ return doc;
+ }
- rootNamespace = CodeNamespace.InitRootNamespace();
- var codeNamespace = rootNamespace.AddNamespace(config.ClientNamespaceName);
- modelsNamespace = rootNamespace.AddNamespace($"{codeNamespace.Name}.models");
- CreateRequestBuilderClass(codeNamespace, root, root);
- StopLogAndReset(stopwatch, $"{nameof(CreateRequestBuilderClass)}");
- stopwatch.Start();
- MapTypeDefinitions(codeNamespace);
- StopLogAndReset(stopwatch, $"{nameof(MapTypeDefinitions)}");
+ ///
+ /// Translate OpenApi PathItems into a tree structure that will define the classes
+ ///
+ /// OpenAPI Document of the API to be processed
+ /// Root node of the API URI space
+ public OpenApiUrlTreeNode CreateUriSpace(OpenApiDocument doc)
+ {
+ var stopwatch = new Stopwatch();
+ stopwatch.Start();
+ var node = OpenApiUrlTreeNode.Create(doc, Constants.DefaultOpenApiLabel);
+ stopwatch.Stop();
+ logger.LogTrace("{timestamp}ms: Created UriSpace tree", stopwatch.ElapsedMilliseconds);
+ return node;
+ }
+ private CodeNamespace rootNamespace;
+ private CodeNamespace modelsNamespace;
+
+ ///
+ /// Convert UriSpace of OpenApiPathItems into conceptual SDK Code model
+ ///
+ /// Root OpenApiUriSpaceNode of API to be generated
+ ///
+ public CodeNamespace CreateSourceModel(OpenApiUrlTreeNode root)
+ {
+ var stopwatch = new Stopwatch();
+ stopwatch.Start();
- logger.LogTrace("{timestamp}ms: Created source model with {count} classes", stopwatch.ElapsedMilliseconds, codeNamespace.GetChildElements(true).Count());
+ rootNamespace = CodeNamespace.InitRootNamespace();
+ var codeNamespace = rootNamespace.AddNamespace(config.ClientNamespaceName);
+ modelsNamespace = rootNamespace.AddNamespace($"{codeNamespace.Name}.models");
+ CreateRequestBuilderClass(codeNamespace, root, root);
+ StopLogAndReset(stopwatch, $"{nameof(CreateRequestBuilderClass)}");
+ stopwatch.Start();
+ MapTypeDefinitions(codeNamespace);
+ StopLogAndReset(stopwatch, $"{nameof(MapTypeDefinitions)}");
- return rootNamespace;
- }
+ logger.LogTrace("{timestamp}ms: Created source model with {count} classes", stopwatch.ElapsedMilliseconds, codeNamespace.GetChildElements(true).Count());
- ///
- /// Manipulate CodeDOM for language specific issues
- ///
- ///
- ///
- public void ApplyLanguageRefinement(GenerationConfiguration config, CodeNamespace generatedCode)
- {
- var stopwatch = new Stopwatch();
- stopwatch.Start();
+ return rootNamespace;
+ }
+
+ ///
+ /// Manipulate CodeDOM for language specific issues
+ ///
+ ///
+ ///
+ public void ApplyLanguageRefinement(GenerationConfiguration config, CodeNamespace generatedCode)
+ {
+ var stopwatch = new Stopwatch();
+ stopwatch.Start();
- ILanguageRefiner.Refine(config, generatedCode);
+ ILanguageRefiner.Refine(config, generatedCode);
- stopwatch.Stop();
- logger.LogDebug("{timestamp}ms: Language refinement applied", stopwatch.ElapsedMilliseconds);
- }
+ stopwatch.Stop();
+ logger.LogDebug("{timestamp}ms: Language refinement applied", stopwatch.ElapsedMilliseconds);
+ }
- ///
- /// Iterate through Url Space and create request builder classes for each node in the tree
- ///
- /// Root node of URI space from the OpenAPI described API
- /// A CodeNamespace object that contains request builder classes for the Uri Space
+ ///
+ /// Iterate through Url Space and create request builder classes for each node in the tree
+ ///
+ /// Root node of URI space from the OpenAPI described API
+ /// A CodeNamespace object that contains request builder classes for the Uri Space
- public async Task CreateLanguageSourceFilesAsync(GenerationLanguage language, CodeNamespace generatedCode)
- {
- var languageWriter = LanguageWriter.GetLanguageWriter(language, this.config.OutputPath, this.config.ClientNamespaceName);
- var stopwatch = new Stopwatch();
- stopwatch.Start();
- await new CodeRenderer(config).RenderCodeNamespaceToFilePerClassAsync(languageWriter, generatedCode);
- stopwatch.Stop();
- logger.LogTrace("{timestamp}ms: Files written to {path}", stopwatch.ElapsedMilliseconds, config.OutputPath);
- }
- private static readonly string requestBuilderSuffix = "RequestBuilder";
- private static readonly string voidType = "void";
- private static readonly string coreInterfaceType = "IRequestAdapter";
- private static readonly string requestAdapterParameterName = "requestAdapter";
- private static readonly string constructorMethodName = "constructor";
- ///
- /// Create a CodeClass instance that is a request builder class for the OpenApiUrlTreeNode
- ///
- private void CreateRequestBuilderClass(CodeNamespace currentNamespace, OpenApiUrlTreeNode currentNode, OpenApiUrlTreeNode rootNode)
+ public async Task CreateLanguageSourceFilesAsync(GenerationLanguage language, CodeNamespace generatedCode)
+ {
+ var languageWriter = LanguageWriter.GetLanguageWriter(language, this.config.OutputPath, this.config.ClientNamespaceName);
+ var stopwatch = new Stopwatch();
+ stopwatch.Start();
+ await new CodeRenderer(config).RenderCodeNamespaceToFilePerClassAsync(languageWriter, generatedCode);
+ stopwatch.Stop();
+ logger.LogTrace("{timestamp}ms: Files written to {path}", stopwatch.ElapsedMilliseconds, config.OutputPath);
+ }
+ private static readonly string requestBuilderSuffix = "RequestBuilder";
+ private static readonly string voidType = "void";
+ private static readonly string coreInterfaceType = "IRequestAdapter";
+ private static readonly string requestAdapterParameterName = "requestAdapter";
+ private static readonly string constructorMethodName = "constructor";
+ ///
+ /// Create a CodeClass instance that is a request builder class for the OpenApiUrlTreeNode
+ ///
+ private void CreateRequestBuilderClass(CodeNamespace currentNamespace, OpenApiUrlTreeNode currentNode, OpenApiUrlTreeNode rootNode)
+ {
+ // Determine Class Name
+ CodeClass codeClass;
+ var isApiClientClass = currentNode == rootNode;
+ if (isApiClientClass)
+ codeClass = currentNamespace.AddClass(new CodeClass {
+ Name = config.ClientClassName,
+ ClassKind = CodeClassKind.RequestBuilder,
+ Description = "The main entry point of the SDK, exposes the configuration and the fluent API."
+ }).First();
+ else
{
- // Determine Class Name
- CodeClass codeClass;
- var isApiClientClass = currentNode == rootNode;
- if (isApiClientClass)
- codeClass = currentNamespace.AddClass(new CodeClass {
- Name = config.ClientClassName,
+ var targetNS = currentNode.DoesNodeBelongToItemSubnamespace() ? currentNamespace.EnsureItemNamespace() : currentNamespace;
+ var className = currentNode.GetClassName(requestBuilderSuffix);
+ codeClass = targetNS.AddClass(new CodeClass {
+ Name = className,
ClassKind = CodeClassKind.RequestBuilder,
- Description = "The main entry point of the SDK, exposes the configuration and the fluent API."
+ Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel, $"Builds and executes requests for operations under {currentNode.Path}"),
}).First();
- else
- {
- var targetNS = currentNode.DoesNodeBelongToItemSubnamespace() ? currentNamespace.EnsureItemNamespace() : currentNamespace;
- var className = currentNode.GetClassName(requestBuilderSuffix);
- codeClass = targetNS.AddClass(new CodeClass {
- Name = className,
- ClassKind = CodeClassKind.RequestBuilder,
- Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel, $"Builds and executes requests for operations under {currentNode.Path}"),
- }).First();
- }
+ }
- logger.LogTrace("Creating class {class}", codeClass.Name);
+ logger.LogTrace("Creating class {class}", codeClass.Name);
- // Add properties for children
- foreach (var child in currentNode.Children)
+ // Add properties for children
+ foreach (var child in currentNode.Children)
+ {
+ var propIdentifier = child.Value.GetClassName();
+ var propType = propIdentifier + requestBuilderSuffix;
+ if (child.Value.IsPathSegmentWithSingleSimpleParameter())
{
- var propIdentifier = child.Value.GetClassName();
- var propType = propIdentifier + requestBuilderSuffix;
- if (child.Value.IsPathSegmentWithSingleSimpleParameter())
- {
- var prop = CreateIndexer($"{propIdentifier}-indexer", propType, child.Value, currentNode);
- codeClass.SetIndexer(prop);
- }
- else if (child.Value.IsComplexPathWithAnyNumberOfParameters())
- {
- CreateMethod(propIdentifier, propType, codeClass, child.Value);
- }
- else
- {
- var prop = CreateProperty(propIdentifier, propType, kind: CodePropertyKind.RequestBuilder); // we should add the type definition here but we can't as it might not have been generated yet
- codeClass.AddProperty(prop);
- }
+ var prop = CreateIndexer($"{propIdentifier}-indexer", propType, child.Value, currentNode);
+ codeClass.SetIndexer(prop);
}
-
- // Add methods for Operations
- if (currentNode.HasOperations(Constants.DefaultOpenApiLabel))
+ else if (child.Value.IsComplexPathWithAnyNumberOfParameters())
{
- foreach(var operation in currentNode
- .PathItems[Constants.DefaultOpenApiLabel]
- .Operations
- .Where(x => x.Value.RequestBody?.Content?.Any(y => !config.IgnoredRequestContentTypes.Contains(y.Key)) ?? true))
- CreateOperationMethods(currentNode, operation.Key, operation.Value, codeClass);
+ CreateMethod(propIdentifier, propType, codeClass, child.Value);
}
- CreateUrlManagement(codeClass, currentNode, isApiClientClass);
-
- Parallel.ForEach(currentNode.Children.Values, childNode =>
+ else
{
- var targetNamespaceName = childNode.GetNodeNamespaceFromPath(config.ClientNamespaceName);
- var targetNamespace = rootNamespace.FindNamespaceByName(targetNamespaceName) ?? rootNamespace.AddNamespace(targetNamespaceName);
- CreateRequestBuilderClass(targetNamespace, childNode, rootNode);
- });
- }
- private static void CreateMethod(string propIdentifier, string propType, CodeClass codeClass, OpenApiUrlTreeNode currentNode)
- {
- var methodToAdd = new CodeMethod {
- Name = propIdentifier,
- MethodKind = CodeMethodKind.RequestBuilderWithParameters,
- Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel, $"Builds and executes requests for operations under {currentNode.Path}"),
- Access = AccessModifier.Public,
- IsAsync = false,
- IsStatic = false,
- };
- methodToAdd.ReturnType = new CodeType {
- Name = propType,
- ActionOf = false,
- CollectionKind = CodeTypeBase.CodeTypeCollectionKind.None,
- IsExternal = false,
- IsNullable = false,
- };
- AddPathParametersToMethod(currentNode, methodToAdd, false);
- codeClass.AddMethod(methodToAdd);
- }
- private static void AddPathParametersToMethod(OpenApiUrlTreeNode currentNode, CodeMethod methodToAdd, bool asOptional) {
- foreach(var parameter in currentNode.GetPathParametersForCurrentSegment()) {
- var mParameter = new CodeParameter {
- Name = parameter.Name,
- Optional = asOptional,
- Description = parameter.Description,
- ParameterKind = CodeParameterKind.Path,
- UrlTemplateParameterName = parameter.Name,
- };
- mParameter.Type = GetPrimitiveType(parameter.Schema);
- methodToAdd.AddParameter(mParameter);
+ var prop = CreateProperty(propIdentifier, propType, kind: CodePropertyKind.RequestBuilder); // we should add the type definition here but we can't as it might not have been generated yet
+ codeClass.AddProperty(prop);
}
}
- private static readonly string PathParametersParameterName = "pathParameters";
- private void CreateUrlManagement(CodeClass currentClass, OpenApiUrlTreeNode currentNode, bool isApiClientClass) {
- var pathProperty = new CodeProperty {
- Access = AccessModifier.Private,
- Name = "urlTemplate",
- DefaultValue = $"\"{currentNode.GetUrlTemplate()}\"",
- ReadOnly = true,
- Description = "Url template to use to build the URL for the current request builder",
- PropertyKind = CodePropertyKind.UrlTemplate,
- Type = new CodeType {
- Name = "string",
- IsNullable = false,
- IsExternal = true,
- },
- };
- currentClass.AddProperty(pathProperty);
- var requestAdapterProperty = new CodeProperty {
- Name = requestAdapterParameterName,
- Description = "The request adapter to use to execute the requests.",
- PropertyKind = CodePropertyKind.RequestAdapter,
- Access = AccessModifier.Private,
- ReadOnly = true,
+ // Add methods for Operations
+ if (currentNode.HasOperations(Constants.DefaultOpenApiLabel))
+ {
+ foreach(var operation in currentNode
+ .PathItems[Constants.DefaultOpenApiLabel]
+ .Operations
+ .Where(x => x.Value.RequestBody?.Content?.Any(y => !config.IgnoredRequestContentTypes.Contains(y.Key)) ?? true))
+ CreateOperationMethods(currentNode, operation.Key, operation.Value, codeClass);
+ }
+ CreateUrlManagement(codeClass, currentNode, isApiClientClass);
+
+ Parallel.ForEach(currentNode.Children.Values, childNode =>
+ {
+ var targetNamespaceName = childNode.GetNodeNamespaceFromPath(config.ClientNamespaceName);
+ var targetNamespace = rootNamespace.FindNamespaceByName(targetNamespaceName) ?? rootNamespace.AddNamespace(targetNamespaceName);
+ CreateRequestBuilderClass(targetNamespace, childNode, rootNode);
+ });
+ }
+ private static void CreateMethod(string propIdentifier, string propType, CodeClass codeClass, OpenApiUrlTreeNode currentNode)
+ {
+ var methodToAdd = new CodeMethod {
+ Name = propIdentifier,
+ MethodKind = CodeMethodKind.RequestBuilderWithParameters,
+ Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel, $"Builds and executes requests for operations under {currentNode.Path}"),
+ Access = AccessModifier.Public,
+ IsAsync = false,
+ IsStatic = false,
+ };
+ methodToAdd.ReturnType = new CodeType {
+ Name = propType,
+ ActionOf = false,
+ CollectionKind = CodeTypeBase.CodeTypeCollectionKind.None,
+ IsExternal = false,
+ IsNullable = false,
+ };
+ AddPathParametersToMethod(currentNode, methodToAdd, false);
+ codeClass.AddMethod(methodToAdd);
+ }
+ private static void AddPathParametersToMethod(OpenApiUrlTreeNode currentNode, CodeMethod methodToAdd, bool asOptional) {
+ foreach(var parameter in currentNode.GetPathParametersForCurrentSegment()) {
+ var mParameter = new CodeParameter {
+ Name = parameter.Name,
+ Optional = asOptional,
+ Description = parameter.Description,
+ ParameterKind = CodeParameterKind.Path,
+ UrlTemplateParameterName = parameter.Name,
};
- requestAdapterProperty.Type = new CodeType {
- Name = coreInterfaceType,
+ mParameter.Type = GetPrimitiveType(parameter.Schema);
+ methodToAdd.AddParameter(mParameter);
+ }
+ }
+ private static readonly string PathParametersParameterName = "pathParameters";
+ private void CreateUrlManagement(CodeClass currentClass, OpenApiUrlTreeNode currentNode, bool isApiClientClass) {
+ var pathProperty = new CodeProperty {
+ Access = AccessModifier.Private,
+ Name = "urlTemplate",
+ DefaultValue = $"\"{currentNode.GetUrlTemplate()}\"",
+ ReadOnly = true,
+ Description = "Url template to use to build the URL for the current request builder",
+ PropertyKind = CodePropertyKind.UrlTemplate,
+ Type = new CodeType {
+ Name = "string",
+ IsNullable = false,
+ IsExternal = true,
+ },
+ };
+ currentClass.AddProperty(pathProperty);
+
+ var requestAdapterProperty = new CodeProperty {
+ Name = requestAdapterParameterName,
+ Description = "The request adapter to use to execute the requests.",
+ PropertyKind = CodePropertyKind.RequestAdapter,
+ Access = AccessModifier.Private,
+ ReadOnly = true,
+ };
+ requestAdapterProperty.Type = new CodeType {
+ Name = coreInterfaceType,
+ IsExternal = true,
+ IsNullable = false,
+ };
+ currentClass.AddProperty(requestAdapterProperty);
+ var constructor = currentClass.AddMethod(new CodeMethod {
+ Name = constructorMethodName,
+ MethodKind = isApiClientClass ? CodeMethodKind.ClientConstructor : CodeMethodKind.Constructor,
+ IsAsync = false,
+ IsStatic = false,
+ Description = $"Instantiates a new {currentClass.Name.ToFirstCharacterUpperCase()} and sets the default values.",
+ Access = AccessModifier.Public,
+ }).First();
+ constructor.ReturnType = new CodeType { Name = voidType, IsExternal = true };
+ var pathParametersProperty = new CodeProperty {
+ Name = PathParametersParameterName,
+ Description = "Path parameters for the request",
+ PropertyKind = CodePropertyKind.PathParameters,
+ Access = AccessModifier.Private,
+ ReadOnly = true,
+ Type = new CodeType {
+ Name = "Dictionary",
IsExternal = true,
IsNullable = false,
- };
- currentClass.AddProperty(requestAdapterProperty);
- var constructor = currentClass.AddMethod(new CodeMethod {
- Name = constructorMethodName,
- MethodKind = isApiClientClass ? CodeMethodKind.ClientConstructor : CodeMethodKind.Constructor,
- IsAsync = false,
- IsStatic = false,
- Description = $"Instantiates a new {currentClass.Name.ToFirstCharacterUpperCase()} and sets the default values.",
- Access = AccessModifier.Public,
- }).First();
- constructor.ReturnType = new CodeType { Name = voidType, IsExternal = true };
- var pathParametersProperty = new CodeProperty {
- Name = PathParametersParameterName,
- Description = "Path parameters for the request",
- PropertyKind = CodePropertyKind.PathParameters,
- Access = AccessModifier.Private,
- ReadOnly = true,
- Type = new CodeType {
- Name = "Dictionary",
- IsExternal = true,
- IsNullable = false,
- },
- };
- currentClass.AddProperty(pathParametersProperty);
- if(isApiClientClass) {
- constructor.SerializerModules = config.Serializers;
- constructor.DeserializerModules = config.Deserializers;
- constructor.BaseUrl = config.ApiRootUrl;
- pathParametersProperty.DefaultValue = $"new {pathParametersProperty.Type.Name}()";
- } else {
- constructor.AddParameter(new CodeParameter {
- Name = PathParametersParameterName,
- Type = pathParametersProperty.Type,
- Optional = false,
- Description = pathParametersProperty.Description,
- ParameterKind = CodeParameterKind.PathParameters,
- });
- AddPathParametersToMethod(currentNode, constructor, true);
- }
+ },
+ };
+ currentClass.AddProperty(pathParametersProperty);
+ if(isApiClientClass) {
+ constructor.SerializerModules = config.Serializers;
+ constructor.DeserializerModules = config.Deserializers;
+ constructor.BaseUrl = config.ApiRootUrl;
+ pathParametersProperty.DefaultValue = $"new {pathParametersProperty.Type.Name}()";
+ } else {
constructor.AddParameter(new CodeParameter {
- Name = requestAdapterParameterName,
- Type = requestAdapterProperty.Type,
+ Name = PathParametersParameterName,
+ Type = pathParametersProperty.Type,
Optional = false,
- Description = requestAdapterProperty.Description,
- ParameterKind = CodeParameterKind.RequestAdapter,
- });
- if(isApiClientClass && config.UsesBackingStore) {
- var factoryInterfaceName = $"{BackingStoreInterface}Factory";
- var backingStoreParam = new CodeParameter {
- Name = "backingStore",
- Optional = true,
- Description = "The backing store to use for the models.",
- ParameterKind = CodeParameterKind.BackingStore,
- Type = new CodeType {
- Name = factoryInterfaceName,
- IsNullable = true,
- }
- };
- constructor.AddParameter(backingStoreParam);
- }
- }
- private static readonly Func shortestNamespaceOrder = (x) => x.GetNamespaceDepth();
- ///
- /// Remaps definitions to custom types so they can be used later in generation or in refiners
- ///
- private void MapTypeDefinitions(CodeElement codeElement) {
- var unmappedTypes = GetUnmappedTypeDefinitions(codeElement).Distinct();
-
- var unmappedTypesWithNoName = unmappedTypes.Where(x => string.IsNullOrEmpty(x.Name)).ToList();
-
- unmappedTypesWithNoName.ForEach(x => {
- logger.LogWarning($"Type with empty name and parent {x.Parent.Name}");
+ Description = pathParametersProperty.Description,
+ ParameterKind = CodeParameterKind.PathParameters,
});
-
- var unmappedTypesWithName = unmappedTypes.Except(unmappedTypesWithNoName);
-
- var unmappedRequestBuilderTypes = unmappedTypesWithName
- .Where(x =>
- x.Parent is CodeProperty property && property.IsOfKind(CodePropertyKind.RequestBuilder) ||
- x.Parent is CodeIndexer ||
- x.Parent is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestBuilderWithParameters))
- .ToList();
-
- Parallel.ForEach(unmappedRequestBuilderTypes, x => {
- var parentNS = x.Parent.Parent.Parent as CodeNamespace;
- x.TypeDefinition = parentNS.FindChildrenByName(x.Name)
- .OrderBy(shortestNamespaceOrder)
- .FirstOrDefault();
- // searching down first because most request builder properties on a request builder are just sub paths on the API
- if(x.TypeDefinition == null) {
- parentNS = parentNS.Parent as CodeNamespace;
- x.TypeDefinition = parentNS
- .FindNamespaceByName($"{parentNS.Name}.{x.Name.Substring(0, x.Name.Length - requestBuilderSuffix.Length).ToFirstCharacterLowerCase()}".TrimEnd(nsNameSeparator))
- ?.FindChildrenByName(x.Name)
- ?.OrderBy(shortestNamespaceOrder)
- ?.FirstOrDefault();
- // in case of the .item namespace, going to the parent and then down to the target by convention
- // this avoid getting the wrong request builder in case we have multiple request builders with the same name in the parent branch
- // in both cases we always take the uppermost item (smaller numbers of segments in the namespace name)
+ AddPathParametersToMethod(currentNode, constructor, true);
+ }
+ constructor.AddParameter(new CodeParameter {
+ Name = requestAdapterParameterName,
+ Type = requestAdapterProperty.Type,
+ Optional = false,
+ Description = requestAdapterProperty.Description,
+ ParameterKind = CodeParameterKind.RequestAdapter,
+ });
+ if(isApiClientClass && config.UsesBackingStore) {
+ var factoryInterfaceName = $"{BackingStoreInterface}Factory";
+ var backingStoreParam = new CodeParameter {
+ Name = "backingStore",
+ Optional = true,
+ Description = "The backing store to use for the models.",
+ ParameterKind = CodeParameterKind.BackingStore,
+ Type = new CodeType {
+ Name = factoryInterfaceName,
+ IsNullable = true,
}
- });
-
- Parallel.ForEach(unmappedTypesWithName.Where(x => x.TypeDefinition == null).GroupBy(x => x.Name), x => {
- if (rootNamespace.FindChildByName(x.First().Name) is CodeElement definition)
- foreach (var type in x)
- {
- type.TypeDefinition = definition;
- logger.LogWarning($"Mapped type {type.Name} for {type.Parent.Name} using the fallback approach.");
- }
- });
- }
- private static readonly char nsNameSeparator = '.';
- private static IEnumerable filterUnmappedTypeDefitions(IEnumerable source) =>
- source.OfType()
- .Union(source
- .OfType()
- .SelectMany(x => x.Types))
- .Where(x => !x.IsExternal && x.TypeDefinition == null);
- private IEnumerable GetUnmappedTypeDefinitions(CodeElement codeElement) {
- var childElementsUnmappedTypes = codeElement.GetChildElements(true).SelectMany(x => GetUnmappedTypeDefinitions(x));
- return codeElement switch
- {
- CodeMethod method => filterUnmappedTypeDefitions(method.Parameters.Select(x => x.Type).Union(new CodeTypeBase[] { method.ReturnType })).Union(childElementsUnmappedTypes),
- CodeProperty property => filterUnmappedTypeDefitions(new CodeTypeBase[] { property.Type }).Union(childElementsUnmappedTypes),
- CodeIndexer indexer => filterUnmappedTypeDefitions(new CodeTypeBase[] { indexer.ReturnType }).Union(childElementsUnmappedTypes),
- _ => childElementsUnmappedTypes,
};
+ constructor.AddParameter(backingStoreParam);
}
- private CodeIndexer CreateIndexer(string childIdentifier, string childType, OpenApiUrlTreeNode currentNode, OpenApiUrlTreeNode parentNode)
+ }
+ private static readonly Func shortestNamespaceOrder = (x) => x.GetNamespaceDepth();
+ ///
+ /// Remaps definitions to custom types so they can be used later in generation or in refiners
+ ///
+ private void MapTypeDefinitions(CodeElement codeElement) {
+ var unmappedTypes = GetUnmappedTypeDefinitions(codeElement).Distinct();
+
+ var unmappedTypesWithNoName = unmappedTypes.Where(x => string.IsNullOrEmpty(x.Name)).ToList();
+
+ unmappedTypesWithNoName.ForEach(x => {
+ logger.LogWarning($"Type with empty name and parent {x.Parent.Name}");
+ });
+
+ var unmappedTypesWithName = unmappedTypes.Except(unmappedTypesWithNoName);
+
+ var unmappedRequestBuilderTypes = unmappedTypesWithName
+ .Where(x =>
+ x.Parent is CodeProperty property && property.IsOfKind(CodePropertyKind.RequestBuilder) ||
+ x.Parent is CodeIndexer ||
+ x.Parent is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestBuilderWithParameters))
+ .ToList();
+
+ Parallel.ForEach(unmappedRequestBuilderTypes, x => {
+ var parentNS = x.Parent.Parent.Parent as CodeNamespace;
+ x.TypeDefinition = parentNS.FindChildrenByName(x.Name)
+ .OrderBy(shortestNamespaceOrder)
+ .FirstOrDefault();
+ // searching down first because most request builder properties on a request builder are just sub paths on the API
+ if(x.TypeDefinition == null) {
+ parentNS = parentNS.Parent as CodeNamespace;
+ x.TypeDefinition = parentNS
+ .FindNamespaceByName($"{parentNS.Name}.{x.Name.Substring(0, x.Name.Length - requestBuilderSuffix.Length).ToFirstCharacterLowerCase()}".TrimEnd(nsNameSeparator))
+ ?.FindChildrenByName(x.Name)
+ ?.OrderBy(shortestNamespaceOrder)
+ ?.FirstOrDefault();
+ // in case of the .item namespace, going to the parent and then down to the target by convention
+ // this avoid getting the wrong request builder in case we have multiple request builders with the same name in the parent branch
+ // in both cases we always take the uppermost item (smaller numbers of segments in the namespace name)
+ }
+ });
+
+ Parallel.ForEach(unmappedTypesWithName.Where(x => x.TypeDefinition == null).GroupBy(x => x.Name), x => {
+ if (rootNamespace.FindChildByName(x.First().Name) is CodeElement definition)
+ foreach (var type in x)
+ {
+ type.TypeDefinition = definition;
+ logger.LogWarning($"Mapped type {type.Name} for {type.Parent.Name} using the fallback approach.");
+ }
+ });
+ }
+ private static readonly char nsNameSeparator = '.';
+ private static IEnumerable filterUnmappedTypeDefitions(IEnumerable source) =>
+ source.OfType()
+ .Union(source
+ .OfType()
+ .SelectMany(x => x.Types))
+ .Where(x => !x.IsExternal && x.TypeDefinition == null);
+ private IEnumerable GetUnmappedTypeDefinitions(CodeElement codeElement) {
+ var childElementsUnmappedTypes = codeElement.GetChildElements(true).SelectMany(x => GetUnmappedTypeDefinitions(x));
+ return codeElement switch
{
- logger.LogTrace("Creating indexer {name}", childIdentifier);
- return new CodeIndexer
- {
- Name = childIdentifier,
- Description = $"Gets an item from the {currentNode.GetNodeNamespaceFromPath(config.ClientNamespaceName)} collection",
- IndexType = new CodeType { Name = "string", IsExternal = true, },
- ReturnType = new CodeType { Name = childType },
- ParameterName = currentNode.Segment.SanitizeUrlTemplateParameterName().TrimStart('{').TrimEnd('}'),
- PathSegment = parentNode.GetNodeNamespaceFromPath(string.Empty).Split('.').Last(),
- };
- }
+ CodeMethod method => filterUnmappedTypeDefitions(method.Parameters.Select(x => x.Type).Union(new CodeTypeBase[] { method.ReturnType })).Union(childElementsUnmappedTypes),
+ CodeProperty property => filterUnmappedTypeDefitions(new CodeTypeBase[] { property.Type }).Union(childElementsUnmappedTypes),
+ CodeIndexer indexer => filterUnmappedTypeDefitions(new CodeTypeBase[] { indexer.ReturnType }).Union(childElementsUnmappedTypes),
+ _ => childElementsUnmappedTypes,
+ };
+ }
+ private CodeIndexer CreateIndexer(string childIdentifier, string childType, OpenApiUrlTreeNode currentNode, OpenApiUrlTreeNode parentNode)
+ {
+ logger.LogTrace("Creating indexer {name}", childIdentifier);
+ return new CodeIndexer
+ {
+ Name = childIdentifier,
+ Description = $"Gets an item from the {currentNode.GetNodeNamespaceFromPath(config.ClientNamespaceName)} collection",
+ IndexType = new CodeType { Name = "string", IsExternal = true, },
+ ReturnType = new CodeType { Name = childType },
+ ParameterName = currentNode.Segment.SanitizeUrlTemplateParameterName().TrimStart('{').TrimEnd('}'),
+ PathSegment = parentNode.GetNodeNamespaceFromPath(string.Empty).Split('.').Last(),
+ };
+ }
- private CodeProperty CreateProperty(string childIdentifier, string childType, string defaultValue = null, OpenApiSchema typeSchema = null, CodeElement typeDefinition = null, CodePropertyKind kind = CodePropertyKind.Custom)
+ private CodeProperty CreateProperty(string childIdentifier, string childType, string defaultValue = null, OpenApiSchema typeSchema = null, CodeElement typeDefinition = null, CodePropertyKind kind = CodePropertyKind.Custom)
+ {
+ var propertyName = childIdentifier;
+ config.PropertiesPrefixToStrip.ForEach(x => propertyName = propertyName.Replace(x, string.Empty));
+ var prop = new CodeProperty
{
- var propertyName = childIdentifier;
- config.PropertiesPrefixToStrip.ForEach(x => propertyName = propertyName.Replace(x, string.Empty));
- var prop = new CodeProperty
- {
- Name = propertyName,
- DefaultValue = defaultValue,
- PropertyKind = kind,
- Description = typeSchema?.Description,
- };
- if(propertyName != childIdentifier)
- prop.SerializationName = childIdentifier;
-
- var propType = GetPrimitiveType(typeSchema, childType);
- propType.TypeDefinition = typeDefinition;
- propType.CollectionKind = typeSchema.IsArray() ? CodeType.CodeTypeCollectionKind.Complex : default;
- prop.Type = propType;
- logger.LogTrace("Creating property {name} of {type}", prop.Name, prop.Type.Name);
- return prop;
- }
- private static readonly HashSet typeNamesToSkip = new() {"object", "array"};
- private static CodeType GetPrimitiveType(OpenApiSchema typeSchema, string childType = default) {
- var typeNames = new List{typeSchema?.Items?.Type, childType, typeSchema?.Type};
- if(typeSchema?.AnyOf?.Any() ?? false)
- typeNames.AddRange(typeSchema.AnyOf.Select(x => x.Type)); // double is sometimes an anyof string, number and enum
- // first value that's not null, and not "object" for primitive collections, the items type matters
- var typeName = typeNames.FirstOrDefault(x => !string.IsNullOrEmpty(x) && !typeNamesToSkip.Contains(x));
-
- if(string.IsNullOrEmpty(typeName))
- return null;
- var format = typeSchema?.Format ?? typeSchema?.Items?.Format;
- var isExternal = false;
- if (typeSchema?.Items?.Enum?.Any() ?? false)
- typeName = childType;
- else if("string".Equals(typeName, StringComparison.OrdinalIgnoreCase)) {
- isExternal = true;
- if("date-time".Equals(format, StringComparison.OrdinalIgnoreCase))
- typeName = "DateTimeOffset";
- else if("duration".Equals(format, StringComparison.OrdinalIgnoreCase))
- typeName = "TimeSpan";
- else if("date".Equals(format, StringComparison.OrdinalIgnoreCase))
- typeName = "DateOnly";
- else if("time".Equals(format, StringComparison.OrdinalIgnoreCase))
- typeName = "TimeOnly";
- else if ("base64url".Equals(format, StringComparison.OrdinalIgnoreCase))
- typeName = "binary";
- } else if ("double".Equals(format, StringComparison.OrdinalIgnoreCase) ||
- "float".Equals(format, StringComparison.OrdinalIgnoreCase) ||
- "int64".Equals(format, StringComparison.OrdinalIgnoreCase) ||
- "decimal".Equals(format, StringComparison.OrdinalIgnoreCase)) {
- isExternal = true;
- typeName = format.ToLowerInvariant();
- } else if ("boolean".Equals(typeName, StringComparison.OrdinalIgnoreCase) ||
- "integer".Equals(typeName, StringComparison.OrdinalIgnoreCase))
+ Name = propertyName,
+ DefaultValue = defaultValue,
+ PropertyKind = kind,
+ Description = typeSchema?.Description,
+ };
+ if(propertyName != childIdentifier)
+ prop.SerializationName = childIdentifier;
+
+ var propType = GetPrimitiveType(typeSchema, childType);
+ propType.TypeDefinition = typeDefinition;
+ propType.CollectionKind = typeSchema.IsArray() ? CodeType.CodeTypeCollectionKind.Complex : default;
+ prop.Type = propType;
+ logger.LogTrace("Creating property {name} of {type}", prop.Name, prop.Type.Name);
+ return prop;
+ }
+ private static readonly HashSet typeNamesToSkip = new() {"object", "array"};
+ private static CodeType GetPrimitiveType(OpenApiSchema typeSchema, string childType = default) {
+ var typeNames = new List{typeSchema?.Items?.Type, childType, typeSchema?.Type};
+ if(typeSchema?.AnyOf?.Any() ?? false)
+ typeNames.AddRange(typeSchema.AnyOf.Select(x => x.Type)); // double is sometimes an anyof string, number and enum
+ // first value that's not null, and not "object" for primitive collections, the items type matters
+ var typeName = typeNames.FirstOrDefault(x => !string.IsNullOrEmpty(x) && !typeNamesToSkip.Contains(x));
+
+ if(string.IsNullOrEmpty(typeName))
+ return null;
+ var format = typeSchema?.Format ?? typeSchema?.Items?.Format;
+ var isExternal = false;
+ if (typeSchema?.Items?.Enum?.Any() ?? false)
+ typeName = childType;
+ else if("string".Equals(typeName, StringComparison.OrdinalIgnoreCase)) {
isExternal = true;
- return new CodeType {
- Name = typeName,
- IsExternal = isExternal,
- };
- }
- private const string RequestBodyBinaryContentType = "application/octet-stream";
- private static readonly HashSet noContentStatusCodes = new() { "201", "202", "204" };
- private void CreateOperationMethods(OpenApiUrlTreeNode currentNode, OperationType operationType, OpenApiOperation operation, CodeClass parentClass)
- {
- var parameterClass = CreateOperationParameter(currentNode, operationType, operation);
-
- var schema = operation.GetResponseSchema();
- var method = (HttpMethod)Enum.Parse(typeof(HttpMethod), operationType.ToString());
- var executorMethod = new CodeMethod {
- Name = operationType.ToString(),
- MethodKind = CodeMethodKind.RequestExecutor,
- HttpMethod = method,
- Description = operation.Description ?? operation.Summary,
- };
- parentClass.AddMethod(executorMethod);
- if (schema != null)
+ if("date-time".Equals(format, StringComparison.OrdinalIgnoreCase))
+ typeName = "DateTimeOffset";
+ else if("duration".Equals(format, StringComparison.OrdinalIgnoreCase))
+ typeName = "TimeSpan";
+ else if("date".Equals(format, StringComparison.OrdinalIgnoreCase))
+ typeName = "DateOnly";
+ else if("time".Equals(format, StringComparison.OrdinalIgnoreCase))
+ typeName = "TimeOnly";
+ else if ("base64url".Equals(format, StringComparison.OrdinalIgnoreCase))
+ typeName = "binary";
+ } else if ("double".Equals(format, StringComparison.OrdinalIgnoreCase) ||
+ "float".Equals(format, StringComparison.OrdinalIgnoreCase) ||
+ "int64".Equals(format, StringComparison.OrdinalIgnoreCase) ||
+ "decimal".Equals(format, StringComparison.OrdinalIgnoreCase)) {
+ isExternal = true;
+ typeName = format.ToLowerInvariant();
+ } else if ("boolean".Equals(typeName, StringComparison.OrdinalIgnoreCase) ||
+ "integer".Equals(typeName, StringComparison.OrdinalIgnoreCase))
+ isExternal = true;
+ return new CodeType {
+ Name = typeName,
+ IsExternal = isExternal,
+ };
+ }
+ private const string RequestBodyBinaryContentType = "application/octet-stream";
+ private static readonly HashSet noContentStatusCodes = new() { "201", "202", "204" };
+ private static readonly HashSet errorStatusCodes = new(Enumerable.Range(400, 599).Select(x => x.ToString())
+ .Concat(new[] { "4XX", "5XX" }), StringComparer.OrdinalIgnoreCase);
+
+ 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 parentElement = string.IsNullOrEmpty(response.Value.Reference?.Id) && string.IsNullOrEmpty(errorSchema?.Reference?.Id)
+ ? executorMethod as CodeElement
+ : modelsNamespace;
+ var errorType = CreateModelDeclarations(currentNode, errorSchema, operation, parentElement, $"{errorCode}Error", response: response.Value);
+ if (errorType is CodeType codeType &&
+ codeType.TypeDefinition is CodeClass codeClass &&
+ !codeClass.IsErrorDefinition)
{
- var returnType = CreateModelDeclarations(currentNode, schema, operation, executorMethod, "Response");
- executorMethod.ReturnType = returnType ?? throw new InvalidOperationException("Could not resolve return type for operation");
- } else {
- var returnType = voidType;
- if(operation.Responses.Any(x => x.Value.Content.ContainsKey(RequestBodyBinaryContentType)))
- returnType = "binary";
- else if(!operation.Responses.Any(x => noContentStatusCodes.Contains(x.Key)))
- logger.LogWarning($"could not find operation return type {operationType} {currentNode.Path}");
- executorMethod.ReturnType = new CodeType { Name = returnType, IsExternal = true, };
+ codeClass.IsErrorDefinition = true;
}
-
-
- AddRequestBuilderMethodParameters(currentNode, operation, parameterClass, executorMethod);
-
- var handlerParam = new CodeParameter {
- Name = "responseHandler",
- Optional = true,
- ParameterKind = CodeParameterKind.ResponseHandler,
- Description = "Response handler to use in place of the default response handling provided by the core service",
- Type = new CodeType { Name = "IResponseHandler", IsExternal = true },
- };
- executorMethod.AddParameter(handlerParam);// Add response handler parameter
-
- var cancellationParam = new CodeParameter{
- Name = "cancellationToken",
- Optional = true,
- ParameterKind = CodeParameterKind.Cancellation,
- Description = "Cancellation token to use when cancelling requests",
- Type = new CodeType { Name = "CancellationToken", IsExternal = true },
- };
- executorMethod.AddParameter(cancellationParam);// Add cancellation token parameter
- logger.LogTrace("Creating method {name} of {type}", executorMethod.Name, executorMethod.ReturnType);
-
- var generatorMethod = new CodeMethod {
- Name = $"Create{operationType.ToString().ToFirstCharacterUpperCase()}RequestInformation",
- MethodKind = CodeMethodKind.RequestGenerator,
- IsAsync = false,
- HttpMethod = method,
- Description = operation.Description ?? operation.Summary,
- ReturnType = new CodeType { Name = "RequestInformation", IsNullable = false, IsExternal = true},
+ executorMethod.ErrorMappings.Add(errorCode, errorType);
+ }
+ }
+ private void CreateOperationMethods(OpenApiUrlTreeNode currentNode, OperationType operationType, OpenApiOperation operation, CodeClass parentClass)
+ {
+ var parameterClass = CreateOperationParameter(currentNode, operationType, operation);
+
+ var schema = operation.GetResponseSchema();
+ var method = (HttpMethod)Enum.Parse(typeof(HttpMethod), operationType.ToString());
+ var executorMethod = new CodeMethod {
+ Name = operationType.ToString(),
+ MethodKind = CodeMethodKind.RequestExecutor,
+ HttpMethod = method,
+ Description = operation.Description ?? operation.Summary,
+ };
+ parentClass.AddMethod(executorMethod);
+ AddErrorMappingsForExecutorMethod(currentNode, operation, executorMethod);
+ if (schema != null)
+ {
+ var returnType = CreateModelDeclarations(currentNode, schema, operation, executorMethod, "Response");
+ executorMethod.ReturnType = returnType ?? throw new InvalidOperationException("Could not resolve return type for operation");
+ } else {
+ var returnType = voidType;
+ if(operation.Responses.Any(x => x.Value.Content.ContainsKey(RequestBodyBinaryContentType)))
+ returnType = "binary";
+ else if(!operation.Responses.Any(x => noContentStatusCodes.Contains(x.Key)))
+ logger.LogWarning($"could not find operation return type {operationType} {currentNode.Path}");
+ executorMethod.ReturnType = new CodeType { Name = returnType, IsExternal = true, };
+ }
+
+
+ AddRequestBuilderMethodParameters(currentNode, operation, parameterClass, executorMethod);
+
+ var handlerParam = new CodeParameter {
+ Name = "responseHandler",
+ Optional = true,
+ ParameterKind = CodeParameterKind.ResponseHandler,
+ Description = "Response handler to use in place of the default response handling provided by the core service",
+ Type = new CodeType { Name = "IResponseHandler", IsExternal = true },
+ };
+ executorMethod.AddParameter(handlerParam);// Add response handler parameter
+
+ var cancellationParam = new CodeParameter{
+ Name = "cancellationToken",
+ Optional = true,
+ ParameterKind = CodeParameterKind.Cancellation,
+ Description = "Cancellation token to use when cancelling requests",
+ Type = new CodeType { Name = "CancellationToken", IsExternal = true },
+ };
+ executorMethod.AddParameter(cancellationParam);// Add cancellation token parameter
+ logger.LogTrace("Creating method {name} of {type}", executorMethod.Name, executorMethod.ReturnType);
+
+ var generatorMethod = new CodeMethod {
+ Name = $"Create{operationType.ToString().ToFirstCharacterUpperCase()}RequestInformation",
+ MethodKind = CodeMethodKind.RequestGenerator,
+ IsAsync = false,
+ HttpMethod = method,
+ Description = operation.Description ?? operation.Summary,
+ ReturnType = new CodeType { Name = "RequestInformation", IsNullable = false, IsExternal = true},
+ };
+ parentClass.AddMethod(generatorMethod);
+ AddRequestBuilderMethodParameters(currentNode, operation, parameterClass, generatorMethod);
+ logger.LogTrace("Creating method {name} of {type}", generatorMethod.Name, generatorMethod.ReturnType);
+ }
+ private void AddRequestBuilderMethodParameters(OpenApiUrlTreeNode currentNode, OpenApiOperation operation, CodeClass parameterClass, CodeMethod method) {
+ var nonBinaryRequestBody = operation.RequestBody?.Content?.FirstOrDefault(x => !RequestBodyBinaryContentType.Equals(x.Key, StringComparison.OrdinalIgnoreCase));
+ if (nonBinaryRequestBody.HasValue && nonBinaryRequestBody.Value.Value != null)
+ {
+ var requestBodySchema = nonBinaryRequestBody.Value.Value.Schema;
+ var requestBodyType = CreateModelDeclarations(currentNode, requestBodySchema, operation, method, "RequestBody");
+ method.AddParameter(new CodeParameter {
+ Name = "body",
+ Type = requestBodyType,
+ Optional = false,
+ ParameterKind = CodeParameterKind.RequestBody,
+ Description = requestBodySchema.Description
+ });
+ method.ContentType = nonBinaryRequestBody.Value.Key;
+ } else if (operation.RequestBody?.Content?.ContainsKey(RequestBodyBinaryContentType) ?? false) {
+ var nParam = new CodeParameter {
+ Name = "body",
+ Optional = false,
+ ParameterKind = CodeParameterKind.RequestBody,
+ Description = $"Binary request body",
+ Type = new CodeType {
+ Name = "binary",
+ IsExternal = true,
+ IsNullable = false,
+ },
};
- parentClass.AddMethod(generatorMethod);
- AddRequestBuilderMethodParameters(currentNode, operation, parameterClass, generatorMethod);
- logger.LogTrace("Creating method {name} of {type}", generatorMethod.Name, generatorMethod.ReturnType);
+ method.AddParameter(nParam);
}
- private void AddRequestBuilderMethodParameters(OpenApiUrlTreeNode currentNode, OpenApiOperation operation, CodeClass parameterClass, CodeMethod method) {
- var nonBinaryRequestBody = operation.RequestBody?.Content?.FirstOrDefault(x => !RequestBodyBinaryContentType.Equals(x.Key, StringComparison.OrdinalIgnoreCase));
- if (nonBinaryRequestBody.HasValue && nonBinaryRequestBody.Value.Value != null)
+ if(parameterClass != null) {
+ var qsParam = new CodeParameter
{
- var requestBodySchema = nonBinaryRequestBody.Value.Value.Schema;
- var requestBodyType = CreateModelDeclarations(currentNode, requestBodySchema, operation, method, "RequestBody");
- method.AddParameter(new CodeParameter {
- Name = "body",
- Type = requestBodyType,
- Optional = false,
- ParameterKind = CodeParameterKind.RequestBody,
- Description = requestBodySchema.Description
- });
- method.ContentType = nonBinaryRequestBody.Value.Key;
- } else if (operation.RequestBody?.Content?.ContainsKey(RequestBodyBinaryContentType) ?? false) {
- var nParam = new CodeParameter {
- Name = "body",
- Optional = false,
- ParameterKind = CodeParameterKind.RequestBody,
- Description = $"Binary request body",
- Type = new CodeType {
- Name = "binary",
- IsExternal = true,
- IsNullable = false,
- },
- };
- method.AddParameter(nParam);
- }
- if(parameterClass != null) {
- var qsParam = new CodeParameter
- {
- Name = "q",
- Optional = true,
- ParameterKind = CodeParameterKind.QueryParameter,
- Description = "Request query parameters",
- Type = new CodeType { Name = parameterClass.Name, ActionOf = true, TypeDefinition = parameterClass },
- };
- method.AddParameter(qsParam);
- }
- var headersParam = new CodeParameter {
- Name = "h",
- Optional = true,
- ParameterKind = CodeParameterKind.Headers,
- Description = "Request headers",
- Type = new CodeType { Name = "IDictionary", ActionOf = true, IsExternal = true },
- };
- method.AddParameter(headersParam);
- var optionsParam = new CodeParameter {
- Name = "o",
+ Name = "q",
Optional = true,
- ParameterKind = CodeParameterKind.Options,
- Description = "Request options",
- Type = new CodeType { Name = "IEnumerable", ActionOf = false, IsExternal = true },
- };
- method.AddParameter(optionsParam);
- }
- private string GetModelsNamespaceNameFromReferenceId(string referenceId) {
- if (string.IsNullOrEmpty(referenceId)) return referenceId;
- if(referenceId.StartsWith(config.ClientClassName, StringComparison.OrdinalIgnoreCase))
- referenceId = referenceId[config.ClientClassName.Length..];
- referenceId = referenceId.Trim(nsNameSeparator);
- var lastDotIndex = referenceId.LastIndexOf(nsNameSeparator);
- var namespaceSuffix = lastDotIndex != -1 ? referenceId[..lastDotIndex] : referenceId;
- return $"{modelsNamespace.Name}.{namespaceSuffix}";
- }
- private CodeType CreateModelDeclarationAndType(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeNamespace codeNamespace, string classNameSuffix = "") {
- var className = currentNode.GetClassName(operation: operation, suffix: classNameSuffix);
- var codeDeclaration = AddModelDeclarationIfDoesntExit(currentNode, schema, className, codeNamespace);
- return new CodeType {
- TypeDefinition = codeDeclaration,
- Name = className,
+ ParameterKind = CodeParameterKind.QueryParameter,
+ Description = "Request query parameters",
+ Type = new CodeType { Name = parameterClass.Name, ActionOf = true, TypeDefinition = parameterClass },
};
- }
- private CodeTypeBase CreateInheritedModelDeclaration(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation) {
- var allOfs = schema.AllOf.FlattenEmptyEntries(x => x.AllOf);
- CodeElement codeDeclaration = null;
- var className = string.Empty;
- foreach(var currentSchema in allOfs) {
- var referenceId = GetReferenceIdFromOriginalSchema(currentSchema, schema);
- var shortestNamespaceName = string.IsNullOrEmpty(referenceId) ? currentNode.GetNodeNamespaceFromPath(config.ClientNamespaceName) : GetModelsNamespaceNameFromReferenceId(referenceId);
- var shortestNamespace = rootNamespace.FindNamespaceByName(shortestNamespaceName);
- if(shortestNamespace == null)
- shortestNamespace = rootNamespace.AddNamespace(shortestNamespaceName);
- className = currentSchema.GetSchemaTitle() ?? currentNode.GetClassName(operation: operation);
- codeDeclaration = AddModelDeclarationIfDoesntExit(currentNode, currentSchema, className, shortestNamespace, codeDeclaration as CodeClass, true);
- }
-
- return new CodeType {
+ method.AddParameter(qsParam);
+ }
+ var headersParam = new CodeParameter {
+ Name = "h",
+ Optional = true,
+ ParameterKind = CodeParameterKind.Headers,
+ Description = "Request headers",
+ Type = new CodeType { Name = "IDictionary", ActionOf = true, IsExternal = true },
+ };
+ method.AddParameter(headersParam);
+ var optionsParam = new CodeParameter {
+ Name = "o",
+ Optional = true,
+ ParameterKind = CodeParameterKind.Options,
+ Description = "Request options",
+ Type = new CodeType { Name = "IEnumerable", ActionOf = false, IsExternal = true },
+ };
+ method.AddParameter(optionsParam);
+ }
+ private string GetModelsNamespaceNameFromReferenceId(string referenceId) {
+ if (string.IsNullOrEmpty(referenceId)) return referenceId;
+ if(referenceId.StartsWith(config.ClientClassName, StringComparison.OrdinalIgnoreCase))
+ referenceId = referenceId[config.ClientClassName.Length..];
+ referenceId = referenceId.Trim(nsNameSeparator);
+ var lastDotIndex = referenceId.LastIndexOf(nsNameSeparator);
+ var namespaceSuffix = lastDotIndex != -1 ? referenceId[..lastDotIndex] : referenceId;
+ return $"{modelsNamespace.Name}.{namespaceSuffix}";
+ }
+ private CodeType CreateModelDeclarationAndType(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeNamespace codeNamespace, string classNameSuffix = "", OpenApiResponse response = default) {
+ var className = currentNode.GetClassName(operation: operation, suffix: classNameSuffix, response: response, schema: schema);
+ var codeDeclaration = AddModelDeclarationIfDoesntExit(currentNode, schema, className, codeNamespace);
+ return new CodeType {
+ TypeDefinition = codeDeclaration,
+ Name = className,
+ };
+ }
+ private CodeTypeBase CreateInheritedModelDeclaration(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation) {
+ var allOfs = schema.AllOf.FlattenEmptyEntries(x => x.AllOf);
+ CodeElement codeDeclaration = null;
+ var className = string.Empty;
+ foreach(var currentSchema in allOfs) {
+ var referenceId = GetReferenceIdFromOriginalSchema(currentSchema, schema);
+ var shortestNamespaceName = string.IsNullOrEmpty(referenceId) ? currentNode.GetNodeNamespaceFromPath(config.ClientNamespaceName) : GetModelsNamespaceNameFromReferenceId(referenceId);
+ var shortestNamespace = rootNamespace.FindNamespaceByName(shortestNamespaceName);
+ if(shortestNamespace == null)
+ shortestNamespace = rootNamespace.AddNamespace(shortestNamespaceName);
+ className = currentSchema.GetSchemaTitle() ?? currentNode.GetClassName(operation: operation, schema: schema);
+ codeDeclaration = AddModelDeclarationIfDoesntExit(currentNode, currentSchema, className, shortestNamespace, codeDeclaration as CodeClass, true);
+ }
+
+ return new CodeType {
+ TypeDefinition = codeDeclaration,
+ Name = className,
+ };
+ }
+ private static string GetReferenceIdFromOriginalSchema(OpenApiSchema schema, OpenApiSchema parentSchema) {
+ var title = schema.Title;
+ if(!string.IsNullOrEmpty(schema.Reference?.Id)) return schema.Reference.Id;
+ if(parentSchema.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false) return parentSchema.Reference.Id;
+ if(parentSchema.Items?.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false) return parentSchema.Items.Reference.Id;
+ return (parentSchema.
+ AllOf
+ .FirstOrDefault(x => x.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false) ??
+ parentSchema.
+ AnyOf
+ .FirstOrDefault(x => x.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false) ??
+ parentSchema.
+ OneOf
+ .FirstOrDefault(x => x.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false))
+ ?.Reference?.Id;
+ }
+ private CodeTypeBase CreateUnionModelDeclaration(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, string suffixForInlineSchema) {
+ var schemas = schema.AnyOf.Union(schema.OneOf);
+ var unionType = new CodeUnionType {
+ Name = currentNode.GetClassName(operation: operation, suffix: suffixForInlineSchema, schema: schema),
+ };
+ foreach(var currentSchema in schemas) {
+ var shortestNamespaceName = currentSchema.Reference == null ? currentNode.GetNodeNamespaceFromPath(config.ClientNamespaceName) : GetModelsNamespaceNameFromReferenceId(currentSchema.Reference.Id);
+ var shortestNamespace = rootNamespace.FindNamespaceByName(shortestNamespaceName);
+ if(shortestNamespace == null)
+ shortestNamespace = rootNamespace.AddNamespace(shortestNamespaceName);
+ var className = currentSchema.GetSchemaTitle();
+ var codeDeclaration = AddModelDeclarationIfDoesntExit(currentNode, currentSchema, className, shortestNamespace);
+ unionType.AddType(new CodeType {
TypeDefinition = codeDeclaration,
Name = className,
- };
+ });
}
- private static string GetReferenceIdFromOriginalSchema(OpenApiSchema schema, OpenApiSchema parentSchema) {
- var title = schema.Title;
- if(!string.IsNullOrEmpty(schema.Reference?.Id)) return schema.Reference.Id;
- if(parentSchema.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false) return parentSchema.Reference.Id;
- if(parentSchema.Items?.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false) return parentSchema.Items.Reference.Id;
- return (parentSchema.
- AllOf
- .FirstOrDefault(x => x.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false) ??
- parentSchema.
- AnyOf
- .FirstOrDefault(x => x.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false) ??
- parentSchema.
- OneOf
- .FirstOrDefault(x => x.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false))
- ?.Reference?.Id;
+ return unionType;
+ }
+ private CodeTypeBase CreateModelDeclarations(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeElement parentElement, string suffixForInlineSchema, OpenApiResponse response = default)
+ {
+ var codeNamespace = parentElement.GetImmediateParentOfType();
+
+ if (!schema.IsReferencedSchema() && schema.Properties.Any()) { // Inline schema, i.e. specific to the Operation
+ return CreateModelDeclarationAndType(currentNode, schema, operation, codeNamespace, suffixForInlineSchema);
+ } else if(schema.IsAllOf()) {
+ return CreateInheritedModelDeclaration(currentNode, schema, operation);
+ } else if(schema.IsAnyOf() || schema.IsOneOf()) {
+ return CreateUnionModelDeclaration(currentNode, schema, operation, suffixForInlineSchema);
+ } else if(schema.IsObject()) {
+ // referenced schema, no inheritance or union type
+ var targetNamespace = GetShortestNamespace(codeNamespace, schema);
+ return CreateModelDeclarationAndType(currentNode, schema, operation, targetNamespace, response: response);
+ } else if (schema.IsArray()) {
+ // collections at root
+ var type = GetPrimitiveType(schema?.Items, string.Empty);
+ if(type == null)
+ type = CreateModelDeclarationAndType(currentNode, schema?.Items, operation, codeNamespace);
+ type.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array;
+ return type;
+ } else if(!string.IsNullOrEmpty(schema.Type))
+ return GetPrimitiveType(schema, string.Empty);
+ else throw new InvalidOperationException("un handled case, might be object type or array type");
+ }
+ private CodeElement GetExistingDeclaration(bool checkInAllNamespaces, CodeNamespace currentNamespace, OpenApiUrlTreeNode currentNode, string declarationName) {
+ var searchNameSpace = GetSearchNamespace(checkInAllNamespaces, currentNode, currentNamespace);
+ return searchNameSpace.FindChildByName(declarationName, checkInAllNamespaces) as CodeElement;
+ }
+ private CodeNamespace GetSearchNamespace(bool checkInAllNamespaces, OpenApiUrlTreeNode currentNode, CodeNamespace currentNamespace) {
+ if(checkInAllNamespaces) return rootNamespace;
+ else if (currentNode.DoesNodeBelongToItemSubnamespace()) return currentNamespace.EnsureItemNamespace();
+ else return currentNamespace;
+ }
+ private CodeElement AddModelDeclarationIfDoesntExit(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, string declarationName, CodeNamespace currentNamespace, CodeClass inheritsFrom = null, bool checkInAllNamespaces = false) {
+ var existingDeclaration = GetExistingDeclaration(checkInAllNamespaces, currentNamespace, currentNode, declarationName);
+ if(existingDeclaration == null) // we can find it in the components
+ {
+ if(schema.Enum.Any()) {
+ var newEnum = new CodeEnum {
+ Name = declarationName,
+ Options = schema.Enum.OfType().Select(x => x.Value).Where(x => !"null".Equals(x)).ToList(),//TODO set the flag property
+ Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel),
+ };
+ return currentNamespace.AddEnum(newEnum).First();
+ } else
+ return AddModelClass(currentNode, schema, declarationName, currentNamespace, inheritsFrom);
+ } else
+ return existingDeclaration;
+ }
+ private CodeNamespace GetShortestNamespace(CodeNamespace currentNamespace, OpenApiSchema currentSchema) {
+ if(!string.IsNullOrEmpty(currentSchema.Reference?.Id)) {
+ var parentClassNamespaceName = GetModelsNamespaceNameFromReferenceId(currentSchema.Reference.Id);
+ return rootNamespace.AddNamespace(parentClassNamespaceName);
}
- private CodeTypeBase CreateUnionModelDeclaration(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, string suffixForInlineSchema) {
- var schemas = schema.AnyOf.Union(schema.OneOf);
- var unionType = new CodeUnionType {
- Name = currentNode.GetClassName(operation: operation, suffix: suffixForInlineSchema),
- };
- foreach(var currentSchema in schemas) {
- var shortestNamespaceName = currentSchema.Reference == null ? currentNode.GetNodeNamespaceFromPath(config.ClientNamespaceName) : GetModelsNamespaceNameFromReferenceId(currentSchema.Reference.Id);
- var shortestNamespace = rootNamespace.FindNamespaceByName(shortestNamespaceName);
- if(shortestNamespace == null)
- shortestNamespace = rootNamespace.AddNamespace(shortestNamespaceName);
- var className = currentSchema.GetSchemaTitle();
- var codeDeclaration = AddModelDeclarationIfDoesntExit(currentNode, currentSchema, className, shortestNamespace);
- unionType.AddType(new CodeType {
- TypeDefinition = codeDeclaration,
- Name = className,
- });
+ return currentNamespace;
+ }
+ private CodeClass AddModelClass(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, string declarationName, CodeNamespace currentNamespace, CodeClass inheritsFrom = null) {
+ var referencedAllOfs = schema.AllOf.Where(x => x.Reference != null);
+ if(inheritsFrom == null && referencedAllOfs.Any()) {// any non-reference would be the current class in some description styles
+ var parentSchema = referencedAllOfs.FirstOrDefault();
+ if(parentSchema != null) {
+ var parentClassNamespace = GetShortestNamespace(currentNamespace, parentSchema);
+ inheritsFrom = AddModelDeclarationIfDoesntExit(currentNode, parentSchema, parentSchema.GetSchemaTitle(), parentClassNamespace, null, true) as CodeClass;
}
- return unionType;
}
- private CodeTypeBase CreateModelDeclarations(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeElement parentElement, string suffixForInlineSchema)
+ var newClass = currentNamespace.AddClass(new CodeClass {
+ Name = declarationName,
+ ClassKind = CodeClassKind.Model,
+ Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel)
+ }).First();
+ if(inheritsFrom != null) {
+ var declaration = newClass.StartBlock as CodeClass.Declaration;
+ declaration.Inherits = new CodeType { TypeDefinition = inheritsFrom, Name = inheritsFrom.Name };
+ }
+ CreatePropertiesForModelClass(currentNode, schema, currentNamespace, newClass);
+ return newClass;
+ }
+ private void CreatePropertiesForModelClass(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, CodeNamespace ns, CodeClass model) {
+ AddSerializationMembers(model, schema?.AdditionalPropertiesAllowed ?? false, config.UsesBackingStore);
+ if(schema?.Properties?.Any() ?? false)
{
- var codeNamespace = parentElement.GetImmediateParentOfType();
-
- if (!schema.IsReferencedSchema() && schema.Properties.Any()) { // Inline schema, i.e. specific to the Operation
- return CreateModelDeclarationAndType(currentNode, schema, operation, codeNamespace, suffixForInlineSchema);
- } else if(schema.IsAllOf()) {
- return CreateInheritedModelDeclaration(currentNode, schema, operation);
- } else if(schema.IsAnyOf() || schema.IsOneOf()) {
- return CreateUnionModelDeclaration(currentNode, schema, operation, suffixForInlineSchema);
- } else if(schema.IsObject()) {
- // referenced schema, no inheritance or union type
- var targetNamespace = GetShortestNamespace(codeNamespace, schema);
- return CreateModelDeclarationAndType(currentNode, schema, operation, targetNamespace);
- } else if (schema.IsArray()) {
- // collections at root
- var type = GetPrimitiveType(schema?.Items, string.Empty);
- if(type == null)
- type = CreateModelDeclarationAndType(currentNode, schema?.Items, operation, codeNamespace);
- type.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array;
- return type;
- } else if(!string.IsNullOrEmpty(schema.Type))
- return GetPrimitiveType(schema, string.Empty);
- else throw new InvalidOperationException("un handled case, might be object type or array type");
+ model.AddProperty(schema
+ .Properties
+ .Select(x => {
+ var propertyDefinitionSchema = x.Value.GetNonEmptySchemas().FirstOrDefault();
+ var className = propertyDefinitionSchema.GetSchemaTitle();
+ CodeElement definition = default;
+ if(propertyDefinitionSchema != null) {
+ if(string.IsNullOrEmpty(className))
+ className = $"{model.Name}_{x.Key}";
+ var shortestNamespaceName = GetModelsNamespaceNameFromReferenceId(propertyDefinitionSchema.Reference?.Id);
+ var targetNamespace = string.IsNullOrEmpty(shortestNamespaceName) ? ns :
+ (rootNamespace.FindNamespaceByName(shortestNamespaceName) ?? rootNamespace.AddNamespace(shortestNamespaceName));
+ definition = AddModelDeclarationIfDoesntExit(currentNode, propertyDefinitionSchema, className, targetNamespace, null, true);
+ }
+ return CreateProperty(x.Key, className ?? x.Value.Type, typeSchema: x.Value, typeDefinition: definition);
+ })
+ .ToArray());
+ }
+ else if(schema?.AllOf?.Any(x => x.IsObject()) ?? false)
+ CreatePropertiesForModelClass(currentNode, schema.AllOf.Last(x => x.IsObject()), ns, model);
+ }
+ private const string FieldDeserializersMethodName = "GetFieldDeserializers";
+ private const string SerializeMethodName = "Serialize";
+ private const string AdditionalDataPropName = "AdditionalData";
+ private const string BackingStorePropertyName = "BackingStore";
+ private const string BackingStoreInterface = "IBackingStore";
+ private const string BackedModelInterface = "IBackedModel";
+ internal static void AddSerializationMembers(CodeClass model, bool includeAdditionalProperties, bool usesBackingStore) {
+ var serializationPropsType = $"IDictionary>";
+ if(!model.ContainsMember(FieldDeserializersMethodName)) {
+ var deserializeProp = new CodeMethod {
+ Name = FieldDeserializersMethodName,
+ MethodKind = CodeMethodKind.Deserializer,
+ Access = AccessModifier.Public,
+ Description = "The deserialization information for the current model",
+ IsAsync = false,
+ ReturnType = new CodeType {
+ Name = serializationPropsType,
+ IsNullable = false,
+ IsExternal = true,
+ },
+ };
+ model.AddMethod(deserializeProp);
}
- private CodeElement GetExistingDeclaration(bool checkInAllNamespaces, CodeNamespace currentNamespace, OpenApiUrlTreeNode currentNode, string declarationName) {
- var searchNameSpace = GetSearchNamespace(checkInAllNamespaces, currentNode, currentNamespace);
- return searchNameSpace.FindChildByName(declarationName, checkInAllNamespaces) as CodeElement;
+ if(!model.ContainsMember(SerializeMethodName)) {
+ var serializeMethod = new CodeMethod {
+ Name = SerializeMethodName,
+ MethodKind = CodeMethodKind.Serializer,
+ IsAsync = false,
+ Description = $"Serializes information the current object",
+ ReturnType = new CodeType { Name = voidType, IsNullable = false, IsExternal = true },
+ };
+ var parameter = new CodeParameter {
+ Name = "writer",
+ Description = "Serialization writer to use to serialize this model",
+ ParameterKind = CodeParameterKind.Serializer,
+ Type = new CodeType { Name = "ISerializationWriter", IsExternal = true, IsNullable = false },
+ };
+ serializeMethod.AddParameter(parameter);
+
+ model.AddMethod(serializeMethod);
+ }
+ if(!model.ContainsMember(AdditionalDataPropName) &&
+ includeAdditionalProperties &&
+ !(model.GetGreatestGrandparent(model)?.ContainsMember(AdditionalDataPropName) ?? false)) {
+ // we don't want to add the property if the parent already has it
+ var additionalDataProp = new CodeProperty {
+ Name = AdditionalDataPropName,
+ Access = AccessModifier.Public,
+ DefaultValue = "new Dictionary()",
+ PropertyKind = CodePropertyKind.AdditionalData,
+ Description = "Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.",
+ Type = new CodeType {
+ Name = "IDictionary",
+ IsNullable = false,
+ IsExternal = true,
+ },
+ };
+ model.AddProperty(additionalDataProp);
}
- private CodeNamespace GetSearchNamespace(bool checkInAllNamespaces, OpenApiUrlTreeNode currentNode, CodeNamespace currentNamespace) {
- if(checkInAllNamespaces) return rootNamespace;
- else if (currentNode.DoesNodeBelongToItemSubnamespace()) return currentNamespace.EnsureItemNamespace();
- else return currentNamespace;
+ if(!model.ContainsMember(BackingStorePropertyName) &&
+ usesBackingStore &&
+ !(model.GetGreatestGrandparent(model)?.ContainsMember(BackingStorePropertyName) ?? false)) {
+ var backingStoreProperty = new CodeProperty {
+ Name = BackingStorePropertyName,
+ Access = AccessModifier.Public,
+ DefaultValue = $"BackingStoreFactorySingleton.Instance.CreateBackingStore()",
+ PropertyKind = CodePropertyKind.BackingStore,
+ Description = "Stores model information.",
+ ReadOnly = true,
+ Type = new CodeType {
+ Name = BackingStoreInterface,
+ IsNullable = false,
+ IsExternal = true,
+ },
+ };
+ model.AddProperty(backingStoreProperty);
+ (model.StartBlock as CodeClass.Declaration).AddImplements(new CodeType {
+ Name = BackedModelInterface,
+ IsExternal = true,
+ });
}
- private CodeElement AddModelDeclarationIfDoesntExit(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, string declarationName, CodeNamespace currentNamespace, CodeClass inheritsFrom = null, bool checkInAllNamespaces = false) {
- var existingDeclaration = GetExistingDeclaration(checkInAllNamespaces, currentNamespace, currentNode, declarationName);
- if(existingDeclaration == null) // we can find it in the components
+ }
+ private CodeClass CreateOperationParameter(OpenApiUrlTreeNode node, OperationType operationType, OpenApiOperation operation)
+ {
+ var parameters = node.PathItems[Constants.DefaultOpenApiLabel].Parameters.Union(operation.Parameters).Where(p => p.In == ParameterLocation.Query);
+ if(parameters.Any()) {
+ var parameterClass = new CodeClass
{
- if(schema.Enum.Any()) {
- var newEnum = new CodeEnum {
- Name = declarationName,
- Options = schema.Enum.OfType().Select(x => x.Value).Where(x => !"null".Equals(x)).ToList(),//TODO set the flag property
- Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel),
- };
- return currentNamespace.AddEnum(newEnum).First();
- } else
- return AddModelClass(currentNode, schema, declarationName, currentNamespace, inheritsFrom);
- } else
- return existingDeclaration;
- }
- private CodeNamespace GetShortestNamespace(CodeNamespace currentNamespace, OpenApiSchema currentSchema) {
- if(!string.IsNullOrEmpty(currentSchema.Reference?.Id)) {
- var parentClassNamespaceName = GetModelsNamespaceNameFromReferenceId(currentSchema.Reference.Id);
- return rootNamespace.AddNamespace(parentClassNamespaceName);
- }
- return currentNamespace;
- }
- private CodeClass AddModelClass(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, string declarationName, CodeNamespace currentNamespace, CodeClass inheritsFrom = null) {
- var referencedAllOfs = schema.AllOf.Where(x => x.Reference != null);
- if(inheritsFrom == null && referencedAllOfs.Any()) {// any non-reference would be the current class in some description styles
- var parentSchema = referencedAllOfs.FirstOrDefault();
- if(parentSchema != null) {
- var parentClassNamespace = GetShortestNamespace(currentNamespace, parentSchema);
- inheritsFrom = AddModelDeclarationIfDoesntExit(currentNode, parentSchema, parentSchema.GetSchemaTitle(), parentClassNamespace, null, true) as CodeClass;
- }
- }
- var newClass = currentNamespace.AddClass(new CodeClass {
- Name = declarationName,
- ClassKind = CodeClassKind.Model,
- Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel)
- }).First();
- if(inheritsFrom != null) {
- var declaration = newClass.StartBlock as CodeClass.Declaration;
- declaration.Inherits = new CodeType { TypeDefinition = inheritsFrom, Name = inheritsFrom.Name };
- }
- CreatePropertiesForModelClass(currentNode, schema, currentNamespace, newClass);
- return newClass;
- }
- private void CreatePropertiesForModelClass(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, CodeNamespace ns, CodeClass model) {
- AddSerializationMembers(model, schema?.AdditionalPropertiesAllowed ?? false, config.UsesBackingStore);
- if(schema?.Properties?.Any() ?? false)
+ Name = operationType.ToString() + "QueryParameters",
+ ClassKind = CodeClassKind.QueryParameters,
+ Description = operation.Description ?? operation.Summary
+ };
+ foreach (var parameter in parameters)
{
- model.AddProperty(schema
- .Properties
- .Select(x => {
- var propertyDefinitionSchema = x.Value.GetNonEmptySchemas().FirstOrDefault();
- var className = propertyDefinitionSchema.GetSchemaTitle();
- CodeElement definition = default;
- if(propertyDefinitionSchema != null) {
- if(string.IsNullOrEmpty(className))
- className = $"{model.Name}_{x.Key}";
- var shortestNamespaceName = GetModelsNamespaceNameFromReferenceId(propertyDefinitionSchema.Reference?.Id);
- var targetNamespace = string.IsNullOrEmpty(shortestNamespaceName) ? ns :
- (rootNamespace.FindNamespaceByName(shortestNamespaceName) ?? rootNamespace.AddNamespace(shortestNamespaceName));
- definition = AddModelDeclarationIfDoesntExit(currentNode, propertyDefinitionSchema, className, targetNamespace, null, true);
- }
- return CreateProperty(x.Key, className ?? x.Value.Type, typeSchema: x.Value, typeDefinition: definition);
- })
- .ToArray());
- }
- else if(schema?.AllOf?.Any(x => x.IsObject()) ?? false)
- CreatePropertiesForModelClass(currentNode, schema.AllOf.Last(x => x.IsObject()), ns, model);
- }
- private const string FieldDeserializersMethodName = "GetFieldDeserializers";
- private const string SerializeMethodName = "Serialize";
- private const string AdditionalDataPropName = "AdditionalData";
- private const string BackingStorePropertyName = "BackingStore";
- private const string BackingStoreInterface = "IBackingStore";
- private const string BackedModelInterface = "IBackedModel";
- internal static void AddSerializationMembers(CodeClass model, bool includeAdditionalProperties, bool usesBackingStore) {
- var serializationPropsType = $"IDictionary>";
- if(!model.ContainsMember(FieldDeserializersMethodName)) {
- var deserializeProp = new CodeMethod {
- Name = FieldDeserializersMethodName,
- MethodKind = CodeMethodKind.Deserializer,
- Access = AccessModifier.Public,
- Description = "The deserialization information for the current model",
- IsAsync = false,
- ReturnType = new CodeType {
- Name = serializationPropsType,
- IsNullable = false,
- IsExternal = true,
- },
- };
- model.AddMethod(deserializeProp);
- }
- if(!model.ContainsMember(SerializeMethodName)) {
- var serializeMethod = new CodeMethod {
- Name = SerializeMethodName,
- MethodKind = CodeMethodKind.Serializer,
- IsAsync = false,
- Description = $"Serializes information the current object",
- ReturnType = new CodeType { Name = voidType, IsNullable = false, IsExternal = true },
- };
- var parameter = new CodeParameter {
- Name = "writer",
- Description = "Serialization writer to use to serialize this model",
- ParameterKind = CodeParameterKind.Serializer,
- Type = new CodeType { Name = "ISerializationWriter", IsExternal = true, IsNullable = false },
- };
- serializeMethod.AddParameter(parameter);
-
- model.AddMethod(serializeMethod);
- }
- if(!model.ContainsMember(AdditionalDataPropName) &&
- includeAdditionalProperties &&
- !(model.GetGreatestGrandparent(model)?.ContainsMember(AdditionalDataPropName) ?? false)) {
- // we don't want to add the property if the parent already has it
- var additionalDataProp = new CodeProperty {
- Name = AdditionalDataPropName,
- Access = AccessModifier.Public,
- DefaultValue = "new Dictionary()",
- PropertyKind = CodePropertyKind.AdditionalData,
- Description = "Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.",
- Type = new CodeType {
- Name = "IDictionary",
- IsNullable = false,
- IsExternal = true,
- },
- };
- model.AddProperty(additionalDataProp);
- }
- if(!model.ContainsMember(BackingStorePropertyName) &&
- usesBackingStore &&
- !(model.GetGreatestGrandparent(model)?.ContainsMember(BackingStorePropertyName) ?? false)) {
- var backingStoreProperty = new CodeProperty {
- Name = BackingStorePropertyName,
- Access = AccessModifier.Public,
- DefaultValue = $"BackingStoreFactorySingleton.Instance.CreateBackingStore()",
- PropertyKind = CodePropertyKind.BackingStore,
- Description = "Stores model information.",
- ReadOnly = true,
- Type = new CodeType {
- Name = BackingStoreInterface,
- IsNullable = false,
+ var prop = new CodeProperty
+ {
+ Name = FixQueryParameterIdentifier(parameter),
+ Description = parameter.Description,
+ Type = new CodeType
+ {
IsExternal = true,
+ Name = parameter.Schema.Items?.Type ?? parameter.Schema.Type,
+ CollectionKind = parameter.Schema.IsArray() ? CodeType.CodeTypeCollectionKind.Array : default,
},
};
- model.AddProperty(backingStoreProperty);
- (model.StartBlock as CodeClass.Declaration).AddImplements(new CodeType {
- Name = BackedModelInterface,
- IsExternal = true,
- });
- }
- }
- private CodeClass CreateOperationParameter(OpenApiUrlTreeNode node, OperationType operationType, OpenApiOperation operation)
- {
- var parameters = node.PathItems[Constants.DefaultOpenApiLabel].Parameters.Union(operation.Parameters).Where(p => p.In == ParameterLocation.Query);
- if(parameters.Any()) {
- var parameterClass = new CodeClass
+
+ if (!parameterClass.ContainsMember(parameter.Name))
{
- Name = operationType.ToString() + "QueryParameters",
- ClassKind = CodeClassKind.QueryParameters,
- Description = operation.Description ?? operation.Summary
- };
- foreach (var parameter in parameters)
+ parameterClass.AddProperty(prop);
+ }
+ else
{
- var prop = new CodeProperty
- {
- Name = FixQueryParameterIdentifier(parameter),
- Description = parameter.Description,
- Type = new CodeType
- {
- IsExternal = true,
- Name = parameter.Schema.Items?.Type ?? parameter.Schema.Type,
- CollectionKind = parameter.Schema.IsArray() ? CodeType.CodeTypeCollectionKind.Array : default,
- },
- };
-
- if (!parameterClass.ContainsMember(parameter.Name))
- {
- parameterClass.AddProperty(prop);
- }
- else
- {
- logger.LogWarning("Ignoring duplicate parameter {name}", parameter.Name);
- }
+ logger.LogWarning("Ignoring duplicate parameter {name}", parameter.Name);
}
+ }
- return parameterClass;
- } else return null;
- }
+ return parameterClass;
+ } else return null;
+ }
- private static string FixQueryParameterIdentifier(OpenApiParameter parameter)
- {
- // Replace with regexes pulled from settings that are API specific
+ private static string FixQueryParameterIdentifier(OpenApiParameter parameter)
+ {
+ // Replace with regexes pulled from settings that are API specific
- return parameter.Name.Replace("$","").ToCamelCase();
- }
+ return parameter.Name.Replace("$","").ToCamelCase();
}
}
diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs
index 16c3c23d09..c9ad5d1052 100644
--- a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs
+++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs
@@ -508,4 +508,219 @@ public void MapsDuration(){
var progressProp = codeModel.FindChildByName("progress", true);
Assert.Equal("TimeSpan", progressProp.Type.Name);
}
+ [Fact]
+ public void AddsErrorMapping(){
+ var node = OpenApiUrlTreeNode.Create();
+ node.Attach("tasks", new OpenApiPathItem() {
+ Operations = {
+ [OperationType.Get] = new OpenApiOperation() {
+ Responses = new OpenApiResponses
+ {
+ ["200"] = new OpenApiResponse()
+ {
+ Content =
+ {
+ ["application/json"] = new OpenApiMediaType()
+ {
+ Schema = new OpenApiSchema
+ {
+ Type = "object",
+ Properties = new Dictionary {
+ {
+ "progress", new OpenApiSchema{
+ Type = "string",
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ ["4XX"] = new OpenApiResponse()
+ {
+ Content =
+ {
+ ["application/json"] = new OpenApiMediaType()
+ {
+ Schema = new OpenApiSchema
+ {
+ Type = "object",
+ Properties = new Dictionary {
+ {
+ "errorId", new OpenApiSchema{
+ Type = "string",
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ ["5XX"] = new OpenApiResponse()
+ {
+ Content =
+ {
+ ["application/json"] = new OpenApiMediaType()
+ {
+ Schema = new OpenApiSchema
+ {
+ Type = "object",
+ Properties = new Dictionary {
+ {
+ "serviceErrorId", new OpenApiSchema{
+ Type = "string",
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ ["401"] = new OpenApiResponse()
+ {
+ Content =
+ {
+ ["application/json"] = new OpenApiMediaType()
+ {
+ Schema = new OpenApiSchema
+ {
+ Type = "object",
+ Properties = new Dictionary {
+ {
+ "authenticationRealm", new OpenApiSchema{
+ Type = "string",
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }, "default");
+ var mockLogger = new Mock>();
+ var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration() { ClientClassName = "Graph", ApiRootUrl = "https://localhost" });
+ var codeModel = builder.CreateSourceModel(node);
+ var executorMethod = codeModel.FindChildByName("get", true);
+ Assert.NotNull(executorMethod);
+ Assert.NotEmpty(executorMethod.ErrorMappings);
+ Assert.Contains("4XX", executorMethod.ErrorMappings.Keys);
+ Assert.Contains("401", executorMethod.ErrorMappings.Keys);
+ Assert.Contains("5XX", executorMethod.ErrorMappings.Keys);
+ var errorType401 = codeModel.FindChildByName("tasks401Error", true);
+ Assert.NotNull(errorType401);
+ Assert.True(errorType401.IsErrorDefinition);
+ Assert.NotNull(errorType401.FindChildByName("authenticationRealm", true));
+ var errorType4XX = codeModel.FindChildByName("tasks4XXError", true);
+ Assert.NotNull(errorType4XX);
+ Assert.True(errorType4XX.IsErrorDefinition);
+ Assert.NotNull(errorType4XX.FindChildByName("errorId", true));
+ var errorType5XX = codeModel.FindChildByName("tasks5XXError", true);
+ Assert.NotNull(errorType5XX);
+ Assert.True(errorType5XX.IsErrorDefinition);
+ Assert.NotNull(errorType5XX.FindChildByName("serviceErrorId", true));
+
+ }
+ [Fact]
+ public void DoesntAddSuffixesToErrorTypesWhenComponents(){
+ var errorSchema = new OpenApiSchema {
+ Type = "object",
+ Properties = new Dictionary {
+ {
+ "errorId", new OpenApiSchema {
+ Type = "string"
+ }
+ }
+ },
+ Reference = new OpenApiReference {
+ Id = "microsoft.graph.error",
+ Type = ReferenceType.Schema
+ },
+ UnresolvedReference = false
+ };
+ var errorResponse = new OpenApiResponse()
+ {
+ Content =
+ {
+ ["application/json"] = new OpenApiMediaType()
+ {
+ Schema = errorSchema
+ }
+ },
+ Reference = new OpenApiReference {
+ Id = "microsoft.graph.error",
+ Type = ReferenceType.Response
+ },
+ UnresolvedReference = false
+ };
+ var document = new OpenApiDocument() {
+ Paths = new OpenApiPaths() {
+ ["tasks"] = new OpenApiPathItem() {
+ Operations = {
+ [OperationType.Get] = new OpenApiOperation() {
+ Responses = new OpenApiResponses
+ {
+ ["200"] = new OpenApiResponse()
+ {
+ Content =
+ {
+ ["application/json"] = new OpenApiMediaType()
+ {
+ Schema = new OpenApiSchema
+ {
+ Type = "object",
+ Properties = new Dictionary {
+ {
+ "progress", new OpenApiSchema{
+ Type = "string",
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ ["4XX"] = errorResponse,
+ ["5XX"] = errorResponse,
+ ["401"] = errorResponse
+ }
+ }
+ }
+ }
+ },
+ Components = new OpenApiComponents() {
+ Schemas = new Dictionary {
+ {
+ "microsoft.graph.error", errorSchema
+ }
+ },
+ Responses = new Dictionary {
+ {
+ "microsoft.graph.error", errorResponse
+ }
+ }
+ },
+ };
+ var node = OpenApiUrlTreeNode.Create(document, "default");
+ var mockLogger = new Mock>();
+ var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration() { ClientClassName = "Graph", ApiRootUrl = "https://localhost" });
+ var codeModel = builder.CreateSourceModel(node);
+ var executorMethod = codeModel.FindChildByName("get", true);
+ Assert.NotNull(executorMethod);
+ Assert.NotEmpty(executorMethod.ErrorMappings);
+ Assert.Contains("4XX", executorMethod.ErrorMappings.Keys);
+ Assert.Contains("401", executorMethod.ErrorMappings.Keys);
+ Assert.Contains("5XX", executorMethod.ErrorMappings.Keys);
+ var errorType = codeModel.FindChildByName("Error", true);
+ Assert.NotNull(errorType);
+ Assert.True(errorType.IsErrorDefinition);
+ Assert.NotNull(errorType.FindChildByName("errorId", true));
+
+ Assert.Null(codeModel.FindChildByName("tasks401Error", true));
+ Assert.Null(codeModel.FindChildByName("tasks4XXError", true));
+ Assert.Null(codeModel.FindChildByName("tasks5XXError", true));
+ }
+
}