diff --git a/src/Kiota.Builder/CodeDOM/CodeMethod.cs b/src/Kiota.Builder/CodeDOM/CodeMethod.cs
index 66b36b8fba..21699c1fe4 100644
--- a/src/Kiota.Builder/CodeDOM/CodeMethod.cs
+++ b/src/Kiota.Builder/CodeDOM/CodeMethod.cs
@@ -42,7 +42,11 @@ public enum CodeMethodKind
///
/// The override for the error message for the error/exception type.
///
- ErrorMessageOverride
+ ErrorMessageOverride,
+ ///
+ /// Factory method for error classes that accepts an error message parameter.
+ ///
+ FactoryWithErrorMessage,
}
public enum HttpMethod
{
@@ -254,6 +258,7 @@ public void DeduplicateErrorMappings()
public bool HasUrlTemplateOverride => !string.IsNullOrEmpty(UrlTemplateOverride);
private ConcurrentDictionary errorMappings = new(StringComparer.OrdinalIgnoreCase);
+ private ConcurrentDictionary errorDescriptions = new(StringComparer.OrdinalIgnoreCase);
///
/// Mapping of the error code and response types for this method.
@@ -265,6 +270,17 @@ public IOrderedEnumerable> ErrorMappings
return errorMappings.OrderBy(static x => x.Key);
}
}
+
+ ///
+ /// Mapping of the error code and response descriptions from OpenAPI spec for this method.
+ ///
+ public IOrderedEnumerable> ErrorDescriptions
+ {
+ get
+ {
+ return errorDescriptions.OrderBy(static x => x.Key, StringComparer.Ordinal);
+ }
+ }
public bool HasErrorMappingCode(string code)
{
ArgumentException.ThrowIfNullOrEmpty(code);
@@ -304,6 +320,7 @@ public object Clone()
Parent = Parent,
OriginalIndexer = OriginalIndexer,
errorMappings = new(errorMappings),
+ errorDescriptions = new(errorDescriptions, StringComparer.Ordinal),
AcceptedResponseTypes = new List(AcceptedResponseTypes),
PagingInformation = PagingInformation?.Clone() as PagingInformation,
Documentation = (CodeDocumentation)Documentation.Clone(),
@@ -324,10 +341,19 @@ public void AddParameter(params CodeParameter[] methodParameters)
EnsureElementsAreChildren(methodParameters);
methodParameters.ToList().ForEach(x => parameters.TryAdd(x.Name, x));
}
- public void AddErrorMapping(string errorCode, CodeTypeBase type)
+ public void AddErrorMapping(string errorCode, CodeTypeBase type, string? description = null)
{
ArgumentNullException.ThrowIfNull(type);
ArgumentException.ThrowIfNullOrEmpty(errorCode);
- errorMappings.TryAdd(errorCode, type);
+ if (errorMappings.TryAdd(errorCode, type) && !string.IsNullOrEmpty(description))
+ {
+ errorDescriptions.TryAdd(errorCode, description);
+ }
+ }
+
+ public string? GetErrorDescription(string errorCode)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(errorCode);
+ return errorDescriptions.TryGetValue(errorCode, out var description) ? description : null;
}
}
diff --git a/src/Kiota.Builder/CodeDOM/CodeParameter.cs b/src/Kiota.Builder/CodeDOM/CodeParameter.cs
index 9a23b66659..5f2cc42cd8 100644
--- a/src/Kiota.Builder/CodeDOM/CodeParameter.cs
+++ b/src/Kiota.Builder/CodeDOM/CodeParameter.cs
@@ -63,6 +63,10 @@ public enum CodeParameterKind
/// This is only used for languages that use static functions for the serialization as opposed to instance methods since the OOP inheritance correctly handles that case.
///
SerializingDerivedType,
+ ///
+ /// Error message parameter for error/exception class constructors and factory methods.
+ ///
+ ErrorMessage,
}
public class CodeParameter : CodeTerminalWithKind, ICloneable, IDocumentedElement, IDeprecableElement
diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs
index 94a77921f2..882d42ddff 100644
--- a/src/Kiota.Builder/KiotaBuilder.cs
+++ b/src/Kiota.Builder/KiotaBuilder.cs
@@ -1293,7 +1293,7 @@ private void AddErrorMappingToExecutorMethod(OpenApiUrlTreeNode currentNode, Ope
{
if (!codeClass.IsErrorDefinition)
codeClass.IsErrorDefinition = true;
- executorMethod.AddErrorMapping(errorCode, errorType);
+ executorMethod.AddErrorMapping(errorCode, errorType, response.Description ?? string.Empty);
}
else
logger.LogWarning("Could not create error type for {Error} in {Operation}", errorCode, operation.OperationId);
diff --git a/src/Kiota.Builder/Refiners/CSharpRefiner.cs b/src/Kiota.Builder/Refiners/CSharpRefiner.cs
index 234b73d4a9..48fb020994 100644
--- a/src/Kiota.Builder/Refiners/CSharpRefiner.cs
+++ b/src/Kiota.Builder/Refiners/CSharpRefiner.cs
@@ -107,6 +107,7 @@ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken
AbstractionsNamespaceName
);
AddConstructorsForDefaultValues(generatedCode, false);
+ AddConstructorsForErrorClasses(generatedCode);
AddDiscriminatorMappingsUsingsToParentClasses(
generatedCode,
"IParseNode"
@@ -268,4 +269,33 @@ private void SetTypeAccessModifiers(CodeElement currentElement)
CrawlTree(currentElement, SetTypeAccessModifiers);
}
+
+ private static void AddConstructorsForErrorClasses(CodeElement currentElement)
+ {
+ if (currentElement is CodeClass codeClass && codeClass.IsErrorDefinition)
+ {
+ // Add parameterless constructor if not already present
+ if (!codeClass.Methods.Any(static x => x.IsOfKind(CodeMethodKind.Constructor) && !x.Parameters.Any()))
+ {
+ var parameterlessConstructor = CreateConstructor(codeClass, "Instantiates a new {TypeName} and sets the default values.");
+ codeClass.AddMethod(parameterlessConstructor);
+ }
+
+ var messageParameter = CreateErrorMessageParameter("string");
+ // Add message constructor if not already present
+ if (!codeClass.Methods.Any(static x => x.IsOfKind(CodeMethodKind.Constructor) && x.Parameters.Any(static p => p.IsOfKind(CodeParameterKind.ErrorMessage))))
+ {
+ var messageConstructor = CreateConstructor(codeClass, "Instantiates a new {TypeName} with the specified error message.");
+ messageConstructor.AddParameter(messageParameter);
+ codeClass.AddMethod(messageConstructor);
+ }
+
+ var method = TryAddErrorMessageFactoryMethod(
+ codeClass,
+ methodName: "CreateFromDiscriminatorValueWithMessage",
+ messageParameter: messageParameter,
+ parseNodeTypeName: "IParseNode");
+ }
+ CrawlTree(currentElement, AddConstructorsForErrorClasses);
+ }
}
diff --git a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs
index d5ae1ff21c..355af32714 100644
--- a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs
+++ b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs
@@ -243,28 +243,36 @@ protected static void AddConstructorsForDefaultValues(CodeElement current, bool
currentClass.Properties.Any(static x => !string.IsNullOrEmpty(x.DefaultValue)) ||
addIfInherited && DoesAnyParentHaveAPropertyWithDefaultValue(currentClass)) &&
!currentClass.Methods.Any(x => x.IsOfKind(CodeMethodKind.ClientConstructor)))
- currentClass.AddMethod(new CodeMethod
- {
- Name = "constructor",
- Kind = CodeMethodKind.Constructor,
- ReturnType = new CodeType
- {
- Name = "void"
- },
- IsAsync = false,
- Documentation = new(new() {
- { "TypeName", new CodeType() {
- IsExternal = false,
- TypeDefinition = current,
- }}
- })
- {
- DescriptionTemplate = "Instantiates a new {TypeName} and sets the default values.",
- },
- });
+ currentClass.AddMethod(CreateConstructor(currentClass, "Instantiates a new {TypeName} and sets the default values."));
CrawlTree(current, x => AddConstructorsForDefaultValues(x, addIfInherited, forceAdd, classKindsToExclude));
}
+ protected static CodeMethod CreateConstructor(CodeClass parentClass, string descriptionTemplate, AccessModifier access = AccessModifier.Public)
+ {
+ return new CodeMethod
+ {
+ Name = "constructor",
+ Kind = CodeMethodKind.Constructor,
+ ReturnType = new CodeType
+ {
+ Name = "void",
+ IsExternal = true
+ },
+ IsAsync = false,
+ IsStatic = false,
+ Access = access,
+ Documentation = new(new() {
+ { "TypeName", new CodeType() {
+ IsExternal = false,
+ TypeDefinition = parentClass,
+ }}
+ })
+ {
+ DescriptionTemplate = descriptionTemplate,
+ },
+ };
+ }
+
protected static void ReplaceReservedModelTypes(CodeElement current, IReservedNamesProvider provider, Func replacement) =>
ReplaceReservedNames(current,
provider,
@@ -1618,4 +1626,118 @@ protected static void DeduplicateErrorMappings(CodeElement codeElement)
}
CrawlTree(codeElement, DeduplicateErrorMappings);
}
+
+ ///
+ /// Creates a CodeParameter for error messages with language-specific configuration.
+ ///
+ /// The type name for the message parameter (e.g., "string", "String", "str")
+ /// Whether the parameter is optional
+ /// The default value if optional (e.g., "None", "nil")
+ /// The documentation description template
+ /// A configured CodeParameter for error messages
+ protected static CodeParameter CreateErrorMessageParameter(
+ string typeName,
+ bool optional = false,
+ string defaultValue = "")
+ {
+ return new CodeParameter
+ {
+ Name = "message",
+ Type = new CodeType { Name = typeName, IsExternal = true },
+ Kind = CodeParameterKind.ErrorMessage,
+ Optional = optional,
+ DefaultValue = defaultValue,
+ Documentation = new()
+ {
+ DescriptionTemplate = "The error message to set"
+ }
+ };
+ }
+
+ ///
+ /// Creates a factory method for error classes that accepts both parseNode and message parameters,
+ /// and adds it to the class if it doesn't already exist.
+ /// This factory method is used to create error instances with custom error messages from discriminator values.
+ ///
+ /// The error class to create the factory method for
+ /// The name of the factory method (e.g., "createFromDiscriminatorValueWithMessage")
+ /// The type name for the ParseNode parameter (e.g., "ParseNode", "IParseNode")
+ /// The type name for the message parameter (e.g., "string", "String")
+ /// Optional custom return type name; if null, uses codeClass.Name
+ /// Whether the return type should be nullable
+ /// Whether the message parameter should be optional
+ /// Whether to set the Parent property on the method
+ /// Whether the return type is external (e.g., for Go's "Parsable")
+ /// True if the method was created and added; false if it already existed
+ protected static bool TryAddErrorMessageFactoryMethod(
+ CodeClass codeClass,
+ string methodName,
+ string parseNodeTypeName,
+ CodeParameter messageParameter,
+ string? returnTypeName = null,
+ bool returnTypeIsNullable = false,
+ bool setParent = true,
+ bool returnTypeIsExternal = false,
+ string parseNodeParameterName = "parseNode")
+ {
+ ArgumentNullException.ThrowIfNull(codeClass);
+
+ // Check if method already exists
+ if (codeClass.Methods.Any(m => m.Name.Equals(methodName, StringComparison.Ordinal)))
+ {
+ return false;
+ }
+
+ var method = new CodeMethod
+ {
+ Name = methodName,
+ Kind = CodeMethodKind.FactoryWithErrorMessage,
+ IsAsync = false,
+ IsStatic = true,
+ Documentation = new(new Dictionary
+ {
+ {
+ "TypeName", new CodeType
+ {
+ IsExternal = false,
+ TypeDefinition = codeClass,
+ }
+ }
+ })
+ {
+ DescriptionTemplate = "Creates a new instance of the appropriate class based on discriminator value with a custom error message.",
+ },
+ Access = AccessModifier.Public,
+ ReturnType = new CodeType
+ {
+ Name = returnTypeName ?? codeClass.Name,
+ TypeDefinition = returnTypeIsExternal ? null : codeClass,
+ IsNullable = returnTypeIsNullable,
+ IsExternal = returnTypeIsExternal
+ }
+ };
+
+ if (setParent)
+ {
+ method.Parent = codeClass;
+ }
+
+ method.AddParameter(new CodeParameter
+ {
+ Name = parseNodeParameterName,
+ Kind = CodeParameterKind.ParseNode,
+ Type = new CodeType { Name = parseNodeTypeName, IsExternal = true },
+ Optional = false,
+ Documentation = new()
+ {
+ DescriptionTemplate = "The parse node to use to read the discriminator value and create the object"
+ }
+ });
+
+ // Add message parameter
+ method.AddParameter(messageParameter);
+
+ codeClass.AddMethod(method);
+ return true;
+ }
}
diff --git a/src/Kiota.Builder/Refiners/DartRefiner.cs b/src/Kiota.Builder/Refiners/DartRefiner.cs
index fc74705673..05b3a7daa2 100644
--- a/src/Kiota.Builder/Refiners/DartRefiner.cs
+++ b/src/Kiota.Builder/Refiners/DartRefiner.cs
@@ -173,28 +173,29 @@ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken
///error classes should always have a constructor for the copyWith method
private void AddConstructorForErrorClass(CodeElement currentElement)
{
- if (currentElement is CodeClass codeClass && codeClass.IsErrorDefinition && !codeClass.Methods.Where(static x => x.IsOfKind(CodeMethodKind.Constructor)).Any())
+ if (currentElement is CodeClass codeClass && codeClass.IsErrorDefinition)
{
- codeClass.AddMethod(new CodeMethod
+ // Add parameterless constructor if not already present
+ if (!codeClass.Methods.Any(static x => x.IsOfKind(CodeMethodKind.Constructor) && !x.Parameters.Any()))
{
- Name = "constructor",
- Kind = CodeMethodKind.Constructor,
- IsAsync = false,
- IsStatic = false,
- Documentation = new(new() {
- {"TypeName", new CodeType {
- IsExternal = false,
- TypeDefinition = codeClass,
- }
- }
- })
- {
- DescriptionTemplate = "Instantiates a new {TypeName} and sets the default values.",
- },
- Access = AccessModifier.Public,
- ReturnType = new CodeType { Name = "void", IsExternal = true },
- Parent = codeClass,
- });
+ var parameterlessConstructor = CreateConstructor(codeClass, "Instantiates a new {TypeName} and sets the default values.");
+ codeClass.AddMethod(parameterlessConstructor);
+ }
+ var messageParameter = CreateErrorMessageParameter("String");
+ // Add message constructor if not already present
+ if (!codeClass.Methods.Any(static x => x.IsOfKind(CodeMethodKind.Constructor) && x.Parameters.Any(static p => p.IsOfKind(CodeParameterKind.ErrorMessage))))
+ {
+ var messageConstructor = CreateConstructor(codeClass, "Instantiates a new {TypeName} with the specified error message.");
+ messageConstructor.AddParameter(messageParameter);
+ codeClass.AddMethod(messageConstructor);
+ }
+
+ TryAddErrorMessageFactoryMethod(
+ codeClass,
+ methodName: "createFromDiscriminatorValueWithMessage",
+ parseNodeTypeName: "ParseNode",
+ messageParameter: messageParameter,
+ setParent: false);
}
CrawlTree(currentElement, element => AddConstructorForErrorClass(element));
}
diff --git a/src/Kiota.Builder/Refiners/GoRefiner.cs b/src/Kiota.Builder/Refiners/GoRefiner.cs
index b7c2e93589..8882b5dd74 100644
--- a/src/Kiota.Builder/Refiners/GoRefiner.cs
+++ b/src/Kiota.Builder/Refiners/GoRefiner.cs
@@ -186,6 +186,7 @@ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken
"ApiError",
"github.com/microsoft/kiota-abstractions-go"
);
+ AddConstructorsForErrorClasses(generatedCode);
AddDiscriminatorMappingsUsingsToParentClasses(
generatedCode,
"ParseNode",
@@ -1009,4 +1010,36 @@ private void NormalizeNamespaceNames(CodeElement currentElement)
}
CrawlTree(currentElement, NormalizeNamespaceNames);
}
+
+ private static void AddConstructorsForErrorClasses(CodeElement currentElement)
+ {
+ if (currentElement is CodeClass codeClass && codeClass.IsErrorDefinition)
+ {
+ // Add parameterless constructor if not already present (Go writer generates New* names)
+ if (!codeClass.Methods.Any(static x => x.IsOfKind(CodeMethodKind.Constructor) && !x.Parameters.Any()))
+ {
+ var parameterlessConstructor = CreateConstructor(codeClass, "Instantiates a new {TypeName} and sets the default values.");
+ codeClass.AddMethod(parameterlessConstructor);
+ }
+
+ var messageParameter = CreateErrorMessageParameter("string");
+ // Add message constructor if not already present (Go writer generates NewWithMessage names)
+ if (!codeClass.Methods.Any(static x => x.IsOfKind(CodeMethodKind.Constructor) && x.Parameters.Any(static p => p.IsOfKind(CodeParameterKind.ErrorMessage))))
+ {
+ var messageConstructor = CreateConstructor(codeClass, "Instantiates a new {TypeName} with the specified error message.");
+ messageConstructor.AddParameter(messageParameter);
+ codeClass.AddMethod(messageConstructor);
+ }
+
+ // Add discriminator-based factory with message
+ TryAddErrorMessageFactoryMethod(
+ codeClass,
+ methodName: "CreateFromDiscriminatorValueWithMessage",
+ parseNodeTypeName: "ParseNode",
+ messageParameter: messageParameter,
+ returnTypeName: "Parsable",
+ returnTypeIsExternal: true);
+ }
+ CrawlTree(currentElement, AddConstructorsForErrorClasses);
+ }
}
diff --git a/src/Kiota.Builder/Refiners/JavaRefiner.cs b/src/Kiota.Builder/Refiners/JavaRefiner.cs
index 123a2e8fc6..d515427a25 100644
--- a/src/Kiota.Builder/Refiners/JavaRefiner.cs
+++ b/src/Kiota.Builder/Refiners/JavaRefiner.cs
@@ -148,6 +148,7 @@ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken
"ApiException",
AbstractionsNamespaceName
);
+ AddConstructorsForErrorClasses(generatedCode);
AddDiscriminatorMappingsUsingsToParentClasses(
generatedCode,
"ParseNode",
@@ -552,4 +553,32 @@ private void AddQueryParameterExtractorMethod(CodeElement currentElement, string
}
CrawlTree(currentElement, x => AddQueryParameterExtractorMethod(x, methodName));
}
+
+ private static void AddConstructorsForErrorClasses(CodeElement currentElement)
+ {
+ if (currentElement is CodeClass codeClass && codeClass.IsErrorDefinition)
+ {
+ // Add parameterless constructor if not already present
+ if (!codeClass.Methods.Any(static x => x.IsOfKind(CodeMethodKind.Constructor) && !x.Parameters.Any()))
+ {
+ var parameterlessConstructor = CreateConstructor(codeClass, "Instantiates a new {TypeName} and sets the default values.");
+ codeClass.AddMethod(parameterlessConstructor);
+ }
+ var messageParameter = CreateErrorMessageParameter("String");
+ // Add message constructor if not already present
+ if (!codeClass.Methods.Any(static x => x.IsOfKind(CodeMethodKind.Constructor) && x.Parameters.Any(static p => p.IsOfKind(CodeParameterKind.ErrorMessage))))
+ {
+ var messageConstructor = CreateConstructor(codeClass, "Instantiates a new {TypeName} with the specified error message.");
+ messageConstructor.AddParameter(messageParameter);
+ codeClass.AddMethod(messageConstructor);
+ }
+
+ TryAddErrorMessageFactoryMethod(
+ codeClass,
+ methodName: "createFromDiscriminatorValueWithMessage",
+ parseNodeTypeName: "ParseNode",
+ messageParameter: messageParameter);
+ }
+ CrawlTree(currentElement, AddConstructorsForErrorClasses);
+ }
}
diff --git a/src/Kiota.Builder/Refiners/PhpRefiner.cs b/src/Kiota.Builder/Refiners/PhpRefiner.cs
index 9c68210629..a9ba1132a3 100644
--- a/src/Kiota.Builder/Refiners/PhpRefiner.cs
+++ b/src/Kiota.Builder/Refiners/PhpRefiner.cs
@@ -80,6 +80,7 @@ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken
);
MoveClassesWithNamespaceNamesUnderNamespace(generatedCode);
AddConstructorsForDefaultValues(generatedCode, true);
+ AddConstructorsForErrorClasses(generatedCode); // Run after AddConstructorsForDefaultValues so we can add message parameter to existing constructor
cancellationToken.ThrowIfCancellationRequested();
cancellationToken.ThrowIfCancellationRequested();
CorrectParameterType(generatedCode);
@@ -471,5 +472,35 @@ private static void AddQueryParameterFactoryMethod(CodeElement codeElement)
}
CrawlTree(codeElement, AddQueryParameterFactoryMethod);
}
+
+ private static void AddConstructorsForErrorClasses(CodeElement codeElement)
+ {
+ if (codeElement is CodeClass codeClass && codeClass.IsErrorDefinition)
+ {
+ var messageParameter = CreateErrorMessageParameter("string", optional: true, defaultValue: "''");
+ // PHP only allows one __construct method, so we add an optional message parameter to the existing constructor
+ // The constructor may already exist from AddConstructorsForDefaultValues
+ var existingConstructor = codeClass.Methods.FirstOrDefault(static m => m.IsOfKind(CodeMethodKind.Constructor));
+ if (existingConstructor == null)
+ {
+ existingConstructor = CreateConstructor(codeClass, "Instantiates a new {TypeName} and sets the default values.");
+ codeClass.AddMethod(existingConstructor);
+ }
+ if (!existingConstructor.Parameters.Any(static p => p.IsOfKind(CodeParameterKind.ErrorMessage)))
+ {
+ existingConstructor.AddParameter(messageParameter);
+ }
+
+ TryAddErrorMessageFactoryMethod(
+ codeClass,
+ methodName: "createFromDiscriminatorValueWithMessage",
+ parseNodeTypeName: "ParseNode",
+ messageParameter: messageParameter,
+ returnTypeIsNullable: true,
+ setParent: false);
+ }
+
+ CrawlTree(codeElement, AddConstructorsForErrorClasses);
+ }
}
diff --git a/src/Kiota.Builder/Refiners/PythonRefiner.cs b/src/Kiota.Builder/Refiners/PythonRefiner.cs
index a42229717b..10dc96310b 100644
--- a/src/Kiota.Builder/Refiners/PythonRefiner.cs
+++ b/src/Kiota.Builder/Refiners/PythonRefiner.cs
@@ -99,6 +99,7 @@ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken
"APIError",
$"{AbstractionsPackageName}.api_error"
);
+ AddErrorMessageFactoryMethodsToPython(generatedCode);
AddGetterAndSetterMethods(generatedCode,
new() {
CodePropertyKind.Custom,
@@ -110,6 +111,7 @@ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken
string.Empty,
string.Empty);
AddConstructorsForDefaultValues(generatedCode, true);
+ RemoveConstructorsFromErrorClasses(generatedCode); // Remove constructors added to error classes
var defaultConfiguration = new GenerationConfiguration();
cancellationToken.ThrowIfCancellationRequested();
ReplaceDefaultSerializationModules(
@@ -264,6 +266,20 @@ private static void CorrectCommonNames(CodeElement currentElement)
CrawlTree(currentElement, CorrectCommonNames);
}
+ private static void RemoveConstructorsFromErrorClasses(CodeElement currentElement)
+ {
+ if (currentElement is CodeClass codeClass && codeClass.IsErrorDefinition)
+ {
+ var constructorsToRemove = codeClass.Methods
+ .Where(static m => m.IsOfKind(CodeMethodKind.Constructor, CodeMethodKind.ClientConstructor))
+ .ToList();
+ foreach (var constructor in constructorsToRemove)
+ {
+ codeClass.RemoveChildElement(constructor);
+ }
+ }
+ CrawlTree(currentElement, RemoveConstructorsFromErrorClasses);
+ }
private static void CorrectImplements(ProprietableBlockDeclaration block)
{
block.Implements.Where(x => "IAdditionalDataHolder".Equals(x.Name, StringComparison.OrdinalIgnoreCase)).ToList().ForEach(x => x.Name = x.Name[1..]); // skipping the I
@@ -390,4 +406,21 @@ public static IEnumerable codeTypeFilter(IEnumerable
},
})},
};
+
+ private static void AddErrorMessageFactoryMethodsToPython(CodeElement currentElement)
+ {
+ if (currentElement is CodeClass codeClass && codeClass.IsErrorDefinition)
+ {
+ var messageParameter = CreateErrorMessageParameter("str");
+
+ TryAddErrorMessageFactoryMethod(
+ codeClass,
+ methodName: "create_from_discriminator_value_with_message",
+ parseNodeTypeName: "ParseNode",
+ messageParameter: messageParameter,
+ parseNodeParameterName: "parse_node"
+ );
+ }
+ CrawlTree(currentElement, AddErrorMessageFactoryMethodsToPython);
+ }
}
diff --git a/src/Kiota.Builder/Refiners/RubyRefiner.cs b/src/Kiota.Builder/Refiners/RubyRefiner.cs
index b2165d289a..3565878050 100644
--- a/src/Kiota.Builder/Refiners/RubyRefiner.cs
+++ b/src/Kiota.Builder/Refiners/RubyRefiner.cs
@@ -97,6 +97,8 @@ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken
true,
false,
[CodeClassKind.RequestConfiguration]);
+ // Add constructors for error classes AFTER default constructors are added
+ AddConstructorsForErrorClasses(generatedCode);
ShortenLongNamespaceNames(generatedCode);
if (generatedCode.FindNamespaceByName(_configuration.ClientNamespaceName)?.Parent is CodeNamespace parentOfClientNS)
AddNamespaceModuleImports(parentOfClientNS, generatedCode);
@@ -336,4 +338,49 @@ private static void CorrectImplements(ProprietableBlockDeclaration block)
.ToList()
.ForEach(static x => x.Name = "MicrosoftKiotaAbstractions::AdditionalDataHolder");
}
+
+ private static void AddConstructorsForErrorClasses(CodeElement currentElement)
+ {
+ if (currentElement is CodeClass codeClass && codeClass.IsErrorDefinition)
+ {
+ var messageParameter = CreateErrorMessageParameter("String", optional: true, defaultValue: "nil");
+ // Ruby only allows one initialize method, so we modify the existing constructor to add an optional message parameter
+ var existingConstructor = codeClass.Methods.FirstOrDefault(static m => m.IsOfKind(CodeMethodKind.Constructor));
+ if (existingConstructor != null)
+ {
+ // Add message parameter to existing constructor if not already present
+ if (!existingConstructor.Parameters.Any(static p => p.IsOfKind(CodeParameterKind.ErrorMessage)))
+ {
+ existingConstructor.AddParameter(messageParameter);
+ // Update documentation to reflect the optional parameter
+ existingConstructor.Documentation.DescriptionTemplate = "Instantiates a new {TypeName} with an optional error message.";
+ }
+ }
+ else
+ {
+ // If no constructor exists, create one with the message parameter
+ var messageConstructor = new CodeMethod
+ {
+ Name = "initialize",
+ Kind = CodeMethodKind.Constructor,
+ Access = AccessModifier.Public,
+ IsAsync = false,
+ Documentation = new()
+ {
+ DescriptionTemplate = "Instantiates a new {TypeName} with an optional error message."
+ },
+ ReturnType = new CodeType { Name = "void", IsExternal = true }
+ };
+ messageConstructor.AddParameter(messageParameter);
+ codeClass.AddMethod(messageConstructor);
+ }
+
+ TryAddErrorMessageFactoryMethod(codeClass,
+ "create_from_discriminator_value_with_message",
+ "parse_node",
+ messageParameter: messageParameter
+ );
+ }
+ CrawlTree(currentElement, AddConstructorsForErrorClasses);
+ }
}
diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs
index 155a3e7031..b98b2e4163 100644
--- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs
+++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs
@@ -161,6 +161,7 @@ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken
CodePropertyKind.QueryParameter,
],
static s => s.ToCamelCase(UnderscoreArray));
+ AddConstructorsForErrorClasses(generatedCode);
IntroducesInterfacesAndFunctions(generatedCode, factoryNameCallbackFromType);
GenerateEnumObjects(generatedCode);
AliasUsingsWithSameSymbol(generatedCode);
@@ -1562,4 +1563,57 @@ private static void AddDeserializerUsingToDiscriminatorFactory(CodeElement codeE
}
CrawlTree(codeElement, AddDeserializerUsingToDiscriminatorFactory);
}
+
+ private static void AddConstructorsForErrorClasses(CodeElement codeElement)
+ {
+ if (codeElement is CodeClass codeClass && codeClass.IsErrorDefinition)
+ {
+ // Add parameterless constructor if not exists
+ if (!codeClass.Methods.Any(static m => m.IsOfKind(CodeMethodKind.Constructor) && !m.Parameters.Any()))
+ {
+ var parameterlessConstructor = new CodeMethod
+ {
+ Name = "constructor",
+ Kind = CodeMethodKind.Constructor,
+ Access = AccessModifier.Public,
+ IsAsync = false,
+ Documentation = new()
+ {
+ DescriptionTemplate = "Instantiates a new {TypeName}."
+ },
+ ReturnType = new CodeType { Name = "void", IsExternal = true }
+ };
+ codeClass.AddMethod(parameterlessConstructor);
+ }
+ var messageParameter = CreateErrorMessageParameter("string", optional: true);
+ // Add constructor with message parameter if not exists
+ if (!codeClass.Methods.Any(static m => m.IsOfKind(CodeMethodKind.Constructor) && m.Parameters.Any(static p => p.IsOfKind(CodeParameterKind.ErrorMessage))))
+ {
+ var messageConstructor = new CodeMethod
+ {
+ Name = "constructor",
+ Kind = CodeMethodKind.Constructor,
+ Access = AccessModifier.Public,
+ IsAsync = false,
+ Documentation = new()
+ {
+ DescriptionTemplate = "Instantiates a new {TypeName} with an error message."
+ },
+ ReturnType = new CodeType { Name = "void", IsExternal = true }
+ };
+ messageConstructor.AddParameter(messageParameter);
+ codeClass.AddMethod(messageConstructor);
+ }
+
+ TryAddErrorMessageFactoryMethod(
+ codeClass,
+ methodName: "createFromDiscriminatorValueWithMessage",
+ parseNodeTypeName: "ParseNode",
+ messageParameter: messageParameter,
+ returnTypeIsNullable: true,
+ setParent: false);
+ }
+
+ CrawlTree(codeElement, AddConstructorsForErrorClasses);
+ }
}
diff --git a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs
index f80d43ceb3..c62da69060 100644
--- a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs
+++ b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs
@@ -95,6 +95,9 @@ protected virtual void HandleMethodKind(CodeMethod codeElement, LanguageWriter w
case CodeMethodKind.Factory:
WriteFactoryMethodBody(codeElement, parentClass, writer);
break;
+ case CodeMethodKind.FactoryWithErrorMessage:
+ WriteFactoryMethodBodyForErrorClassWithMessage(codeElement, parentClass, writer);
+ break;
case CodeMethodKind.ComposedTypeMarker:
throw new InvalidOperationException("ComposedTypeMarker is not required as interface is explicitly implemented.");
default:
@@ -191,6 +194,11 @@ private void WriteFactoryMethodBodyForIntersectionModel(CodeMethod codeElement,
writer.WriteLine($"return {ResultVarName};");
}
private const string DiscriminatorMappingVarName = "mappingValue";
+ private void WriteFactoryMethodBodyForErrorClassWithMessage(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer)
+ {
+ var messageParam = codeElement.Parameters.FirstOrDefault(static p => p.IsOfKind(CodeParameterKind.ErrorMessage)) ?? throw new InvalidOperationException($"FactoryWithErrorMessage should have a message parameter");
+ writer.WriteLine($"return new {parentClass.GetFullName()}({messageParam.Name});");
+ }
private void WriteFactoryMethodBody(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer)
{
var parseNodeParameter = codeElement.Parameters.OfKind(CodeParameterKind.ParseNode) ?? throw new InvalidOperationException("Factory method should have a ParseNode parameter");
@@ -400,9 +408,27 @@ protected void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams re
errorMappingVarName = "errorMapping";
writer.WriteLine($"var {errorMappingVarName} = new Dictionary>");
writer.StartBlock();
- foreach (var errorMapping in codeElement.ErrorMappings.Where(errorMapping => errorMapping.Value.AllTypes.FirstOrDefault()?.TypeDefinition is CodeClass))
+ foreach (var errorMapping in codeElement.ErrorMappings)
{
- writer.WriteLine($"{{ \"{errorMapping.Key.ToUpperInvariant()}\", {conventions.GetTypeString(errorMapping.Value, codeElement, false)}.CreateFromDiscriminatorValue }},");
+ var typeName = conventions.GetTypeString(errorMapping.Value, codeElement, false);
+ var errorKey = errorMapping.Key.ToUpperInvariant();
+
+ if (errorMapping.Value.AllTypes.FirstOrDefault()?.TypeDefinition is CodeClass { IsErrorDefinition: true })
+ {
+ var errorDescription = codeElement.GetErrorDescription(errorMapping.Key);
+ if (!string.IsNullOrEmpty(errorDescription))
+ {
+ writer.WriteLine($"{{ \"{errorKey}\", (parseNode) => {typeName}.CreateFromDiscriminatorValueWithMessage(parseNode, \"{errorDescription.SanitizeDoubleQuote()}\") }},");
+ }
+ else
+ {
+ writer.WriteLine($"{{ \"{errorKey}\", {typeName}.CreateFromDiscriminatorValue }},");
+ }
+ }
+ else if (errorMapping.Value.AllTypes.FirstOrDefault()?.TypeDefinition is CodeClass)
+ {
+ writer.WriteLine($"{{ \"{errorKey}\", {typeName}.CreateFromDiscriminatorValue }},");
+ }
}
writer.CloseBlock("};");
}
@@ -560,7 +586,7 @@ protected string GetSendRequestMethodName(bool isVoid, CodeElement currentElemen
private void WriteMethodDocumentation(CodeMethod code, LanguageWriter writer)
{
conventions.WriteLongDescription(code, writer);
- if (!"void".Equals(code.ReturnType.Name, StringComparison.OrdinalIgnoreCase) && code.Kind is not CodeMethodKind.ClientConstructor or CodeMethodKind.Constructor)
+ if (!"void".Equals(code.ReturnType.Name, StringComparison.OrdinalIgnoreCase) && code.Kind is not (CodeMethodKind.ClientConstructor or CodeMethodKind.Constructor))
conventions.WriteAdditionalDescriptionItem($"A {conventions.GetTypeStringForDocumentation(code.ReturnType, code)}", writer);
foreach (var paramWithDescription in code.Parameters
.Where(static x => x.Documentation.DescriptionAvailable)
@@ -609,9 +635,16 @@ private static string GetBaseSuffix(bool isConstructor, bool inherits, CodeClass
}
return " : base()";
}
+ // For error classes with message constructor, pass the message to base constructor
+ else if (isConstructor && parentClass.IsErrorDefinition &&
+ currentMethod.Parameters.Any(static p => p.IsOfKind(CodeParameterKind.ErrorMessage)))
+ {
+ return " : base(message)";
+ }
return string.Empty;
}
+
private void WriteMethodPrototype(CodeMethod code, CodeClass parentClass, LanguageWriter writer, string returnType, bool inherits, bool isVoid)
{
var staticModifier = code.IsStatic ? "static " : string.Empty;
diff --git a/src/Kiota.Builder/Writers/Dart/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Dart/CodeMethodWriter.cs
index 2a9ddca836..4e12af9ee0 100644
--- a/src/Kiota.Builder/Writers/Dart/CodeMethodWriter.cs
+++ b/src/Kiota.Builder/Writers/Dart/CodeMethodWriter.cs
@@ -125,6 +125,9 @@ protected virtual void HandleMethodKind(CodeMethod codeElement, LanguageWriter w
case CodeMethodKind.Factory:
WriteFactoryMethodBody(codeElement, parentClass, writer);
break;
+ case CodeMethodKind.FactoryWithErrorMessage:
+ WriteFactoryMethodBodyForErrorClassWithMessage(codeElement, parentClass, writer);
+ break;
case CodeMethodKind.Custom:
WriteCustomMethodBody(codeElement, parentClass, writer);
break;
@@ -230,6 +233,26 @@ private void WriteFactoryMethodBodyForIntersectionModel(CodeMethod codeElement,
}
private const string DiscriminatorMappingVarName = "mappingValue";
+ private void WriteFactoryMethodBodyForErrorClassWithMessage(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer)
+ {
+ var messageParam = codeElement.Parameters.OfKind(CodeParameterKind.ErrorMessage);
+ if (messageParam != null)
+ {
+ if (parentClass.Properties.Where(static x => x.IsOfKind(CodePropertyKind.AdditionalData)).Any() && !parentClass.Properties.Where(static x => x.IsOfKind(CodePropertyKind.BackingStore)).Any())
+ {
+ writer.WriteLine($"return {parentClass.Name}(message: {messageParam.Name}, additionalData: {{}});");
+ }
+ else
+ {
+ writer.WriteLine($"return {parentClass.Name}(message: {messageParam.Name});");
+ }
+ }
+ else
+ {
+ writer.WriteLine($"return {parentClass.Name}();");
+ }
+ }
+
private void WriteFactoryMethodBody(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer)
{
var parseNodeParameter = codeElement.Parameters.OfKind(CodeParameterKind.ParseNode) ?? throw new InvalidOperationException("Factory method should have a ParseNode parameter");
@@ -291,7 +314,7 @@ private void WriteConstructorBody(CodeClass parentClass, CodeMethod currentMetho
{
if (parentClass.IsErrorDefinition)
{
- WriteErrorClassConstructor(parentClass, writer);
+ WriteErrorClassConstructor(parentClass, currentMethod, writer);
}
else
{
@@ -344,12 +367,26 @@ private void WriteConstructorBody(CodeClass parentClass, CodeMethod currentMetho
}
}
- private void WriteErrorClassConstructor(CodeClass parentClass, LanguageWriter writer)
+ private void WriteErrorClassConstructor(CodeClass parentClass, CodeMethod currentMethod, LanguageWriter writer)
{
- foreach (string prop in DartConventionService.ErrorClassProperties)
+ // Check if this constructor has a message parameter
+ var hasMessageParameter = currentMethod.Parameters.Any(p => p.Name.Equals("message", StringComparison.OrdinalIgnoreCase) && p.Type.Name.Equals("String", StringComparison.OrdinalIgnoreCase));
+
+ // For error class inheritance, pass message to super if present
+ if (hasMessageParameter)
+ {
+ writer.WriteLine("super(message: message),");
+ }
+ else
+ {
+ writer.WriteLine("super(),");
+ }
+
+ foreach (string prop in DartConventionService.ErrorClassProperties.Where(p => !p.Equals("message", StringComparison.OrdinalIgnoreCase)))
{
writer.WriteLine($"super.{prop},");
}
+
if (!parentClass.Properties.Where(static x => x.IsOfKind(CodePropertyKind.BackingStore)).Any())
{
foreach (CodeProperty prop in parentClass.GetPropertiesOfKind(CodePropertyKind.Custom, CodePropertyKind.AdditionalData))
@@ -489,7 +526,20 @@ protected void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams re
writer.StartBlock($"final {errorMappingVarName} = >{{");
foreach (var errorMapping in codeElement.ErrorMappings.Where(errorMapping => errorMapping.Value.AllTypes.FirstOrDefault()?.TypeDefinition is CodeClass))
{
- writer.WriteLine($"'{errorMapping.Key.ToUpperInvariant()}' : {conventions.GetTypeString(errorMapping.Value, codeElement, false)}.createFromDiscriminatorValue,");
+ var typeName = conventions.GetTypeString(errorMapping.Value, codeElement, false);
+
+ if (errorMapping.Value.AllTypes.FirstOrDefault()?.TypeDefinition is CodeClass { IsErrorDefinition: true })
+ {
+ var errorDescription = codeElement.GetErrorDescription(errorMapping.Key);
+ var statusCodeAndDescription = !string.IsNullOrEmpty(errorDescription)
+ ? $"{errorMapping.Key} {errorDescription}".SanitizeSingleQuote()
+ : errorMapping.Key;
+ writer.WriteLine($"'{errorMapping.Key.ToUpperInvariant()}' : (parseNode) => {typeName}.createFromDiscriminatorValueWithMessage(parseNode, '{statusCodeAndDescription}'),");
+ }
+ else
+ {
+ writer.WriteLine($"'{errorMapping.Key.ToUpperInvariant()}' : {typeName}.createFromDiscriminatorValue,");
+ }
}
writer.CloseBlock("};");
}
@@ -721,7 +771,7 @@ private void WriteMethodPrototype(CodeMethod code, CodeClass parentClass, Langua
var methodName = GetMethodName(code, parentClass, isConstructor);
var includeNullableReferenceType = code.IsOfKind(CodeMethodKind.RequestExecutor, CodeMethodKind.RequestGenerator);
var openingBracket = baseSuffix.Equals(" : ", StringComparison.Ordinal) ? "" : "{";
- var closingparenthesis = (isConstructor && parentClass.IsErrorDefinition) ? string.Empty : ")";
+ var closingparenthesis = ")";
// Constuctors (except for ClientConstructor) don't need a body but a closing statement
if (HasEmptyConstructorBody(code, parentClass, isConstructor))
{
diff --git a/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs
index 479f7e01a3..20b3cfbed3 100644
--- a/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs
+++ b/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs
@@ -82,6 +82,9 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri
case CodeMethodKind.Factory:
WriteFactoryMethodBody(codeElement, parentClass, writer);
break;
+ case CodeMethodKind.FactoryWithErrorMessage:
+ WriteFactoryMethodBodyForErrorClassWithMessage(codeElement, parentClass, writer);
+ break;
case CodeMethodKind.ComposedTypeMarker:
WriteComposedTypeMarkerBody(writer);
break;
@@ -115,9 +118,16 @@ private void WriteComposedTypeMarkerBody(LanguageWriter writer)
{
writer.WriteLine("return true");
}
+ private void WriteFactoryMethodBodyForErrorClassWithMessage(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer)
+ {
+ var messageParam = codeElement.Parameters.FirstOrDefault(static p => p.IsOfKind(CodeParameterKind.ErrorMessage)) ?? throw new InvalidOperationException($"FactoryWithErrorMessage should have a message parameter");
+ writer.WriteLine($"return New{parentClass.Name}WithMessage({messageParam.Name}), nil");
+ }
+
private void WriteFactoryMethodBody(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer)
{
var parseNodeParameter = codeElement.Parameters.OfKind(CodeParameterKind.ParseNode) ?? throw new InvalidOperationException("Factory method should have a ParseNode parameter");
+
if (parentClass.DiscriminatorInformation.ShouldWriteDiscriminatorForUnionType || parentClass.DiscriminatorInformation.ShouldWriteDiscriminatorForIntersectionType)
writer.WriteLine($"{ResultVarName} := New{parentClass.Name.ToFirstCharacterUpperCase()}()");
if (parentClass.DiscriminatorInformation.ShouldWriteParseNodeCheck)
@@ -444,7 +454,14 @@ private void WriteMethodPrototype(CodeMethod code, CodeElement parentBlock, Lang
var isConstructor = code.IsOfKind(CodeMethodKind.Constructor, CodeMethodKind.ClientConstructor, CodeMethodKind.RawUrlConstructor);
var methodName = code.Kind switch
{
- CodeMethodKind.Constructor when parentBlock is CodeClass parentClass && parentClass.IsOfKind(CodeClassKind.RequestBuilder) => $"New{parentClass.Name.ToFirstCharacterUpperCase()}Internal", // internal instantiation with url template parameters
+ // Error class constructors: generate names based on parameters
+ CodeMethodKind.Constructor when parentBlock is CodeClass { IsErrorDefinition: true } errorClass && code.Parameters.Any(p => p.IsOfKind(CodeParameterKind.ErrorMessage))
+ => $"New{errorClass.Name.ToFirstCharacterUpperCase()}WithMessage",
+ CodeMethodKind.Constructor when parentBlock is CodeClass { IsErrorDefinition: true }
+ => $"New{parentBlock.Name.ToFirstCharacterUpperCase()}",
+ // RequestBuilder constructors with parameters
+ CodeMethodKind.Constructor when parentBlock is CodeClass parentClass && parentClass.IsOfKind(CodeClassKind.RequestBuilder)
+ => $"New{parentClass.Name.ToFirstCharacterUpperCase()}Internal",
CodeMethodKind.Factory => $"Create{parentBlock.Name.ToFirstCharacterUpperCase()}FromDiscriminatorValue",
_ when isConstructor => $"New{parentBlock.Name.ToFirstCharacterUpperCase()}",
_ when code.Access == AccessModifier.Public => code.Name.ToFirstCharacterUpperCase(),
@@ -486,27 +503,27 @@ private void WriteGetterBody(CodeMethod codeElement, LanguageWriter writer, Code
!(codeElement.AccessedProperty.Type?.IsNullable ?? true) &&
!codeElement.AccessedProperty.ReadOnly &&
!string.IsNullOrEmpty(codeElement.AccessedProperty.DefaultValue))
- {
- writer.WriteLines(
- $"val , err := m.{backingStore.NamePrefix}{backingStore.Name.ToFirstCharacterLowerCase()}.Get(\"{codeElement.AccessedProperty.Name.ToFirstCharacterLowerCase()}\")");
- writer.WriteBlock("if err != nil {", "}", "panic(err)");
- writer.WriteBlock("if val == nil {", "}",
- $"var value = {codeElement.AccessedProperty.DefaultValue};",
- $"m.Set{codeElement.AccessedProperty.Name?.ToFirstCharacterUpperCase()}(value);");
+ {
+ writer.WriteLines(
+ $"val , err := m.{backingStore.NamePrefix}{backingStore.Name.ToFirstCharacterLowerCase()}.Get(\"{codeElement.AccessedProperty.Name.ToFirstCharacterLowerCase()}\")");
+ writer.WriteBlock("if err != nil {", "}", "panic(err)");
+ writer.WriteBlock("if val == nil {", "}",
+ $"var value = {codeElement.AccessedProperty.DefaultValue};",
+ $"m.Set{codeElement.AccessedProperty.Name?.ToFirstCharacterUpperCase()}(value);");
- writer.WriteLine($"return val.({conventions.GetTypeString(codeElement.AccessedProperty.Type, parentClass)})");
- }
- else
- {
- var returnType = conventions.GetTypeString(codeElement.ReturnType, parentClass);
+ writer.WriteLine($"return val.({conventions.GetTypeString(codeElement.AccessedProperty.Type, parentClass)})");
+ }
+ else
+ {
+ var returnType = conventions.GetTypeString(codeElement.ReturnType, parentClass);
- writer.WriteLine($"val, err := m.Get{backingStore.Name.ToFirstCharacterUpperCase()}().Get(\"{codeElement.AccessedProperty?.Name?.ToFirstCharacterLowerCase()}\")");
+ writer.WriteLine($"val, err := m.Get{backingStore.Name.ToFirstCharacterUpperCase()}().Get(\"{codeElement.AccessedProperty?.Name?.ToFirstCharacterLowerCase()}\")");
- writer.WriteBlock("if err != nil {", "}", "panic(err)");
- writer.WriteBlock("if val != nil {", "}", $"return val.({returnType})");
+ writer.WriteBlock("if err != nil {", "}", "panic(err)");
+ writer.WriteBlock("if val != nil {", "}", $"return val.({returnType})");
- writer.WriteLine("return nil");
- }
+ writer.WriteLine("return nil");
+ }
}
private void WriteApiConstructorBody(CodeClass parentClass, CodeMethod method, LanguageWriter writer)
{
@@ -560,6 +577,17 @@ private void WriteConstructorBody(CodeClass parentClass, CodeMethod currentMetho
writer.DecreaseIndent();
}
writer.CloseBlock(decreaseIndent: false);
+
+ // Handle error message parameter for error classes - set the Message field on the parent ApiError
+ if (parentClass.IsErrorDefinition && currentMethod.Parameters.FirstOrDefault(static p => p.IsOfKind(CodeParameterKind.ErrorMessage)) is CodeParameter messageParam)
+ {
+ var parentClassName = parentClass.StartBlock.Inherits?.Name.ToFirstCharacterUpperCase();
+ if (!string.IsNullOrEmpty(parentClassName))
+ {
+ writer.WriteLine($"m.{parentClassName}.Message = *{messageParam.Name.ToFirstCharacterLowerCase()}");
+ }
+ }
+
foreach (var propWithDefault in parentClass.GetPropertiesOfKind(CodePropertyKind.BackingStore,
CodePropertyKind.RequestBuilder)
.Where(static x => !string.IsNullOrEmpty(x.DefaultValue))
@@ -800,7 +828,24 @@ private void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams requ
writer.IncreaseIndent();
foreach (var errorMapping in codeElement.ErrorMappings)
{
- writer.WriteLine($"\"{errorMapping.Key.ToUpperInvariant()}\": {conventions.GetImportedStaticMethodName(errorMapping.Value, parentClass, "Create", "FromDiscriminatorValue", "able")},");
+ var errorKey = errorMapping.Key.ToUpperInvariant();
+
+ if (errorMapping.Value.AllTypes.FirstOrDefault()?.TypeDefinition is CodeClass { IsErrorDefinition: true })
+ {
+ var errorDescription = codeElement.GetErrorDescription(errorMapping.Key);
+ if (!string.IsNullOrEmpty(errorDescription))
+ {
+ writer.WriteLine($"\"{errorKey}\": func(parseNode {conventions.SerializationHash}.ParseNode) error {{ return {conventions.GetTypeString(errorMapping.Value, parentClass, false, false, false)}.CreateFromDiscriminatorValueWithMessage(parseNode, \"{errorDescription.SanitizeDoubleQuote()}\") }},");
+ }
+ else
+ {
+ writer.WriteLine($"\"{errorKey}\": {conventions.GetImportedStaticMethodName(errorMapping.Value, parentClass, "Create", "FromDiscriminatorValue", "able")},");
+ }
+ }
+ else if (errorMapping.Value.AllTypes.FirstOrDefault()?.TypeDefinition is CodeClass)
+ {
+ writer.WriteLine($"\"{errorKey}\": {conventions.GetImportedStaticMethodName(errorMapping.Value, parentClass, "Create", "FromDiscriminatorValue", "able")},");
+ }
}
writer.CloseBlock();
}
diff --git a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs
index ca9385f3db..6e53ba7c28 100644
--- a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs
+++ b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs
@@ -84,6 +84,9 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri
case CodeMethodKind.Factory when codeElement.IsOverload:
WriteFactoryOverloadMethod(codeElement, parentClass, writer);
break;
+ case CodeMethodKind.FactoryWithErrorMessage:
+ WriteFactoryMethodBodyForErrorClassWithMessage(codeElement, parentClass, writer);
+ break;
case CodeMethodKind.ErrorMessageOverride:
WriteErrorMethodOverride(parentClass, writer);
break;
@@ -116,9 +119,16 @@ private void WriteRawUrlBuilderBody(CodeClass parentClass, CodeMethod codeElemen
writer.WriteLine($"return new {parentClass.Name}({rawUrlParameter.Name}, {requestAdapterProperty.Name});");
}
private const string ResultVarName = "result";
+ private void WriteFactoryMethodBodyForErrorClassWithMessage(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer)
+ {
+ var messageParam = codeElement.Parameters.FirstOrDefault(static p => p.IsOfKind(CodeParameterKind.ErrorMessage)) ?? throw new InvalidOperationException($"FactoryWithErrorMessage should have a message parameter");
+ writer.WriteLine($"return new {parentClass.Name}({messageParam.Name});");
+ }
+
private void WriteFactoryMethodBody(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer)
{
var parseNodeParameter = codeElement.Parameters.OfKind(CodeParameterKind.ParseNode) ?? throw new InvalidOperationException("Factory method should have a ParseNode parameter");
+
if (parentClass.DiscriminatorInformation.ShouldWriteDiscriminatorForUnionType || parentClass.DiscriminatorInformation.ShouldWriteDiscriminatorForIntersectionType)
writer.WriteLine($"final {parentClass.Name} {ResultVarName} = new {parentClass.Name}();");
var writeDiscriminatorValueRead = parentClass.DiscriminatorInformation.ShouldWriteParseNodeCheck && !parentClass.DiscriminatorInformation.ShouldWriteDiscriminatorForIntersectionType;
@@ -325,6 +335,7 @@ private static void WriteSerializationRegistration(HashSet serialization
private void WriteConstructorBody(CodeClass parentClass, CodeMethod currentMethod, LanguageWriter writer, bool inherits)
{
if (inherits)
+ {
if (parentClass.IsOfKind(CodeClassKind.RequestBuilder) &&
currentMethod.Parameters.OfKind(CodeParameterKind.RequestAdapter) is CodeParameter requestAdapterParameter &&
parentClass.Properties.FirstOrDefaultOfKind(CodePropertyKind.UrlTemplate) is CodeProperty urlTemplateProperty &&
@@ -339,6 +350,13 @@ private void WriteConstructorBody(CodeClass parentClass, CodeMethod currentMetho
}
else
writer.WriteLine("super();");
+ }
+ // For error classes with message constructor, pass the message to base constructor
+ else if (parentClass.IsErrorDefinition &&
+ currentMethod.Parameters.Any(static p => p.IsOfKind(CodeParameterKind.ErrorMessage)))
+ {
+ writer.WriteLine("super(message);");
+ }
foreach (var propWithDefault in parentClass.GetPropertiesOfKind(CodePropertyKind.BackingStore,
CodePropertyKind.RequestBuilder,
CodePropertyKind.PathParameters)
@@ -527,7 +545,25 @@ private void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams requ
writer.WriteLine($"final HashMap> {errorMappingVarName} = new HashMap>();");
foreach (var errorMapping in codeElement.ErrorMappings)
{
- writer.WriteLine($"{errorMappingVarName}.put(\"{errorMapping.Key.ToUpperInvariant()}\", {errorMapping.Value.Name}::{FactoryMethodName});");
+ var typeName = errorMapping.Value.Name;
+ var errorKey = errorMapping.Key.ToUpperInvariant();
+
+ if (errorMapping.Value.AllTypes.FirstOrDefault()?.TypeDefinition is CodeClass { IsErrorDefinition: true })
+ {
+ var errorDescription = codeElement.GetErrorDescription(errorMapping.Key);
+ if (!string.IsNullOrEmpty(errorDescription))
+ {
+ writer.WriteLine($"{errorMappingVarName}.put(\"{errorKey}\", (parseNode) -> {typeName}.createFromDiscriminatorValueWithMessage(parseNode, \"{errorDescription.SanitizeDoubleQuote()}\"));");
+ }
+ else
+ {
+ writer.WriteLine($"{errorMappingVarName}.put(\"{errorKey}\", {typeName}::{FactoryMethodName});");
+ }
+ }
+ else if (errorMapping.Value.AllTypes.FirstOrDefault()?.TypeDefinition is CodeClass)
+ {
+ writer.WriteLine($"{errorMappingVarName}.put(\"{errorKey}\", {typeName}::{FactoryMethodName});");
+ }
}
}
var factoryParameter = GetSendRequestFactoryParam(returnType, codeElement.ReturnType.AllTypes.First().TypeDefinition is CodeEnum);
diff --git a/src/Kiota.Builder/Writers/Php/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Php/CodeMethodWriter.cs
index 1270a62541..362d15d456 100644
--- a/src/Kiota.Builder/Writers/Php/CodeMethodWriter.cs
+++ b/src/Kiota.Builder/Writers/Php/CodeMethodWriter.cs
@@ -79,6 +79,9 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri
case CodeMethodKind.Factory:
WriteFactoryMethodBody(codeElement, parentClass, writer);
break;
+ case CodeMethodKind.FactoryWithErrorMessage:
+ WriteFactoryMethodBodyForErrorClassWithMessage(codeElement, parentClass, writer);
+ break;
}
writer.CloseBlock();
writer.WriteLine();
@@ -122,6 +125,7 @@ private static void WriteConstructorParentCall(CodeClass parentClass, CodeMethod
var requestHeadersParameter = currentMethod.Parameters.OfKind(CodeParameterKind.Headers);
var pathParametersProperty = parentClass.Properties.FirstOrDefaultOfKind(CodePropertyKind.PathParameters);
var urlTemplateProperty = parentClass.Properties.FirstOrDefaultOfKind(CodePropertyKind.UrlTemplate);
+ var messageParameter = currentMethod.Parameters.FirstOrDefault(static p => p.IsOfKind(CodeParameterKind.ErrorMessage));
if (parentClass.IsOfKind(CodeClassKind.RequestBuilder))
{
@@ -129,6 +133,11 @@ private static void WriteConstructorParentCall(CodeClass parentClass, CodeMethod
}
else if (parentClass.IsOfKind(CodeClassKind.RequestConfiguration))
writer.WriteLine($"parent::__construct(${(requestHeadersParameter?.Name ?? "headers")} ?? [], ${(requestOptionParameter?.Name ?? "options")} ?? []);");
+ else if (parentClass.IsErrorDefinition && messageParameter != null)
+ {
+ // For error classes with optional message parameter, pass it to parent only if provided
+ writer.WriteLine($"parent::__construct(${messageParameter.Name} ?? '');");
+ }
else
writer.WriteLine("parent::__construct();");
@@ -771,7 +780,24 @@ private void WriteRequestExecutorBody(CodeMethod codeElement, CodeClass parentCl
writer.IncreaseIndent(2);
errorMappings.ToList().ForEach(errorMapping =>
{
- writer.WriteLine($"'{errorMapping.Key}' => [{errorMapping.Value.Name.ToFirstCharacterUpperCase()}::class, '{CreateDiscriminatorMethodName}'],");
+ var typeName = errorMapping.Value.Name.ToFirstCharacterUpperCase();
+
+ if (errorMapping.Value.AllTypes.FirstOrDefault()?.TypeDefinition is CodeClass { IsErrorDefinition: true })
+ {
+ var errorDescription = codeElement.GetErrorDescription(errorMapping.Key);
+ if (!string.IsNullOrEmpty(errorDescription))
+ {
+ writer.WriteLine($"'{errorMapping.Key}' => function($parseNode) {{ return {typeName}::createFromDiscriminatorValueWithMessage($parseNode, '{errorDescription.SanitizeSingleQuote()}'); }},");
+ }
+ else
+ {
+ writer.WriteLine($"'{errorMapping.Key}' => [{typeName}::class, '{CreateDiscriminatorMethodName}'],");
+ }
+ }
+ else if (errorMapping.Value.AllTypes.FirstOrDefault()?.TypeDefinition is CodeClass)
+ {
+ writer.WriteLine($"'{errorMapping.Key}' => [{typeName}::class, '{CreateDiscriminatorMethodName}'],");
+ }
});
writer.DecreaseIndent();
writer.WriteLine("];");
@@ -848,6 +874,12 @@ protected string GetSendRequestMethodName(bool isVoid, bool isStream, bool isCol
private const string DiscriminatorMappingVarName = "$mappingValue";
private const string ResultVarName = "$result";
+ private void WriteFactoryMethodBodyForErrorClassWithMessage(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer)
+ {
+ var messageParam = codeElement.Parameters.FirstOrDefault(static p => p.IsOfKind(CodeParameterKind.ErrorMessage)) ?? throw new InvalidOperationException($"FactoryWithErrorMessage should have a message parameter");
+ writer.WriteLine($"return new {parentClass.Name.ToFirstCharacterUpperCase()}(${messageParam.Name});");
+ }
+
private void WriteFactoryMethodBody(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer)
{
switch (parentClass.Kind)
diff --git a/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs
index 01b1fbcbe4..5f6846f63e 100644
--- a/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs
+++ b/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs
@@ -40,7 +40,7 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri
var requestConfigParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestConfiguration);
var requestContentType = codeElement.Parameters.OfKind(CodeParameterKind.RequestBodyContentType);
var requestParams = new RequestParams(requestBodyParam, requestConfigParam, requestContentType);
- if (!codeElement.IsOfKind(CodeMethodKind.Setter) &&
+ if (!codeElement.IsOfKind(CodeMethodKind.Setter, CodeMethodKind.Factory, CodeMethodKind.FactoryWithErrorMessage) &&
!(codeElement.IsOfKind(CodeMethodKind.Constructor) && parentClass.IsOfKind(CodeClassKind.RequestBuilder)))
foreach (var parameter in codeElement.Parameters.Where(static x => !x.Optional).OrderBy(static x => x.Name))
{
@@ -102,6 +102,10 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri
WriteFactoryMethodBody(codeElement, parentClass, writer);
writer.CloseBlock(string.Empty);
break;
+ case CodeMethodKind.FactoryWithErrorMessage:
+ WriteFactoryMethodBodyForErrorClassWithMessage(codeElement, parentClass, writer);
+ writer.CloseBlock(string.Empty);
+ break;
case CodeMethodKind.ComposedTypeMarker:
throw new InvalidOperationException("ComposedTypeMarker is not required as interface is explicitly implemented.");
case CodeMethodKind.RawUrlConstructor:
@@ -207,6 +211,24 @@ private void WriteFactoryMethodBodyForIntersectionModel(CodeMethod codeElement,
}
writer.WriteLine($"return {ResultVarName}");
}
+ private void WriteFactoryMethodBodyForErrorClassWithMessage(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer)
+ {
+ var parseNodeParameter = codeElement.Parameters.OfKind(CodeParameterKind.ParseNode) ?? throw new InvalidOperationException("Factory method with message should have a ParseNode parameter");
+ var messageParam = codeElement.Parameters.FirstOrDefault(static p => p.IsOfKind(CodeParameterKind.ErrorMessage)) ?? throw new InvalidOperationException($"FactoryWithErrorMessage should have a message parameter");
+
+ // Validation
+ writer.StartBlock($"if {parseNodeParameter.Name} is None:");
+ writer.WriteLine($"raise TypeError(\"{parseNodeParameter.Name} cannot be null.\")");
+ writer.CloseBlock(string.Empty);
+
+ // Create instance and set message
+ writer.WriteLine($"error = {parentClass.Name}()");
+ writer.StartBlock($"if {messageParam.Name} is not None:");
+ writer.WriteLine($"error.message = {messageParam.Name}");
+ writer.CloseBlock(string.Empty);
+ writer.WriteLine("return error");
+ }
+
private void WriteFactoryMethodBody(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer)
{
var parseNodeParameter = codeElement.Parameters.OfKind(CodeParameterKind.ParseNode) ?? throw new InvalidOperationException("Factory method should have a ParseNode parameter");
@@ -329,7 +351,14 @@ private CodePropertyKind[] SetterAccessProperties
}
private void WriteConstructorBody(CodeClass parentClass, CodeMethod currentMethod, LanguageWriter writer, bool inherits)
{
- if (inherits && !parentClass.IsOfKind(CodeClassKind.Model))
+ // For error classes with message constructor, pass the message to base constructor
+ if (parentClass.IsErrorDefinition &&
+ currentMethod.Parameters.Any(p => p.Name.Equals("message", StringComparison.OrdinalIgnoreCase) &&
+ p.Type.Name.Equals("str", StringComparison.OrdinalIgnoreCase)))
+ {
+ writer.WriteLine("super().__init__(message)");
+ }
+ else if (inherits && !parentClass.IsOfKind(CodeClassKind.Model))
{
if (parentClass.IsOfKind(CodeClassKind.RequestBuilder) &&
currentMethod.Parameters.OfKind(CodeParameterKind.RequestAdapter) is CodeParameter requestAdapterParameter &&
@@ -359,6 +388,11 @@ private void WriteConstructorBody(CodeClass parentClass, CodeMethod currentMetho
else
writer.WriteLine("super().__init__()");
}
+ else if (parentClass.IsErrorDefinition)
+ {
+ // For error classes without message constructor, call super without arguments
+ writer.WriteLine("super().__init__()");
+ }
if (parentClass.IsOfKind(CodeClassKind.Model))
{
writer.DecreaseIndent();
@@ -553,7 +587,6 @@ private void WriteDeserializerBodyForIntersectionModel(CodeClass parentClass, La
}
private void WriteDeserializerBodyForInheritedModel(bool inherits, CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer)
{
- _codeUsingWriter.WriteInternalImports(parentClass, writer);
writer.StartBlock($"fields: dict[str, Callable[[Any], {NoneKeyword}]] = {{");
foreach (var otherProp in parentClass
.GetPropertiesOfKind(CodePropertyKind.Custom)
@@ -603,7 +636,25 @@ private void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams requ
writer.StartBlock($"{errorMappingVarName}: dict[str, type[ParsableFactory]] = {{");
foreach (var errorMapping in codeElement.ErrorMappings)
{
- writer.WriteLine($"\"{errorMapping.Key.ToUpperInvariant()}\": {errorMapping.Value.Name},");
+ var typeName = errorMapping.Value.Name;
+ var errorKey = errorMapping.Key.ToUpperInvariant();
+
+ if (errorMapping.Value.AllTypes.FirstOrDefault()?.TypeDefinition is CodeClass { IsErrorDefinition: true })
+ {
+ var errorDescription = codeElement.GetErrorDescription(errorMapping.Key);
+ if (!string.IsNullOrEmpty(errorDescription))
+ {
+ writer.WriteLine($"\"{errorKey}\": lambda parse_node: {typeName}.create_from_discriminator_value_with_message(parse_node, \"{errorDescription.SanitizeDoubleQuote()}\"),");
+ }
+ else
+ {
+ writer.WriteLine($"\"{errorKey}\": {typeName},");
+ }
+ }
+ else if (errorMapping.Value.AllTypes.FirstOrDefault()?.TypeDefinition is CodeClass)
+ {
+ writer.WriteLine($"\"{errorKey}\": {typeName},");
+ }
}
writer.CloseBlock();
}
@@ -736,7 +787,7 @@ private void WriteMethodDocumentation(CodeMethod code, LanguageWriter writer, st
private static readonly PythonCodeParameterOrderComparer parameterOrderComparer = new();
private void WriteMethodPrototype(CodeMethod code, LanguageWriter writer, string returnType, bool isVoid)
{
- if (code.IsOfKind(CodeMethodKind.Factory))
+ if (code.IsOfKind(CodeMethodKind.Factory, CodeMethodKind.FactoryWithErrorMessage))
writer.WriteLine("@staticmethod");
var accessModifier = conventions.GetAccessModifier(code.Access);
var isConstructor = code.IsOfKind(CodeMethodKind.Constructor, CodeMethodKind.ClientConstructor);
@@ -747,7 +798,7 @@ private void WriteMethodPrototype(CodeMethod code, LanguageWriter writer, string
_ => code.Name,
};
var asyncPrefix = code.IsAsync && code.Kind is CodeMethodKind.RequestExecutor ? "async " : string.Empty;
- var instanceReference = code.IsOfKind(CodeMethodKind.Factory) ? string.Empty : "self,";
+ var instanceReference = code.IsOfKind(CodeMethodKind.Factory, CodeMethodKind.FactoryWithErrorMessage) ? string.Empty : "self,";
var parameters = string.Join(", ", code.Parameters.OrderBy(x => x, parameterOrderComparer)
.Select(p => new PythonConventionService() // requires a writer instance because method parameters use inline type definitions
.GetParameterSignature(p, code, writer))
diff --git a/src/Kiota.Builder/Writers/Ruby/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Ruby/CodeMethodWriter.cs
index d74c6c71a2..a335aceed7 100644
--- a/src/Kiota.Builder/Writers/Ruby/CodeMethodWriter.cs
+++ b/src/Kiota.Builder/Writers/Ruby/CodeMethodWriter.cs
@@ -68,6 +68,9 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri
case CodeMethodKind.Factory:
WriteFactoryMethodBody(codeElement, parentClass, writer);
break;
+ case CodeMethodKind.FactoryWithErrorMessage:
+ WriteFactoryMethodBodyForErrorClassWithMessage(codeElement, parentClass, writer);
+ break;
case CodeMethodKind.RequestBuilderBackwardCompatibility:
throw new InvalidOperationException("RequestBuilderBackwardCompatibility is not supported as the request builders are implemented by properties.");
default:
@@ -84,6 +87,13 @@ private void WriteRawUrlBuilderBody(CodeClass parentClass, CodeMethod codeElemen
}
private const string DiscriminatorMappingVarName = "mapping_value";
private const string NodeVarName = "mapping_value_node";
+
+ private static void WriteFactoryMethodBodyForErrorClassWithMessage(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer)
+ {
+ var messageParam = codeElement.Parameters.FirstOrDefault(static p => p.IsOfKind(CodeParameterKind.ErrorMessage)) ?? throw new InvalidOperationException($"FactoryWithErrorMessage should have a message parameter");
+ writer.WriteLine($"return {parentClass.Name.ToFirstCharacterUpperCase()}.new({messageParam.Name.ToSnakeCase()})");
+ }
+
private static void WriteFactoryMethodBody(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer)
{
var parseNodeParameter = codeElement.Parameters.OfKind(CodeParameterKind.ParseNode) ?? throw new InvalidOperationException("Factory method should have a ParseNode parameter");
@@ -272,7 +282,25 @@ private void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams requ
writer.WriteLine($"{errorMappingVarName} = Hash.new");
foreach (var errorMapping in codeElement.ErrorMappings)
{
- writer.WriteLine($"{errorMappingVarName}[\"{errorMapping.Key.ToUpperInvariant()}\"] = {getDeserializationLambda(errorMapping.Value)}");
+ var typeName = errorMapping.Value.Name;
+ var errorKey = errorMapping.Key.ToUpperInvariant();
+
+ if (errorMapping.Value.AllTypes.FirstOrDefault()?.TypeDefinition is CodeClass { IsErrorDefinition: true })
+ {
+ var errorDescription = codeElement.GetErrorDescription(errorMapping.Key);
+ if (!string.IsNullOrEmpty(errorDescription))
+ {
+ writer.WriteLine($"{errorMappingVarName}[\"{errorKey}\"] = lambda {{|parse_node| {typeName}.create_from_discriminator_value_with_message(parse_node, \"{errorDescription.SanitizeDoubleQuote()}\")}}");
+ }
+ else
+ {
+ writer.WriteLine($"{errorMappingVarName}[\"{errorKey}\"] = {getDeserializationLambda(errorMapping.Value)}");
+ }
+ }
+ else if (errorMapping.Value.AllTypes.FirstOrDefault()?.TypeDefinition is CodeClass)
+ {
+ writer.WriteLine($"{errorMappingVarName}[\"{errorKey}\"] = {getDeserializationLambda(errorMapping.Value)}");
+ }
}
}
writer.WriteLine($"return @request_adapter.{genericTypeForSendMethod}(request_info, {returnType}, {errorMappingVarName})");
diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs
index c9e7b90453..b0570e8426 100644
--- a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs
+++ b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs
@@ -116,7 +116,19 @@ private void WriteRequestsMetadataConstant(CodeConstant codeElement, LanguageWri
writer.StartBlock("errorMappings: {");
foreach (var errorMapping in executorMethod.ErrorMappings)
{
- writer.WriteLine($"{GetErrorMappingKey(errorMapping.Key)}: {GetFactoryMethodName(errorMapping.Value, codeElement, writer)} as ParsableFactory,");
+ if (!(errorMapping.Value.AllTypes.FirstOrDefault()?.TypeDefinition is CodeClass errorClass)) continue;
+ var errorKey = GetErrorMappingKey(errorMapping.Key);
+ var errorDescription = executorMethod.GetErrorDescription(errorMapping.Key);
+
+ if (!string.IsNullOrEmpty(errorDescription) && errorClass.IsErrorDefinition)
+ {
+ var enhancedFactoryMethodName = GetFactoryMethodName(errorMapping.Value, codeElement, writer).Replace("FromDiscriminatorValue", "FromDiscriminatorValueWithMessage", StringComparison.Ordinal);
+ writer.WriteLine($"{errorKey}: (parseNode: ParseNode) => {enhancedFactoryMethodName}(parseNode, \"{errorDescription}\") as ParsableFactory,");
+ }
+ else
+ {
+ writer.WriteLine($"{errorKey}: {GetFactoryMethodName(errorMapping.Value, codeElement, writer)} as ParsableFactory,");
+ }
}
writer.CloseBlock("},");
}
diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs
index 72aa88dfcf..df30ae0370 100644
--- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs
+++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs
@@ -120,6 +120,9 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w
case CodeMethodKind.Factory:
WriteFactoryMethod(codeElement, codeFile, writer);
break;
+ case CodeMethodKind.FactoryWithErrorMessage:
+ WriteFactoryMethodForErrorClassWithMessage(codeElement, writer);
+ break;
case CodeMethodKind.ClientConstructor:
WriteApiConstructorBody(parentFile, codeMethod, writer);
break;
@@ -332,6 +335,19 @@ private static void WriteSerializationRegistration(HashSet serialization
writer.WriteLine($"{objectName}.{methodName}({module}{(additionalParam != null && additionalParam.Length > 0 ? ", " + string.Join(", ", additionalParam) : string.Empty)});");
}
+ private void WriteFactoryMethodForErrorClassWithMessage(CodeFunction codeElement, LanguageWriter writer)
+ {
+ var messageParam = codeElement.OriginalLocalMethod.Parameters.FirstOrDefault(static p => p.IsOfKind(CodeParameterKind.ErrorMessage));
+ if (messageParam != null)
+ {
+ writer.WriteLine($"return new {codeElement.OriginalMethodParentClass.Name.ToFirstCharacterUpperCase()}({messageParam.Name});");
+ }
+ else
+ {
+ writer.WriteLine($"return new {codeElement.OriginalMethodParentClass.Name.ToFirstCharacterUpperCase()}();");
+ }
+ }
+
private void WriteFactoryMethod(CodeFunction codeElement, CodeFile codeFile, LanguageWriter writer)
{
var returnType = conventions.GetTypeString(codeElement.OriginalLocalMethod.ReturnType, codeElement);
diff --git a/tests/Kiota.Builder.IntegrationTests/GenerateSample.cs b/tests/Kiota.Builder.IntegrationTests/GenerateSample.cs
index ddfe68903e..f2daab2dd8 100644
--- a/tests/Kiota.Builder.IntegrationTests/GenerateSample.cs
+++ b/tests/Kiota.Builder.IntegrationTests/GenerateSample.cs
@@ -121,6 +121,7 @@ public async Task GeneratesErrorsInliningParentsAsync(GenerationLanguage languag
Language = language,
OpenAPIFilePath = GetAbsolutePath("InheritingErrors.yaml"),
OutputPath = $".\\Generated\\ErrorInlineParents\\{language}",
+ CleanOutput = true
};
await new KiotaBuilder(logger, configuration, _httpClient).GenerateClientAsync(new());
}
diff --git a/tests/Kiota.Builder.Tests/Refiners/CSharpLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/CSharpLanguageRefinerTests.cs
index 6919ef14bc..8015bf4c95 100644
--- a/tests/Kiota.Builder.Tests/Refiners/CSharpLanguageRefinerTests.cs
+++ b/tests/Kiota.Builder.Tests/Refiners/CSharpLanguageRefinerTests.cs
@@ -962,5 +962,87 @@ public async Task SetTypeAccessModifierAsync(AccessModifier accessModifier)
Assert.Equal(codeClass.Access, accessModifier);
Assert.Equal(codeEnum.Access, accessModifier);
}
+
+ [Fact]
+ public async Task AddsMessageConstructorToErrorClasses()
+ {
+ // Given
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "Error401",
+ IsErrorDefinition = true
+ }).First();
+
+ // When
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.CSharp }, root);
+
+ // Then
+ var messageConstructor = errorClass.Methods
+ .FirstOrDefault(m => m.IsOfKind(CodeMethodKind.Constructor) &&
+ m.Parameters.Any(p => p.IsOfKind(CodeParameterKind.ErrorMessage)));
+
+ Assert.NotNull(messageConstructor);
+ Assert.Single(messageConstructor.Parameters);
+ var parameter = messageConstructor.Parameters.First();
+ Assert.Equal("message", parameter.Name);
+ Assert.Equal("string", parameter.Type.Name);
+ Assert.False(parameter.Optional);
+ Assert.Equal(CodeParameterKind.ErrorMessage, parameter.Kind);
+ }
+
+ [Fact]
+ public async Task DoesNotAddMessageConstructorToNonErrorClasses()
+ {
+ // Given
+ var regularClass = root.AddClass(new CodeClass
+ {
+ Name = "RegularModel",
+ IsErrorDefinition = false
+ }).First();
+
+ // When
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.CSharp }, root);
+
+ // Then
+ var messageConstructor = regularClass.Methods
+ .FirstOrDefault(m => m.IsOfKind(CodeMethodKind.Constructor) &&
+ m.Parameters.Any(p => p.IsOfKind(CodeParameterKind.ErrorMessage)));
+
+ Assert.Null(messageConstructor);
+ }
+
+ [Fact]
+ public async Task AddsMessageFactoryMethodToErrorClasses()
+ {
+ // Given
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "Error401",
+ IsErrorDefinition = true
+ }).First();
+
+ // When
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.CSharp }, root);
+
+ // Then
+ var messageFactoryMethod = errorClass.Methods
+ .FirstOrDefault(m => m.IsOfKind(CodeMethodKind.FactoryWithErrorMessage));
+
+ Assert.NotNull(messageFactoryMethod);
+ Assert.Equal(2, messageFactoryMethod.Parameters.Count());
+
+ var parseNodeParam = messageFactoryMethod.Parameters.FirstOrDefault(p => p.IsOfKind(CodeParameterKind.ParseNode));
+ Assert.NotNull(parseNodeParam);
+ Assert.Equal("parseNode", parseNodeParam.Name);
+ Assert.Equal("IParseNode", parseNodeParam.Type.Name);
+
+ var messageParam = messageFactoryMethod.Parameters.FirstOrDefault(p => p.IsOfKind(CodeParameterKind.ErrorMessage));
+ Assert.NotNull(messageParam);
+ Assert.Equal("message", messageParam.Name);
+ Assert.Equal("string", messageParam.Type.Name);
+
+ Assert.True(messageFactoryMethod.IsStatic);
+ Assert.Equal(AccessModifier.Public, messageFactoryMethod.Access);
+ }
#endregion
}
diff --git a/tests/Kiota.Builder.Tests/Refiners/DartLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/DartLanguageRefinerTests.cs
index fd9dcc6be2..c27f2481ec 100644
--- a/tests/Kiota.Builder.Tests/Refiners/DartLanguageRefinerTests.cs
+++ b/tests/Kiota.Builder.Tests/Refiners/DartLanguageRefinerTests.cs
@@ -474,5 +474,126 @@ public async Task DoesntOverwriteSerializationNameIfAlreadySet()
Assert.Equal("customType", model.Properties.First().Name);
Assert.Equal("\\$type", model.Properties.First().WireName);
}
+
+ [Fact]
+ public async Task AddsConstructorsForErrorClasses()
+ {
+ // Given
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "Error401",
+ IsErrorDefinition = true
+ }).First();
+
+ // When
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Dart }, root);
+
+ // Then
+ var parameterlessConstructor = errorClass.Methods
+ .FirstOrDefault(m => m.IsOfKind(CodeMethodKind.Constructor) && !m.Parameters.Any());
+
+ Assert.NotNull(parameterlessConstructor);
+ Assert.Equal("constructor", parameterlessConstructor.Name);
+ Assert.Equal(AccessModifier.Public, parameterlessConstructor.Access);
+
+ var messageConstructor = errorClass.Methods
+ .FirstOrDefault(m => m.IsOfKind(CodeMethodKind.Constructor) &&
+ m.Parameters.Any(p => p.Type.Name.Equals("String", StringComparison.OrdinalIgnoreCase) && p.Name.Equals("message", StringComparison.OrdinalIgnoreCase)));
+
+ Assert.NotNull(messageConstructor);
+ Assert.Single(messageConstructor.Parameters);
+ Assert.Equal("message", messageConstructor.Parameters.First().Name);
+ Assert.Equal("String", messageConstructor.Parameters.First().Type.Name);
+ Assert.False(messageConstructor.Parameters.First().Optional);
+ }
+
+ [Fact]
+ public async Task DoesNotAddConstructorsToNonErrorClasses()
+ {
+ // Given
+ var regularClass = root.AddClass(new CodeClass
+ {
+ Name = "RegularModel",
+ IsErrorDefinition = false
+ }).First();
+
+ // When
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Dart }, root);
+
+ // Then
+ var messageConstructor = regularClass.Methods
+ .FirstOrDefault(m => m.IsOfKind(CodeMethodKind.Constructor) &&
+ m.Parameters.Any(p => p.Type.Name.Equals("String", StringComparison.OrdinalIgnoreCase) && p.Name.Equals("message", StringComparison.OrdinalIgnoreCase)));
+
+ Assert.Null(messageConstructor);
+ }
+
+ [Fact]
+ public async Task AddsMessageFactoryMethodToErrorClasses()
+ {
+ // Given
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "Error401",
+ IsErrorDefinition = true
+ }).First();
+
+ // When
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Dart }, root);
+
+ // Then
+ var messageFactoryMethod = errorClass.Methods
+ .FirstOrDefault(m => m.IsOfKind(CodeMethodKind.FactoryWithErrorMessage) &&
+ m.Name.Equals("createFromDiscriminatorValueWithMessage", StringComparison.OrdinalIgnoreCase));
+
+ Assert.NotNull(messageFactoryMethod);
+ Assert.Equal(2, messageFactoryMethod.Parameters.Count());
+
+ var parseNodeParam = messageFactoryMethod.Parameters.FirstOrDefault(p => p.Name.Equals("parseNode", StringComparison.OrdinalIgnoreCase));
+ Assert.NotNull(parseNodeParam);
+ Assert.Equal("ParseNode", parseNodeParam.Type.Name);
+
+ var messageParam = messageFactoryMethod.Parameters.FirstOrDefault(p => p.Name.Equals("message", StringComparison.OrdinalIgnoreCase));
+ Assert.NotNull(messageParam);
+ Assert.Equal("String", messageParam.Type.Name);
+
+ Assert.True(messageFactoryMethod.IsStatic);
+ Assert.Equal(AccessModifier.Public, messageFactoryMethod.Access);
+ }
+
+ [Fact]
+ public async Task DoesNotDuplicateExistingConstructors()
+ {
+ // Given
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "Error401",
+ IsErrorDefinition = true
+ }).First();
+
+ // Add an existing message constructor
+ var existingConstructor = new CodeMethod
+ {
+ Name = "constructor",
+ Kind = CodeMethodKind.Constructor,
+ Access = AccessModifier.Public,
+ ReturnType = new CodeType { Name = "void", IsExternal = true }
+ };
+ existingConstructor.AddParameter(new CodeParameter
+ {
+ Name = "message",
+ Type = new CodeType { Name = "String", IsExternal = true }
+ });
+ errorClass.AddMethod(existingConstructor);
+
+ var initialConstructorCount = errorClass.Methods.Count(m => m.IsOfKind(CodeMethodKind.Constructor));
+
+ // When
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Dart }, root);
+
+ // Then - should add only the parameterless constructor, not duplicate the message one
+ var finalConstructorCount = errorClass.Methods.Count(m => m.IsOfKind(CodeMethodKind.Constructor));
+ Assert.Equal(initialConstructorCount + 1, finalConstructorCount); // Only parameterless constructor added
+ }
#endregion
}
diff --git a/tests/Kiota.Builder.Tests/Refiners/GoLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/GoLanguageRefinerTests.cs
index 652e1580b8..4312e3a986 100644
--- a/tests/Kiota.Builder.Tests/Refiners/GoLanguageRefinerTests.cs
+++ b/tests/Kiota.Builder.Tests/Refiners/GoLanguageRefinerTests.cs
@@ -1344,6 +1344,106 @@ public async Task ImportsStrConvForRelevantTypesOnly(string pathParameterType, b
await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Go }, root);
Assert.Equal(isImported, model.StartBlock.Usings.Any(static x => x.Declaration.Name.Equals("strconv", StringComparison.OrdinalIgnoreCase)));
}
+ [Fact]
+ public async Task AddsConstructorsForErrorClassesAsync()
+ {
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "SomeError",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = true
+ }).First();
+
+ root.AddNamespace("ApiSdk/models"); // so the interface copy refiner goes through
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Go }, root);
+
+ // Check that the factory methods were created for error classes
+ var parameterlessConstructor = errorClass.Methods.FirstOrDefault(m => m.IsOfKind(CodeMethodKind.Constructor) && !m.Parameters.Any());
+ var messageConstructor = errorClass.Methods.FirstOrDefault(m => m.IsOfKind(CodeMethodKind.Constructor) && m.Parameters.Any(p => p.IsOfKind(CodeParameterKind.ErrorMessage)));
+ var discriminatorMessageFactory = errorClass.Methods.FirstOrDefault(m => m.IsOfKind(CodeMethodKind.FactoryWithErrorMessage));
+
+ Assert.NotNull(parameterlessConstructor);
+ Assert.NotNull(messageConstructor);
+ Assert.NotNull(discriminatorMessageFactory);
+
+ // Check parameter counts
+ Assert.Single(messageConstructor.Parameters);
+ Assert.Equal(2, discriminatorMessageFactory.Parameters.Count());
+ }
+
+ [Fact]
+ public async Task DoesNotAddConstructorsForNonErrorClassesAsync()
+ {
+ var regularClass = root.AddClass(new CodeClass
+ {
+ Name = "SomeModel",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = false
+ }).First();
+
+ root.AddNamespace("ApiSdk/models"); // so the interface copy refiner goes through
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Go }, root);
+
+ // Should not have error-specific factory methods
+ Assert.DoesNotContain(regularClass.Methods, m => m.Name == "NewSomeModel" && m.IsOfKind(CodeMethodKind.Factory));
+ Assert.DoesNotContain(regularClass.Methods, m => m.Name == "NewSomeModelWithMessage" && m.IsOfKind(CodeMethodKind.Factory));
+ Assert.DoesNotContain(regularClass.Methods, m => m.Name == "CreateFromDiscriminatorValueWithMessage" && m.IsOfKind(CodeMethodKind.FactoryWithErrorMessage));
+ }
+
+ [Fact]
+ public async Task AddsConstructorsOnlyOnceForErrorClassesAsync()
+ {
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "DuplicateError",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = true
+ }).First();
+
+ // Add existing factory method to simulate already having one
+ errorClass.AddMethod(new CodeMethod
+ {
+ Name = "NewDuplicateError",
+ Kind = CodeMethodKind.Factory,
+ ReturnType = new CodeType { Name = "*DuplicateError", IsNullable = true }
+ });
+
+ root.AddNamespace("ApiSdk/models"); // so the interface copy refiner goes through
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Go }, root);
+
+ // Should have only one of each factory method
+ Assert.Single(errorClass.Methods, m => m.IsOfKind(CodeMethodKind.Constructor) && !m.Parameters.Any());
+ Assert.Single(errorClass.Methods, m => m.IsOfKind(CodeMethodKind.Constructor) && m.Parameters.Any(p => p.IsOfKind(CodeParameterKind.ErrorMessage)));
+ Assert.Single(errorClass.Methods, m => m.IsOfKind(CodeMethodKind.FactoryWithErrorMessage));
+ }
+
+ [Fact]
+ public async Task AddsConstructorsWithCorrectDescriptionForErrorClassesAsync()
+ {
+ var errorClassWithDescription = root.AddClass(new CodeClass
+ {
+ Name = "DetailedError",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = true
+ }).First();
+
+ root.AddNamespace("ApiSdk/models"); // so the interface copy refiner goes through
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Go }, root);
+
+ // Check factory methods are created
+ var parameterlessConstructor = errorClassWithDescription.Methods.FirstOrDefault(m => m.IsOfKind(CodeMethodKind.Constructor) && !m.Parameters.Any());
+ Assert.NotNull(parameterlessConstructor);
+ Assert.NotEmpty(parameterlessConstructor.Documentation.DescriptionTemplate);
+
+ var messageConstructor = errorClassWithDescription.Methods.FirstOrDefault(m => m.IsOfKind(CodeMethodKind.Constructor) && m.Parameters.Any(p => p.IsOfKind(CodeParameterKind.ErrorMessage)));
+ Assert.NotNull(messageConstructor);
+ Assert.NotEmpty(messageConstructor.Documentation.DescriptionTemplate);
+
+ var discriminatorMessageFactory = errorClassWithDescription.Methods.FirstOrDefault(m => m.IsOfKind(CodeMethodKind.FactoryWithErrorMessage));
+ Assert.NotNull(discriminatorMessageFactory);
+ Assert.NotEmpty(discriminatorMessageFactory.Documentation.DescriptionTemplate);
+ }
+
[Fact]
public async Task EscapesReservedKeywordsInMethodParametersAsync()
{
diff --git a/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs
index ec23487015..87f91b5861 100644
--- a/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs
+++ b/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs
@@ -768,5 +768,126 @@ public async Task AddsUsingForUntypedNodeInMethodParameterAsync()
Assert.Single(nodeUsing);
Assert.Equal("com.microsoft.kiota.serialization", nodeUsing[0].Declaration.Name);
}
+
+ [Fact]
+ public async Task AddsConstructorsForErrorClasses()
+ {
+ // Given
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "Error401",
+ IsErrorDefinition = true
+ }).First();
+
+ // When
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Java }, root);
+
+ // Then
+ var parameterlessConstructor = errorClass.Methods
+ .FirstOrDefault(m => m.IsOfKind(CodeMethodKind.Constructor) && !m.Parameters.Any());
+
+ Assert.NotNull(parameterlessConstructor);
+ Assert.Equal("constructor", parameterlessConstructor.Name);
+ Assert.Equal(AccessModifier.Public, parameterlessConstructor.Access);
+
+ var messageConstructor = errorClass.Methods
+ .FirstOrDefault(m => m.IsOfKind(CodeMethodKind.Constructor) &&
+ m.Parameters.Any(p => p.Type.Name.Equals("String", StringComparison.OrdinalIgnoreCase) && p.Name.Equals("message", StringComparison.OrdinalIgnoreCase)));
+
+ Assert.NotNull(messageConstructor);
+ Assert.Single(messageConstructor.Parameters);
+ Assert.Equal("message", messageConstructor.Parameters.First().Name);
+ Assert.Equal("String", messageConstructor.Parameters.First().Type.Name);
+ Assert.False(messageConstructor.Parameters.First().Optional);
+ }
+
+ [Fact]
+ public async Task DoesNotAddConstructorsToNonErrorClasses()
+ {
+ // Given
+ var regularClass = root.AddClass(new CodeClass
+ {
+ Name = "RegularModel",
+ IsErrorDefinition = false
+ }).First();
+
+ // When
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Java }, root);
+
+ // Then
+ var messageConstructor = regularClass.Methods
+ .FirstOrDefault(m => m.IsOfKind(CodeMethodKind.Constructor) &&
+ m.Parameters.Any(p => p.Type.Name.Equals("String", StringComparison.OrdinalIgnoreCase) && p.Name.Equals("message", StringComparison.OrdinalIgnoreCase)));
+
+ Assert.Null(messageConstructor);
+ }
+
+ [Fact]
+ public async Task AddsMessageFactoryMethodToErrorClasses()
+ {
+ // Given
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "Error401",
+ IsErrorDefinition = true
+ }).First();
+
+ // When
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Java }, root);
+
+ // Then
+ var messageFactoryMethod = errorClass.Methods
+ .FirstOrDefault(m => m.IsOfKind(CodeMethodKind.FactoryWithErrorMessage) &&
+ m.Name.Equals("createFromDiscriminatorValueWithMessage", StringComparison.OrdinalIgnoreCase));
+
+ Assert.NotNull(messageFactoryMethod);
+ Assert.Equal(2, messageFactoryMethod.Parameters.Count());
+
+ var parseNodeParam = messageFactoryMethod.Parameters.FirstOrDefault(p => p.Name.Equals("parseNode", StringComparison.OrdinalIgnoreCase));
+ Assert.NotNull(parseNodeParam);
+ Assert.Equal("ParseNode", parseNodeParam.Type.Name);
+
+ var messageParam = messageFactoryMethod.Parameters.FirstOrDefault(p => p.Name.Equals("message", StringComparison.OrdinalIgnoreCase));
+ Assert.NotNull(messageParam);
+ Assert.Equal("String", messageParam.Type.Name);
+
+ Assert.True(messageFactoryMethod.IsStatic);
+ Assert.Equal(AccessModifier.Public, messageFactoryMethod.Access);
+ }
+
+ [Fact]
+ public async Task DoesNotDuplicateExistingConstructors()
+ {
+ // Given
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "Error401",
+ IsErrorDefinition = true
+ }).First();
+
+ // Add an existing message constructor
+ var existingConstructor = new CodeMethod
+ {
+ Name = "constructor",
+ Kind = CodeMethodKind.Constructor,
+ Access = AccessModifier.Public,
+ ReturnType = new CodeType { Name = "void", IsExternal = true }
+ };
+ existingConstructor.AddParameter(new CodeParameter
+ {
+ Name = "message",
+ Type = new CodeType { Name = "String", IsExternal = true }
+ });
+ errorClass.AddMethod(existingConstructor);
+
+ var initialConstructorCount = errorClass.Methods.Count(m => m.IsOfKind(CodeMethodKind.Constructor));
+
+ // When
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Java }, root);
+
+ // Then - should add only the parameterless constructor, not duplicate the message one
+ var finalConstructorCount = errorClass.Methods.Count(m => m.IsOfKind(CodeMethodKind.Constructor));
+ Assert.Equal(initialConstructorCount + 1, finalConstructorCount); // Only parameterless constructor added
+ }
#endregion
}
diff --git a/tests/Kiota.Builder.Tests/Refiners/PhpLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/PhpLanguageRefinerTests.cs
index 7651e1d416..ee2ac38596 100644
--- a/tests/Kiota.Builder.Tests/Refiners/PhpLanguageRefinerTests.cs
+++ b/tests/Kiota.Builder.Tests/Refiners/PhpLanguageRefinerTests.cs
@@ -299,4 +299,106 @@ public async Task DoesNotCreateDuplicateComposedTypeWrapperIfOneAlreadyExistsAsy
Assert.True(root.FindChildByName("Union", false) is CodeClass unionTypeWrapper && unionTypeWrapper.OriginalComposedType != null);
Assert.True(root.FindChildByName("UnionWrapper", false) is null);
}
+
+ [Fact]
+ public async Task AddsConstructorsForErrorClassesAsync()
+ {
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "SomeError",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = true
+ }).First();
+
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.PHP }, root);
+
+ // PHP only allows one __construct method, so check for single constructor with optional message parameter
+ var constructor = errorClass.Methods.FirstOrDefault(m => m.IsOfKind(CodeMethodKind.Constructor));
+ Assert.NotNull(constructor);
+ Assert.Single(constructor.Parameters);
+ var constructorMessageParam = constructor.Parameters.First();
+ Assert.Equal("message", constructorMessageParam.Name);
+ Assert.Equal("string", constructorMessageParam.Type.Name);
+ Assert.True(constructorMessageParam.Optional);
+
+ // Check for CreateFromDiscriminatorValueWithMessage factory method
+ var discriminatorMessageFactory = errorClass.Methods.FirstOrDefault(m =>
+ m.Name == "createFromDiscriminatorValueWithMessage" &&
+ m.IsOfKind(CodeMethodKind.FactoryWithErrorMessage));
+ Assert.NotNull(discriminatorMessageFactory);
+ Assert.Equal(2, discriminatorMessageFactory.Parameters.Count());
+ var parseNodeParam = discriminatorMessageFactory.Parameters.FirstOrDefault(p => p.Name == "parseNode");
+ var factoryMessageParam = discriminatorMessageFactory.Parameters.FirstOrDefault(p => p.Name == "message");
+ Assert.NotNull(parseNodeParam);
+ Assert.NotNull(factoryMessageParam);
+ Assert.Equal("string", factoryMessageParam.Type.Name);
+ }
+
+ [Fact]
+ public async Task DoesNotAddConstructorsForNonErrorClassesAsync()
+ {
+ var regularClass = root.AddClass(new CodeClass
+ {
+ Name = "SomeModel",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = false
+ }).First();
+
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.PHP }, root);
+
+ // Should not have error-specific constructors or factory methods
+ Assert.DoesNotContain(regularClass.Methods, m => m.Name == "createFromDiscriminatorValueWithMessage" && m.IsOfKind(CodeMethodKind.FactoryWithErrorMessage));
+ }
+
+ [Fact]
+ public async Task AddsConstructorsOnlyOnceForErrorClassesAsync()
+ {
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "DuplicateError",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = true
+ }).First();
+
+ // Add existing constructor to simulate already having one
+ errorClass.AddMethod(new CodeMethod
+ {
+ Name = "constructor",
+ Kind = CodeMethodKind.Constructor,
+ ReturnType = new CodeType { Name = "void", IsExternal = true }
+ });
+
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.PHP }, root);
+
+ // Should have only one constructor (with optional message parameter added to existing constructor)
+ Assert.Single(errorClass.Methods, m => m.IsOfKind(CodeMethodKind.Constructor));
+ var constructor = errorClass.Methods.First(m => m.IsOfKind(CodeMethodKind.Constructor));
+ Assert.Single(constructor.Parameters); // Should have message parameter added
+ Assert.True(constructor.Parameters.First().Optional);
+ Assert.Single(errorClass.Methods, m => m.Name == "createFromDiscriminatorValueWithMessage" && m.IsOfKind(CodeMethodKind.FactoryWithErrorMessage));
+ }
+
+ [Fact]
+ public async Task AddsConstructorsWithCorrectDescriptionForErrorClassesAsync()
+ {
+ var errorClassWithDescription = root.AddClass(new CodeClass
+ {
+ Name = "DetailedError",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = true
+ }).First();
+
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.PHP }, root);
+
+ // Check single constructor with optional message parameter is created
+ var constructor = errorClassWithDescription.Methods.FirstOrDefault(m => m.IsOfKind(CodeMethodKind.Constructor));
+ Assert.NotNull(constructor);
+ Assert.NotEmpty(constructor.Documentation.DescriptionTemplate);
+ Assert.Single(constructor.Parameters);
+ Assert.True(constructor.Parameters.First().Optional);
+
+ var discriminatorMessageFactory = errorClassWithDescription.Methods.FirstOrDefault(m => m.Name == "createFromDiscriminatorValueWithMessage" && m.IsOfKind(CodeMethodKind.FactoryWithErrorMessage));
+ Assert.NotNull(discriminatorMessageFactory);
+ Assert.NotEmpty(discriminatorMessageFactory.Documentation.DescriptionTemplate);
+ }
}
diff --git a/tests/Kiota.Builder.Tests/Refiners/PythonLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/PythonLanguageRefinerTests.cs
index f0635cfb4e..af3ebc45c8 100644
--- a/tests/Kiota.Builder.Tests/Refiners/PythonLanguageRefinerTests.cs
+++ b/tests/Kiota.Builder.Tests/Refiners/PythonLanguageRefinerTests.cs
@@ -651,5 +651,129 @@ public async Task ReplacesUntypedNodeInMethodParameterAndReturnTypeAsync()
Assert.Equal("bytes", method.Parameters.First().Type.Name);// type is renamed to use the stream type
Assert.Equal("bytes", method.ReturnType.Name);// return type is renamed to use the stream type
}
+
+ [Fact]
+ public async Task AddsConstructorsForErrorClasses()
+ {
+ // Given
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "Error401",
+ IsErrorDefinition = true
+ }).First();
+
+ // When
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Python }, root);
+
+ // Then
+ var parameterlessConstructor = errorClass.Methods
+ .FirstOrDefault(m => m.IsOfKind(CodeMethodKind.Constructor) && !m.Parameters.Any());
+
+ Assert.NotNull(parameterlessConstructor);
+ Assert.Equal("__init__", parameterlessConstructor.Name);
+ Assert.Equal(AccessModifier.Public, parameterlessConstructor.Access);
+
+ var messageConstructor = errorClass.Methods
+ .FirstOrDefault(m => m.IsOfKind(CodeMethodKind.Constructor) &&
+ m.Parameters.Any(p => p.Type.Name.Equals("str", StringComparison.OrdinalIgnoreCase) && p.Name.Equals("message", StringComparison.OrdinalIgnoreCase)));
+
+ Assert.NotNull(messageConstructor);
+ Assert.Single(messageConstructor.Parameters);
+ Assert.Equal("message", messageConstructor.Parameters.First().Name);
+ Assert.Equal("str", messageConstructor.Parameters.First().Type.Name);
+ Assert.True(messageConstructor.Parameters.First().Optional);
+ Assert.Equal("None", messageConstructor.Parameters.First().DefaultValue);
+ }
+
+ [Fact]
+ public async Task DoesNotAddConstructorsToNonErrorClasses()
+ {
+ // Given
+ var regularClass = root.AddClass(new CodeClass
+ {
+ Name = "RegularModel",
+ IsErrorDefinition = false
+ }).First();
+
+ // When
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Python }, root);
+
+ // Then
+ var messageConstructor = regularClass.Methods
+ .FirstOrDefault(m => m.IsOfKind(CodeMethodKind.Constructor) &&
+ m.Parameters.Any(p => p.Type.Name.Equals("str", StringComparison.OrdinalIgnoreCase) && p.Name.Equals("message", StringComparison.OrdinalIgnoreCase)));
+
+ Assert.Null(messageConstructor);
+ }
+
+ [Fact]
+ public async Task AddsMessageFactoryMethodToErrorClasses()
+ {
+ // Given
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "Error401",
+ IsErrorDefinition = true
+ }).First();
+
+ // When
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Python }, root);
+
+ // Then
+ var messageFactoryMethod = errorClass.Methods
+ .FirstOrDefault(m => m.IsOfKind(CodeMethodKind.FactoryWithErrorMessage) &&
+ m.Name.Equals("create_from_discriminator_value_with_message", StringComparison.OrdinalIgnoreCase));
+
+ Assert.NotNull(messageFactoryMethod);
+ Assert.Equal(2, messageFactoryMethod.Parameters.Count());
+
+ var parseNodeParam = messageFactoryMethod.Parameters.FirstOrDefault(p => p.Name.Equals("parse_node", StringComparison.OrdinalIgnoreCase));
+ Assert.NotNull(parseNodeParam);
+ Assert.Equal("ParseNode", parseNodeParam.Type.Name);
+
+ var messageParam = messageFactoryMethod.Parameters.FirstOrDefault(p => p.Name.Equals("message", StringComparison.OrdinalIgnoreCase));
+ Assert.NotNull(messageParam);
+ Assert.Equal("str", messageParam.Type.Name);
+ Assert.True(messageParam.Optional);
+ Assert.Equal("None", messageParam.DefaultValue);
+
+ Assert.True(messageFactoryMethod.IsStatic);
+ Assert.Equal(AccessModifier.Public, messageFactoryMethod.Access);
+ }
+
+ [Fact]
+ public async Task DoesNotDuplicateExistingConstructors()
+ {
+ // Given
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "Error401",
+ IsErrorDefinition = true
+ }).First();
+
+ // Add an existing message constructor
+ var existingConstructor = new CodeMethod
+ {
+ Name = "__init__",
+ Kind = CodeMethodKind.Constructor,
+ Access = AccessModifier.Public,
+ ReturnType = new CodeType { Name = "None", IsExternal = true }
+ };
+ existingConstructor.AddParameter(new CodeParameter
+ {
+ Name = "message",
+ Type = new CodeType { Name = "str", IsExternal = true }
+ });
+ errorClass.AddMethod(existingConstructor);
+
+ var initialConstructorCount = errorClass.Methods.Count(m => m.IsOfKind(CodeMethodKind.Constructor));
+
+ // When
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Python }, root);
+
+ // Then - should add only the parameterless constructor, not duplicate the message one
+ var finalConstructorCount = errorClass.Methods.Count(m => m.IsOfKind(CodeMethodKind.Constructor));
+ Assert.Equal(initialConstructorCount + 1, finalConstructorCount); // Only parameterless constructor added
+ }
#endregion
}
diff --git a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs
index f0b68dd3a7..13de9906d3 100644
--- a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs
+++ b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs
@@ -1120,5 +1120,100 @@ public async Task AddsUsingForUntypedNodeInMethodParameterAsync()
Assert.Single(nodeUsing);
Assert.Equal("@microsoft/kiota-abstractions", nodeUsing[0].Declaration.Name);
}
+
+ [Fact]
+ public async Task AddsConstructorsForErrorClassesAsync()
+ {
+ var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript };
+ var errorClass = TestHelper.CreateModelClassInModelsNamespace(generationConfiguration, root, "SomeError");
+ errorClass.IsErrorDefinition = true;
+
+ await ILanguageRefiner.RefineAsync(generationConfiguration, root);
+
+ // Check for parameterless constructor
+ var parameterlessConstructor = errorClass.Methods.FirstOrDefault(m => m.Name == "constructor" && m.IsOfKind(CodeMethodKind.Constructor) && !m.Parameters.Any());
+ Assert.NotNull(parameterlessConstructor);
+
+ // Check for constructor with message parameter
+ var messageConstructor = errorClass.Methods.FirstOrDefault(m =>
+ m.Name == "constructor" &&
+ m.IsOfKind(CodeMethodKind.Constructor) &&
+ m.Parameters.Count() == 1 &&
+ m.Parameters.First().Name == "message");
+ Assert.NotNull(messageConstructor);
+ Assert.Equal("string", messageConstructor.Parameters.First().Type.Name);
+ Assert.True(messageConstructor.Parameters.First().Optional);
+
+ // Check for createFromDiscriminatorValueWithMessage factory method
+ var discriminatorMessageFactory = errorClass.Methods.FirstOrDefault(m =>
+ m.Name == "createFromDiscriminatorValueWithMessage" &&
+ m.IsOfKind(CodeMethodKind.FactoryWithErrorMessage));
+ Assert.NotNull(discriminatorMessageFactory);
+ Assert.Equal(2, discriminatorMessageFactory.Parameters.Count());
+ var parseNodeParam = discriminatorMessageFactory.Parameters.FirstOrDefault(p => p.Name == "parseNode");
+ var messageParam = discriminatorMessageFactory.Parameters.FirstOrDefault(p => p.Name == "message");
+ Assert.NotNull(parseNodeParam);
+ Assert.NotNull(messageParam);
+ Assert.Equal("string", messageParam.Type.Name);
+ Assert.True(messageParam.Optional);
+ }
+
+ [Fact]
+ public async Task DoesNotAddConstructorsForNonErrorClassesAsync()
+ {
+ var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript };
+ var regularClass = TestHelper.CreateModelClassInModelsNamespace(generationConfiguration, root, "SomeModel");
+
+ await ILanguageRefiner.RefineAsync(generationConfiguration, root);
+
+ // Should not have error-specific constructors or factory methods
+ Assert.DoesNotContain(regularClass.Methods, m => m.Name == "createFromDiscriminatorValueWithMessage" && m.IsOfKind(CodeMethodKind.Factory));
+ }
+
+ [Fact]
+ public async Task AddsConstructorsOnlyOnceForErrorClassesAsync()
+ {
+ var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript };
+ var errorClass = TestHelper.CreateModelClassInModelsNamespace(generationConfiguration, root, "DuplicateError");
+ errorClass.IsErrorDefinition = true;
+
+ // Add existing constructor to simulate already having one
+ errorClass.AddMethod(new CodeMethod
+ {
+ Name = "constructor",
+ Kind = CodeMethodKind.Constructor,
+ ReturnType = new CodeType { Name = "void", IsExternal = true }
+ });
+
+ await ILanguageRefiner.RefineAsync(generationConfiguration, root);
+
+ // Should have only one of each constructor/method
+ Assert.Single(errorClass.Methods, m => m.Name == "constructor" && m.IsOfKind(CodeMethodKind.Constructor) && !m.Parameters.Any());
+ Assert.Single(errorClass.Methods, m => m.Name == "constructor" && m.IsOfKind(CodeMethodKind.Constructor) && m.Parameters.Count() == 1);
+ Assert.Single(errorClass.Methods, m => m.Name == "createFromDiscriminatorValueWithMessage" && m.IsOfKind(CodeMethodKind.FactoryWithErrorMessage));
+ }
+
+ [Fact]
+ public async Task AddsConstructorsWithCorrectDescriptionForErrorClassesAsync()
+ {
+ var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript };
+ var errorClassWithDescription = TestHelper.CreateModelClassInModelsNamespace(generationConfiguration, root, "DetailedError");
+ errorClassWithDescription.IsErrorDefinition = true;
+
+ await ILanguageRefiner.RefineAsync(generationConfiguration, root);
+
+ // Check constructors/methods are created
+ var parameterlessConstructor = errorClassWithDescription.Methods.FirstOrDefault(m => m.Name == "constructor" && m.IsOfKind(CodeMethodKind.Constructor) && !m.Parameters.Any());
+ Assert.NotNull(parameterlessConstructor);
+ Assert.NotEmpty(parameterlessConstructor.Documentation.DescriptionTemplate);
+
+ var messageConstructor = errorClassWithDescription.Methods.FirstOrDefault(m => m.Name == "constructor" && m.IsOfKind(CodeMethodKind.Constructor) && m.Parameters.Count() == 1);
+ Assert.NotNull(messageConstructor);
+ Assert.NotEmpty(messageConstructor.Documentation.DescriptionTemplate);
+
+ var discriminatorMessageFactory = errorClassWithDescription.Methods.FirstOrDefault(m => m.Name == "createFromDiscriminatorValueWithMessage" && m.IsOfKind(CodeMethodKind.FactoryWithErrorMessage));
+ Assert.NotNull(discriminatorMessageFactory);
+ Assert.NotEmpty(discriminatorMessageFactory.Documentation.DescriptionTemplate);
+ }
#endregion
}
diff --git a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs
index 020e4cfae3..de76b2d59a 100644
--- a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs
+++ b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs
@@ -2101,4 +2101,32 @@ public void WritesRequestGeneratorContentTypeQuotes()
var result = tw.ToString();
Assert.Contains("\"application/json; profile=\\\"CamelCase\\\"\"", result);
}
+
+ [Fact]
+ public void WritesRequestExecutorWithEnhancedErrorMapping()
+ {
+ setup();
+ method.Kind = CodeMethodKind.RequestExecutor;
+ method.HttpMethod = HttpMethod.Get;
+ var error4XX = root.AddClass(new CodeClass
+ {
+ Name = "Error4XX",
+ IsErrorDefinition = true
+ }).First();
+ var error401 = root.AddClass(new CodeClass
+ {
+ Name = "Error401",
+ IsErrorDefinition = true
+ }).First();
+ method.AddErrorMapping("4XX", new CodeType { Name = "Error4XX", TypeDefinition = error4XX }, "Client Error");
+ method.AddErrorMapping("401", new CodeType { Name = "Error401", TypeDefinition = error401 }, "Unauthorized");
+ AddRequestBodyParameters();
+ writer.Write(method);
+ var result = tw.ToString();
+ Assert.Contains("var requestInfo", result);
+ Assert.Contains("var errorMapping = new Dictionary>", result);
+ Assert.Contains("{ \"4XX\", (parseNode) => Error4XX.CreateFromDiscriminatorValueWithMessage(parseNode, \"Client Error\") }", result);
+ Assert.Contains("{ \"401\", (parseNode) => Error401.CreateFromDiscriminatorValueWithMessage(parseNode, \"Unauthorized\") }", result);
+ Assert.Contains("send", result.ToLower());
+ }
}
diff --git a/tests/Kiota.Builder.Tests/Writers/Dart/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Dart/CodeMethodWriterTests.cs
index d4058d9483..9044b8767c 100644
--- a/tests/Kiota.Builder.Tests/Writers/Dart/CodeMethodWriterTests.cs
+++ b/tests/Kiota.Builder.Tests/Writers/Dart/CodeMethodWriterTests.cs
@@ -1620,4 +1620,171 @@ public void WritesRequestGeneratorContentTypeQuotes()
var result = tw.ToString();
Assert.Contains("'application/json; profile=\"CamelCase\"'", result);
}
+
+ [Fact]
+ public void WritesRequestExecutorWithEnhancedErrorMapping()
+ {
+ setup();
+ method.Kind = CodeMethodKind.RequestExecutor;
+ method.HttpMethod = HttpMethod.Get;
+ var error4XX = root.AddClass(new CodeClass
+ {
+ Name = "Error4XX",
+ IsErrorDefinition = true
+ }).First();
+ var error401 = root.AddClass(new CodeClass
+ {
+ Name = "Error401",
+ IsErrorDefinition = true
+ }).First();
+ method.AddErrorMapping("4XX", new CodeType { Name = "Error4XX", TypeDefinition = error4XX }, "Client Error");
+ method.AddErrorMapping("401", new CodeType { Name = "Error401", TypeDefinition = error401 }, "Unauthorized");
+ AddRequestBodyParameters();
+ writer.Write(method);
+ var result = tw.ToString();
+ Assert.Contains("final errorMapping = >{", result);
+ Assert.Contains("'4XX' : (parseNode) => Error4XX.createFromDiscriminatorValueWithMessage(parseNode, '4XX Client Error'),", result);
+ Assert.Contains("'401' : (parseNode) => Error401.createFromDiscriminatorValueWithMessage(parseNode, '401 Unauthorized'),", result);
+ }
+
+ [Fact]
+ public void WritesRequestExecutorWithRegularErrorMapping()
+ {
+ setup();
+ method.Kind = CodeMethodKind.RequestExecutor;
+ method.HttpMethod = HttpMethod.Get;
+ var regularClass = root.AddClass(new CodeClass
+ {
+ Name = "RegularError",
+ IsErrorDefinition = false
+ }).First();
+ method.AddErrorMapping("500", new CodeType { Name = "RegularError", TypeDefinition = regularClass });
+ AddRequestBodyParameters();
+ writer.Write(method);
+ var result = tw.ToString();
+ Assert.Contains("final errorMapping = >{", result);
+ Assert.Contains("'500' : RegularError.createFromDiscriminatorValue,", result);
+ Assert.DoesNotContain("createFromDiscriminatorValueWithMessage", result);
+ }
+
+ [Fact]
+ public void WritesFactoryMethodBodyForErrorClassWithMessage()
+ {
+ setup();
+ parentClass.IsErrorDefinition = true;
+ method.Kind = CodeMethodKind.FactoryWithErrorMessage;
+ method.Name = "createFromDiscriminatorValueWithMessage";
+ method.IsStatic = true;
+ method.AddParameter(new CodeParameter
+ {
+ Name = "parseNode",
+ Kind = CodeParameterKind.ParseNode,
+ Type = new CodeType { Name = "ParseNode", IsExternal = true }
+ });
+ method.AddParameter(new CodeParameter
+ {
+ Name = "message",
+ Kind = CodeParameterKind.ErrorMessage,
+ Type = new CodeType { Name = "String", IsExternal = true }
+ });
+ writer.Write(method);
+ var result = tw.ToString();
+ Assert.Contains($"return {parentClass.Name}(message: message);", result);
+ }
+
+ [Fact]
+ public void WritesFactoryMethodBodyForErrorClassWithMessageAndAdditionalData()
+ {
+ setup();
+ parentClass.IsErrorDefinition = true;
+ parentClass.AddProperty(new CodeProperty
+ {
+ Name = "additionalData",
+ Kind = CodePropertyKind.AdditionalData,
+ Type = new CodeType { Name = "Map", IsExternal = true }
+ });
+ method.Kind = CodeMethodKind.FactoryWithErrorMessage;
+ method.Name = "createFromDiscriminatorValueWithMessage";
+ method.IsStatic = true;
+ method.AddParameter(new CodeParameter
+ {
+ Name = "parseNode",
+ Kind = CodeParameterKind.ParseNode,
+ Type = new CodeType { Name = "ParseNode", IsExternal = true }
+ });
+ method.AddParameter(new CodeParameter
+ {
+ Name = "message",
+ Kind = CodeParameterKind.ErrorMessage,
+ Type = new CodeType { Name = "String", IsExternal = true }
+ });
+ writer.Write(method);
+ var result = tw.ToString();
+ Assert.Contains($"return {parentClass.Name}(message: message, additionalData: {{}});", result);
+ }
+
+ [Fact]
+ public void WritesConstructorWithMessageInheritance()
+ {
+ // Given
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "TestError",
+ IsErrorDefinition = true
+ }).First();
+
+ var constructor = new CodeMethod
+ {
+ Name = "constructor",
+ Kind = CodeMethodKind.Constructor,
+ ReturnType = new CodeType { Name = "void", IsExternal = true },
+ Parent = errorClass
+ };
+ constructor.AddParameter(new CodeParameter
+ {
+ Name = "message",
+ Type = new CodeType { Name = "String", IsExternal = true }
+ });
+
+ parentClass = errorClass;
+ method = constructor;
+
+ // When
+ writer.Write(method);
+ var result = tw.ToString();
+
+ // Then
+ Assert.Contains("super(message: message),", result);
+ }
+
+ [Fact]
+ public void WritesConstructorWithoutMessageInheritance()
+ {
+ // Given
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "TestError",
+ IsErrorDefinition = true
+ }).First();
+
+ var constructor = new CodeMethod
+ {
+ Name = "constructor",
+ Kind = CodeMethodKind.Constructor,
+ ReturnType = new CodeType { Name = "void", IsExternal = true },
+ Parent = errorClass
+ };
+ // No message parameter
+
+ parentClass = errorClass;
+ method = constructor;
+
+ // When
+ writer.Write(method);
+ var result = tw.ToString();
+
+ // Then
+ Assert.Contains("super(),", result);
+ Assert.DoesNotContain("super(message:", result);
+ }
}
diff --git a/tests/Kiota.Builder.Tests/Writers/Go/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Go/CodeMethodWriterTests.cs
index ca98e97656..dd477227fd 100644
--- a/tests/Kiota.Builder.Tests/Writers/Go/CodeMethodWriterTests.cs
+++ b/tests/Kiota.Builder.Tests/Writers/Go/CodeMethodWriterTests.cs
@@ -2331,4 +2331,246 @@ public void WritesRequestGeneratorContentTypeQuotes()
var result = tw.ToString();
Assert.Contains("\"application/json; profile=\\\"CamelCase\\\"\"", result);
}
+
+ [Fact]
+ public void WritesErrorMappingWithDescriptions()
+ {
+ setup();
+ method.Kind = CodeMethodKind.RequestExecutor;
+ method.HttpMethod = HttpMethod.Get;
+
+ // Create error classes with descriptions
+ var error4XX = root.AddClass(new CodeClass
+ {
+ Name = "Error4XX",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = true
+ }).First();
+
+ var error5XX = root.AddClass(new CodeClass
+ {
+ Name = "Error5XX",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = true
+ }).First();
+
+ // Add error mappings with descriptions
+ method.AddErrorMapping("4XX", new CodeType { Name = "Error4XX", TypeDefinition = error4XX }, "Client error response");
+ method.AddErrorMapping("5XX", new CodeType { Name = "Error5XX", TypeDefinition = error5XX }, "Server error response");
+
+ AddRequestBodyParameters();
+ writer.Write(method);
+ var result = tw.ToString();
+
+ // Check for enhanced error mapping with descriptions
+ Assert.Contains($"errorMapping := {AbstractionsPackageHash}.ErrorMappings", result);
+ Assert.Contains("Client error response", result);
+ Assert.Contains("Server error response", result);
+ AssertExtensions.CurlyBracesAreClosed(result);
+ }
+
+ [Fact]
+ public void WritesErrorMappingWithoutDescriptions()
+ {
+ setup();
+ method.Kind = CodeMethodKind.RequestExecutor;
+ method.HttpMethod = HttpMethod.Get;
+
+ // Create error classes without descriptions
+ var error4XX = root.AddClass(new CodeClass
+ {
+ Name = "Error4XX",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = true
+ }).First();
+
+ // Add error mapping without description
+ method.AddErrorMapping("4XX", new CodeType { Name = "Error4XX", TypeDefinition = error4XX });
+
+ // No error descriptions set up
+
+ AddRequestBodyParameters();
+ writer.Write(method);
+ var result = tw.ToString();
+
+ // Should use original factory method when no description is provided
+ Assert.Contains($"errorMapping := {AbstractionsPackageHash}.ErrorMappings", result);
+ Assert.Contains("\"4XX\": CreateError4XXFromDiscriminatorValue", result);
+ Assert.DoesNotContain("CreateError4XXFromDiscriminatorValueWithMessage", result);
+ AssertExtensions.CurlyBracesAreClosed(result);
+ }
+
+ [Fact]
+ public void WritesFactoryMethodBodyForErrorClassWithMessage()
+ {
+ setup();
+
+ // Create an error class
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "SomeError",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = true
+ }).First();
+
+ // Create the factory method with message parameter
+ var factoryMethod = errorClass.AddMethod(new CodeMethod
+ {
+ Name = "CreateFromDiscriminatorValueWithMessage",
+ Kind = CodeMethodKind.FactoryWithErrorMessage,
+ ReturnType = new CodeType
+ {
+ Name = "*SomeError",
+ TypeDefinition = errorClass,
+ IsNullable = true
+ },
+ IsStatic = true,
+ }).First();
+
+ // Add parseNode parameter
+ factoryMethod.AddParameter(new CodeParameter
+ {
+ Name = "parseNode",
+ Kind = CodeParameterKind.ParseNode,
+ Type = new CodeType
+ {
+ Name = "ParseNode",
+ IsExternal = true,
+ },
+ Optional = false,
+ });
+
+ // Add message parameter
+ factoryMethod.AddParameter(new CodeParameter
+ {
+ Name = "message",
+ Kind = CodeParameterKind.ErrorMessage,
+ Type = new CodeType
+ {
+ Name = "string",
+ IsExternal = true,
+ },
+ Optional = false,
+ });
+
+ parentClass = errorClass; // Set parentClass for the test
+ writer.Write(factoryMethod);
+ var result = tw.ToString();
+
+ // Should call the factory method with message
+ Assert.Contains("return NewSomeErrorWithMessage(message), nil", result);
+ AssertExtensions.CurlyBracesAreClosed(result);
+ }
+
+ [Fact]
+ public void WritesFactoryMethodBodyForErrorClassWithoutMessage()
+ {
+ setup();
+
+ // Create an error class
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "SomeError",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = true
+ }).First();
+
+ // Create the factory method without message parameter
+ var factoryMethod = errorClass.AddMethod(new CodeMethod
+ {
+ Name = "CreateFromDiscriminatorValueWithMessage",
+ Kind = CodeMethodKind.Factory,
+ ReturnType = new CodeType
+ {
+ Name = "*SomeError",
+ TypeDefinition = errorClass,
+ IsNullable = true
+ },
+ IsStatic = true,
+ }).First();
+
+ // Add only parseNode parameter (no message parameter)
+ factoryMethod.AddParameter(new CodeParameter
+ {
+ Name = "parseNode",
+ Kind = CodeParameterKind.ParseNode,
+ Type = new CodeType
+ {
+ Name = "ParseNode",
+ IsExternal = true,
+ },
+ Optional = false,
+ });
+
+ parentClass = errorClass; // Set parentClass for the test
+ writer.Write(factoryMethod);
+ var result = tw.ToString();
+
+ // Should call the factory method without message
+ Assert.Contains("return NewSomeError(), nil", result);
+ AssertExtensions.CurlyBracesAreClosed(result);
+ }
+
+ [Fact]
+ public void DoesNotWriteSpecialFactoryMethodForNonErrorClasses()
+ {
+ setup();
+
+ // Create a regular class
+ var regularClass = root.AddClass(new CodeClass
+ {
+ Name = "SomeModel",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = false
+ }).First();
+
+ // Create the factory method
+ var factoryMethod = regularClass.AddMethod(new CodeMethod
+ {
+ Name = "CreateFromDiscriminatorValueWithMessage",
+ Kind = CodeMethodKind.Factory,
+ ReturnType = new CodeType
+ {
+ Name = "*SomeModel",
+ TypeDefinition = regularClass,
+ IsNullable = true
+ },
+ IsStatic = true,
+ }).First();
+
+ // Add parameters
+ factoryMethod.AddParameter(new CodeParameter
+ {
+ Name = "parseNode",
+ Kind = CodeParameterKind.ParseNode,
+ Type = new CodeType
+ {
+ Name = "ParseNode",
+ IsExternal = true,
+ },
+ Optional = false,
+ });
+
+ factoryMethod.AddParameter(new CodeParameter
+ {
+ Name = "message",
+ Kind = CodeParameterKind.RequestBodyContentType,
+ Type = new CodeType
+ {
+ Name = "string",
+ IsExternal = true,
+ },
+ Optional = false,
+ });
+
+ parentClass = regularClass;
+ writer.Write(factoryMethod);
+ var result = tw.ToString();
+
+ // Should not use special error handling for non-error classes
+ Assert.DoesNotContain("NewSomeModelWithMessage", result);
+ // Should use normal factory method logic
+ Assert.Contains("return NewSomeModel(), nil", result);
+ AssertExtensions.CurlyBracesAreClosed(result);
+ }
}
diff --git a/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs
index 5f21b81ff2..1f6d72f2d5 100644
--- a/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs
+++ b/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs
@@ -2192,4 +2192,111 @@ public void WritesRequestGeneratorContentTypeQuotes()
var result = tw.ToString();
Assert.Contains("\"application/json; profile=\\\"CamelCase\\\"\"", result);
}
+
+ [Fact]
+ public void WritesRequestExecutorWithEnhancedErrorMapping()
+ {
+ setup();
+ method.Kind = CodeMethodKind.RequestExecutor;
+ method.HttpMethod = HttpMethod.Get;
+ var error4XX = root.AddClass(new CodeClass
+ {
+ Name = "Error4XX",
+ IsErrorDefinition = true
+ }).First();
+ var error401 = root.AddClass(new CodeClass
+ {
+ Name = "Error401",
+ IsErrorDefinition = true
+ }).First();
+ method.AddErrorMapping("4XX", new CodeType { Name = "Error4XX", TypeDefinition = error4XX }, "Client Error");
+ method.AddErrorMapping("401", new CodeType { Name = "Error401", TypeDefinition = error401 }, "Unauthorized");
+ AddRequestBodyParameters();
+ writer.Write(method);
+ var result = tw.ToString();
+ Assert.Contains("final HashMap> errorMapping", result);
+ Assert.Contains("(parseNode) -> Error4XX.createFromDiscriminatorValueWithMessage(parseNode, \"Client Error\")", result);
+ Assert.Contains("(parseNode) -> Error401.createFromDiscriminatorValueWithMessage(parseNode, \"Unauthorized\")", result);
+ Assert.Contains("send", result.ToLower());
+ }
+
+ [Fact]
+ public void WritesRequestExecutorWithRegularErrorMapping()
+ {
+ setup();
+ method.Kind = CodeMethodKind.RequestExecutor;
+ method.HttpMethod = HttpMethod.Get;
+ var regularClass = root.AddClass(new CodeClass
+ {
+ Name = "RegularError",
+ IsErrorDefinition = false
+ }).First();
+ method.AddErrorMapping("500", new CodeType { Name = "RegularError", TypeDefinition = regularClass });
+ AddRequestBodyParameters();
+ writer.Write(method);
+ var result = tw.ToString();
+ Assert.Contains("final HashMap> errorMapping", result);
+ Assert.Contains("RegularError::createFromDiscriminatorValue", result);
+ Assert.DoesNotContain("createFromDiscriminatorValueWithMessage", result);
+ }
+
+ [Fact]
+ public void WritesFactoryMethodBodyForErrorClassWithMessage()
+ {
+ setup();
+ parentClass.IsErrorDefinition = true;
+ method.Kind = CodeMethodKind.FactoryWithErrorMessage;
+ method.Name = "createFromDiscriminatorValueWithMessage";
+ method.IsStatic = true;
+ method.AddParameter(new CodeParameter
+ {
+ Name = "parseNode",
+ Kind = CodeParameterKind.ParseNode,
+ Type = new CodeType { Name = "ParseNode", IsExternal = true }
+ });
+ method.AddParameter(new CodeParameter
+ {
+ Name = "message",
+ Kind = CodeParameterKind.ErrorMessage,
+ Type = new CodeType { Name = "String", IsExternal = true }
+ });
+ writer.Write(method);
+ var result = tw.ToString();
+ Assert.Contains($"return new {parentClass.Name}(message);", result);
+ }
+
+ [Fact]
+ public void WritesConstructorWithMessageInheritance()
+ {
+ // Given
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "TestError",
+ IsErrorDefinition = true
+ }).First();
+
+ var constructor = new CodeMethod
+ {
+ Name = "constructor",
+ Kind = CodeMethodKind.Constructor,
+ ReturnType = new CodeType { Name = "void", IsExternal = true },
+ Parent = errorClass
+ };
+ constructor.AddParameter(new CodeParameter
+ {
+ Name = "message",
+ Kind = CodeParameterKind.ErrorMessage,
+ Type = new CodeType { Name = "String", IsExternal = true }
+ });
+
+ parentClass = errorClass;
+ method = constructor;
+
+ // When
+ writer.Write(method);
+ var result = tw.ToString();
+
+ // Then
+ Assert.Contains("super(message);", result);
+ }
}
diff --git a/tests/Kiota.Builder.Tests/Writers/Php/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Php/CodeMethodWriterTests.cs
index fdf000c00e..1747e6451b 100644
--- a/tests/Kiota.Builder.Tests/Writers/Php/CodeMethodWriterTests.cs
+++ b/tests/Kiota.Builder.Tests/Writers/Php/CodeMethodWriterTests.cs
@@ -2558,4 +2558,255 @@ public async Task WritesRequestGeneratorBodyForMultipartAsync()
Assert.Contains("MultiPartBody $body", result);
Assert.Contains("$requestInfo->setContentFromParsable($this->requestAdapter, \"multipart/form-data\", $body);", result);
}
+
+ [Fact]
+ public void WritesErrorMappingWithDescriptions()
+ {
+ setup();
+ method.Kind = CodeMethodKind.RequestExecutor;
+ method.HttpMethod = HttpMethod.Get;
+
+ // Add required properties for request executor
+ parentClass.AddProperty(new CodeProperty
+ {
+ Name = "requestAdapter",
+ Kind = CodePropertyKind.RequestAdapter,
+ Type = new CodeType { Name = "RequestAdapter" }
+ });
+
+ // Create error classes
+ var error4XX = root.AddClass(new CodeClass
+ {
+ Name = "Error4XX",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = true
+ }).First();
+
+ var error5XX = root.AddClass(new CodeClass
+ {
+ Name = "Error5XX",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = true
+ }).First();
+
+ // Add error mappings with descriptions
+ method.AddErrorMapping("4XX", new CodeType { Name = "Error4XX", TypeDefinition = error4XX }, "Client error response");
+ method.AddErrorMapping("5XX", new CodeType { Name = "Error5XX", TypeDefinition = error5XX }, "Server error response");
+
+ AddRequestBodyParameters();
+ languageWriter.Write(method);
+ var result = stringWriter.ToString();
+
+ // Check for enhanced error mapping with descriptions and PHP function syntax
+ Assert.Contains("$errorMappings = [", result);
+ Assert.Contains("'4XX' => function($parseNode) { return Error4XX::createFromDiscriminatorValueWithMessage($parseNode, 'Client error response'); }", result);
+ Assert.Contains("'5XX' => function($parseNode) { return Error5XX::createFromDiscriminatorValueWithMessage($parseNode, 'Server error response'); }", result);
+ }
+
+ [Fact]
+ public void WritesErrorMappingWithoutDescriptions()
+ {
+ setup();
+ method.Kind = CodeMethodKind.RequestExecutor;
+ method.HttpMethod = HttpMethod.Get;
+
+ // Add required properties for request executor
+ parentClass.AddProperty(new CodeProperty
+ {
+ Name = "requestAdapter",
+ Kind = CodePropertyKind.RequestAdapter,
+ Type = new CodeType { Name = "RequestAdapter" }
+ });
+
+ // Create error classes without descriptions
+ var error4XX = root.AddClass(new CodeClass
+ {
+ Name = "Error4XX",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = true
+ }).First();
+
+ // Add error mapping without description
+ method.AddErrorMapping("4XX", new CodeType { Name = "Error4XX", TypeDefinition = error4XX });
+
+ AddRequestBodyParameters();
+ languageWriter.Write(method);
+ var result = stringWriter.ToString();
+
+ // Should use original factory method when no description is provided
+ Assert.Contains("$errorMappings = [", result);
+ Assert.Contains("'4XX' => [Error4XX::class, 'createFromDiscriminatorValue']", result);
+ Assert.DoesNotContain("createFromDiscriminatorValueWithMessage", result);
+ }
+
+ [Fact]
+ public void WritesFactoryMethodBodyForErrorClassWithMessage()
+ {
+ setup();
+
+ // Create an error class
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "SomeError",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = true
+ }).First();
+
+ // Create the factory method with message parameter
+ var factoryMethod = errorClass.AddMethod(new CodeMethod
+ {
+ Name = "createFromDiscriminatorValueWithMessage",
+ Kind = CodeMethodKind.FactoryWithErrorMessage,
+ ReturnType = new CodeType
+ {
+ Name = "SomeError",
+ TypeDefinition = errorClass,
+ IsNullable = true
+ },
+ IsStatic = true,
+ }).First();
+
+ // Add parseNode parameter
+ factoryMethod.AddParameter(new CodeParameter
+ {
+ Name = "parseNode",
+ Kind = CodeParameterKind.ParseNode,
+ Type = new CodeType
+ {
+ Name = "ParseNode",
+ IsExternal = true,
+ },
+ Optional = false,
+ });
+
+ // Add message parameter
+ factoryMethod.AddParameter(new CodeParameter
+ {
+ Name = "message",
+ Kind = CodeParameterKind.ErrorMessage,
+ Type = new CodeType
+ {
+ Name = "string",
+ IsExternal = true,
+ },
+ Optional = false,
+ });
+
+ parentClass = errorClass; // Set parentClass for the test
+ languageWriter.Write(factoryMethod);
+ var result = stringWriter.ToString();
+
+ // Should call the constructor with message
+ Assert.Contains("return new SomeError($message);", result);
+ }
+
+ [Fact]
+ public void WritesFactoryMethodBodyForErrorClassWithoutMessage()
+ {
+ setup();
+
+ // Create an error class
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "SomeError",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = true
+ }).First();
+
+ // Create the factory method without message parameter
+ var factoryMethod = errorClass.AddMethod(new CodeMethod
+ {
+ Name = "createFromDiscriminatorValueWithMessage",
+ Kind = CodeMethodKind.Factory,
+ ReturnType = new CodeType
+ {
+ Name = "SomeError",
+ TypeDefinition = errorClass,
+ IsNullable = true
+ },
+ IsStatic = true,
+ }).First();
+
+ // Add only parseNode parameter (no message parameter)
+ factoryMethod.AddParameter(new CodeParameter
+ {
+ Name = "parseNode",
+ Kind = CodeParameterKind.ParseNode,
+ Type = new CodeType
+ {
+ Name = "ParseNode",
+ IsExternal = true,
+ },
+ Optional = false,
+ });
+
+ parentClass = errorClass; // Set parentClass for the test
+ languageWriter.Write(factoryMethod);
+ var result = stringWriter.ToString();
+
+ // Should call the parameterless constructor
+ Assert.Contains("return new SomeError();", result);
+ }
+
+ [Fact]
+ public void DoesNotWriteSpecialFactoryMethodForNonErrorClasses()
+ {
+ setup();
+
+ // Create a regular class
+ var regularClass = root.AddClass(new CodeClass
+ {
+ Name = "SomeModel",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = false
+ }).First();
+
+ // Create the factory method
+ var factoryMethod = regularClass.AddMethod(new CodeMethod
+ {
+ Name = "createFromDiscriminatorValueWithMessage",
+ Kind = CodeMethodKind.Factory,
+ ReturnType = new CodeType
+ {
+ Name = "SomeModel",
+ TypeDefinition = regularClass,
+ IsNullable = true
+ },
+ IsStatic = true,
+ }).First();
+
+ // Add parameters
+ factoryMethod.AddParameter(new CodeParameter
+ {
+ Name = "parseNode",
+ Kind = CodeParameterKind.ParseNode,
+ Type = new CodeType
+ {
+ Name = "ParseNode",
+ IsExternal = true,
+ },
+ Optional = false,
+ });
+
+ factoryMethod.AddParameter(new CodeParameter
+ {
+ Name = "message",
+ Kind = CodeParameterKind.RequestBodyContentType,
+ Type = new CodeType
+ {
+ Name = "string",
+ IsExternal = true,
+ },
+ Optional = false,
+ });
+
+ parentClass = regularClass;
+ languageWriter.Write(factoryMethod);
+ var result = stringWriter.ToString();
+
+ // Should not use special error handling for non-error classes
+ Assert.DoesNotContain("new SomeModel($message)", result);
+ // Should continue with normal factory method logic or use parameterless constructor
+ Assert.Contains("new SomeModel();", result);
+ }
}
diff --git a/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs
index c59c16a6b5..51320eb6db 100644
--- a/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs
+++ b/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs
@@ -2298,4 +2298,140 @@ public void WritesRequestGeneratorContentTypeQuotes()
var result = tw.ToString();
Assert.Contains("\"application/json; profile=\\\"CamelCase\\\"\"", result);
}
+
+ [Fact]
+ public void WritesRequestExecutorWithEnhancedErrorMapping()
+ {
+ setup();
+ method.Kind = CodeMethodKind.RequestExecutor;
+ method.HttpMethod = HttpMethod.Get;
+ var error4XX = root.AddClass(new CodeClass
+ {
+ Name = "Error4XX",
+ IsErrorDefinition = true
+ }).First();
+ var error401 = root.AddClass(new CodeClass
+ {
+ Name = "Error401",
+ IsErrorDefinition = true
+ }).First();
+ method.AddErrorMapping("4XX", new CodeType { Name = "Error4XX", TypeDefinition = error4XX }, "Client Error");
+ method.AddErrorMapping("401", new CodeType { Name = "Error401", TypeDefinition = error401 }, "Unauthorized");
+ AddRequestBodyParameters();
+ writer.Write(method);
+ var result = tw.ToString();
+ Assert.Contains("error_mapping: dict[str, type[ParsableFactory]] = {", result);
+ Assert.Contains("\"4XX\": lambda parse_node: Error4XX.create_from_discriminator_value_with_message(parse_node, \"Client Error\"),", result);
+ Assert.Contains("\"401\": lambda parse_node: Error401.create_from_discriminator_value_with_message(parse_node, \"Unauthorized\"),", result);
+ }
+
+ [Fact]
+ public void WritesRequestExecutorWithRegularErrorMapping()
+ {
+ setup();
+ method.Kind = CodeMethodKind.RequestExecutor;
+ method.HttpMethod = HttpMethod.Get;
+ var regularClass = root.AddClass(new CodeClass
+ {
+ Name = "RegularError",
+ IsErrorDefinition = false
+ }).First();
+ method.AddErrorMapping("500", new CodeType { Name = "RegularError", TypeDefinition = regularClass });
+ AddRequestBodyParameters();
+ writer.Write(method);
+ var result = tw.ToString();
+ Assert.Contains("error_mapping: dict[str, type[ParsableFactory]] = {", result);
+ Assert.Contains("\"500\": RegularError,", result);
+ Assert.DoesNotContain("create_from_discriminator_value_with_message", result);
+ }
+
+ [Fact]
+ public void WritesFactoryMethodBodyForErrorClassWithMessage()
+ {
+ setup();
+ parentClass.IsErrorDefinition = true;
+ method.Kind = CodeMethodKind.FactoryWithErrorMessage;
+ method.Name = "create_from_discriminator_value_with_message";
+ method.IsStatic = true;
+ method.AddParameter(new CodeParameter
+ {
+ Name = "parse_node",
+ Kind = CodeParameterKind.ParseNode,
+ Type = new CodeType { Name = "ParseNode", IsExternal = true }
+ });
+ method.AddParameter(new CodeParameter
+ {
+ Name = "message",
+ Kind = CodeParameterKind.ErrorMessage,
+ Type = new CodeType { Name = "str", IsExternal = true }
+ });
+ writer.Write(method);
+ var result = tw.ToString();
+ Assert.Contains($"return {parentClass.Name}(message)", result);
+ }
+
+ [Fact]
+ public void WritesConstructorWithMessageInheritance()
+ {
+ // Given
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "TestError",
+ IsErrorDefinition = true
+ }).First();
+
+ var constructor = new CodeMethod
+ {
+ Name = "__init__",
+ Kind = CodeMethodKind.Constructor,
+ ReturnType = new CodeType { Name = "None", IsExternal = true },
+ Parent = errorClass
+ };
+ constructor.AddParameter(new CodeParameter
+ {
+ Name = "message",
+ Type = new CodeType { Name = "str", IsExternal = true }
+ });
+
+ parentClass = errorClass;
+ method = constructor;
+
+ // When
+ writer.Write(method);
+ var result = tw.ToString();
+
+ // Then
+ Assert.Contains("super().__init__(message)", result);
+ }
+
+ [Fact]
+ public void WritesConstructorWithoutMessageInheritance()
+ {
+ // Given
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "TestError",
+ IsErrorDefinition = true
+ }).First();
+
+ var constructor = new CodeMethod
+ {
+ Name = "__init__",
+ Kind = CodeMethodKind.Constructor,
+ ReturnType = new CodeType { Name = "None", IsExternal = true },
+ Parent = errorClass
+ };
+ // No message parameter
+
+ parentClass = errorClass;
+ method = constructor;
+
+ // When
+ writer.Write(method);
+ var result = tw.ToString();
+
+ // Then
+ Assert.Contains("super().__init__()", result);
+ Assert.DoesNotContain("super().__init__(message)", result);
+ }
}
diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs
index 6c65439a9f..30a8e34175 100644
--- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs
+++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs
@@ -477,6 +477,80 @@ public void WritesRequestExecutorBody()
Assert.Contains("_5XX: createError5XXFromDiscriminatorValue as ParsableFactory", result);
Assert.Contains("403: createError403FromDiscriminatorValue as ParsableFactory", result);
}
+
+ [Fact]
+ public void WritesRequestExecutorBodyWithErrorDescriptions()
+ {
+ parentClass.Kind = CodeClassKind.RequestBuilder;
+ method.Kind = CodeMethodKind.RequestExecutor;
+ method.HttpMethod = HttpMethod.Get;
+ var error4XX = root.AddClass(new CodeClass
+ {
+ Name = "Error4XX",
+ IsErrorDefinition = true
+ }).First();
+ var error5XX = root.AddClass(new CodeClass
+ {
+ Name = "Error5XX",
+ IsErrorDefinition = true
+ }).First();
+
+ // Add error mappings with descriptions
+ method.AddErrorMapping("4XX", new CodeType { Name = "Error4XX", TypeDefinition = error4XX }, "Client error response");
+ method.AddErrorMapping("5XX", new CodeType { Name = "Error5XX", TypeDefinition = error5XX }, "Server error response");
+
+ AddRequestBodyParameters();
+ var constant = CodeConstant.FromRequestBuilderToRequestsMetadata(parentClass);
+ var codeFile = root.TryAddCodeFile("foo", constant);
+ codeFile.AddElements(new CodeConstant
+ {
+ Name = "UriTemplate",
+ Kind = CodeConstantKind.UriTemplate,
+ UriTemplate = "{baseurl+}/foo/bar"
+ });
+ writer.Write(constant);
+ var result = tw.ToString();
+
+ // Check for enhanced error mapping with descriptions and TypeScript arrow function syntax
+ Assert.Contains("errorMappings: {", result);
+ Assert.Contains("_4XX: (parseNode: ParseNode) => createError4XXFromDiscriminatorValueWithMessage(parseNode, \"Client error response\") as ParsableFactory", result);
+ Assert.Contains("_5XX: (parseNode: ParseNode) => createError5XXFromDiscriminatorValueWithMessage(parseNode, \"Server error response\") as ParsableFactory", result);
+ AssertExtensions.CurlyBracesAreClosed(result);
+ }
+
+ [Fact]
+ public void WritesRequestExecutorBodyWithoutErrorDescriptions()
+ {
+ parentClass.Kind = CodeClassKind.RequestBuilder;
+ method.Kind = CodeMethodKind.RequestExecutor;
+ method.HttpMethod = HttpMethod.Get;
+ var error4XX = root.AddClass(new CodeClass
+ {
+ Name = "Error4XX",
+ IsErrorDefinition = true
+ }).First();
+
+ // Add error mapping without description
+ method.AddErrorMapping("4XX", new CodeType { Name = "Error4XX", TypeDefinition = error4XX });
+
+ AddRequestBodyParameters();
+ var constant = CodeConstant.FromRequestBuilderToRequestsMetadata(parentClass);
+ var codeFile = root.TryAddCodeFile("foo", constant);
+ codeFile.AddElements(new CodeConstant
+ {
+ Name = "UriTemplate",
+ Kind = CodeConstantKind.UriTemplate,
+ UriTemplate = "{baseurl+}/foo/bar"
+ });
+ writer.Write(constant);
+ var result = tw.ToString();
+
+ // Should use original factory method when no description is provided
+ Assert.Contains("errorMappings: {", result);
+ Assert.Contains("_4XX: createError4XXFromDiscriminatorValue as ParsableFactory", result);
+ Assert.DoesNotContain("createError4XXFromDiscriminatorValueWithMessage", result);
+ AssertExtensions.CurlyBracesAreClosed(result);
+ }
[Fact]
public void WritesIndexer()
{
diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs
index 02e13813af..d513066c9a 100644
--- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs
+++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs
@@ -1718,6 +1718,176 @@ public void WritesByteArrayPropertyDeserialization()
Assert.Contains("\"property\": n => { model.property = n.getByteArrayValue(); }", result, StringComparison.Ordinal);
}
+ [Fact]
+ public void WritesFactoryMethodBodyForErrorClassWithMessage()
+ {
+ // Create an error class
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "SomeError",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = true
+ }).First();
+
+ // Create the factory method with message parameter
+ var factoryMethod = errorClass.AddMethod(new CodeMethod
+ {
+ Name = "createFromDiscriminatorValueWithMessage",
+ Kind = CodeMethodKind.FactoryWithErrorMessage,
+ ReturnType = new CodeType
+ {
+ Name = "SomeError",
+ TypeDefinition = errorClass,
+ IsNullable = true
+ },
+ IsStatic = true,
+ }).First();
+
+ // Add parseNode parameter
+ factoryMethod.AddParameter(new CodeParameter
+ {
+ Name = "parseNode",
+ Kind = CodeParameterKind.ParseNode,
+ Type = new CodeType
+ {
+ Name = "ParseNode",
+ IsExternal = true,
+ },
+ Optional = false,
+ });
+
+ // Add message parameter
+ factoryMethod.AddParameter(new CodeParameter
+ {
+ Name = "message",
+ Kind = CodeParameterKind.ErrorMessage,
+ Type = new CodeType
+ {
+ Name = "string",
+ IsExternal = true,
+ },
+ Optional = true,
+ });
+
+ var function = new CodeFunction(factoryMethod);
+ var codeFile = root.TryAddCodeFile("foo", function);
+ writer.Write(function);
+ var result = tw.ToString();
+
+ // Should call the constructor with message
+ Assert.Contains("return new SomeError(message);", result);
+ AssertExtensions.CurlyBracesAreClosed(result, 1);
+ }
+
+ [Fact]
+ public void WritesFactoryMethodBodyForErrorClassWithoutMessage()
+ {
+ // Create an error class
+ var errorClass = root.AddClass(new CodeClass
+ {
+ Name = "SomeError",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = true
+ }).First();
+
+ // Create the factory method without message parameter
+ var factoryMethod = errorClass.AddMethod(new CodeMethod
+ {
+ Name = "createFromDiscriminatorValueWithMessage",
+ Kind = CodeMethodKind.FactoryWithErrorMessage,
+ ReturnType = new CodeType
+ {
+ Name = "SomeError",
+ TypeDefinition = errorClass,
+ IsNullable = true
+ },
+ IsStatic = true,
+ }).First();
+
+ // Add only parseNode parameter (no message parameter)
+ factoryMethod.AddParameter(new CodeParameter
+ {
+ Name = "parseNode",
+ Kind = CodeParameterKind.ParseNode,
+ Type = new CodeType
+ {
+ Name = "ParseNode",
+ IsExternal = true,
+ },
+ Optional = false,
+ });
+
+ var function = new CodeFunction(factoryMethod);
+ var codeFile = root.TryAddCodeFile("foo", function);
+ writer.Write(function);
+ var result = tw.ToString();
+
+ // Should call the parameterless constructor
+ Assert.Contains("return new SomeError();", result);
+ AssertExtensions.CurlyBracesAreClosed(result, 1);
+ }
+
+ [Fact]
+ public void DoesNotWriteSpecialFactoryMethodForNonErrorClasses()
+ {
+ // Create a regular class
+ var regularClass = root.AddClass(new CodeClass
+ {
+ Name = "SomeModel",
+ Kind = CodeClassKind.Model,
+ IsErrorDefinition = false
+ }).First();
+
+ // Create the factory method
+ var factoryMethod = regularClass.AddMethod(new CodeMethod
+ {
+ Name = "createFromDiscriminatorValueWithMessage",
+ Kind = CodeMethodKind.Factory,
+ ReturnType = new CodeType
+ {
+ Name = "SomeModel",
+ TypeDefinition = regularClass,
+ IsNullable = true
+ },
+ IsStatic = true,
+ }).First();
+
+ // Add parameters
+ factoryMethod.AddParameter(new CodeParameter
+ {
+ Name = "parseNode",
+ Kind = CodeParameterKind.ParseNode,
+ Type = new CodeType
+ {
+ Name = "ParseNode",
+ IsExternal = true,
+ },
+ Optional = false,
+ });
+
+ factoryMethod.AddParameter(new CodeParameter
+ {
+ Name = "message",
+ Kind = CodeParameterKind.ErrorMessage,
+ Type = new CodeType
+ {
+ Name = "string",
+ IsExternal = true,
+ },
+ Optional = true,
+ });
+
+ var function = new CodeFunction(factoryMethod);
+ var codeFile = root.TryAddCodeFile("foo", function);
+ writer.Write(function);
+ var result = tw.ToString();
+
+ // Should not use special error handling for non-error classes
+ Assert.DoesNotContain("new SomeModel(message)", result);
+ // Should continue with normal factory method logic - the exact output varies based on setup but should not be the error handling path
+ AssertExtensions.CurlyBracesAreClosed(result, 1);
+ }
+
[Fact]
public async Task WritesOneOfWithInheritanceDeserializationAsync()
{