Skip to content

Conversation

@koen-lee
Copy link

@koen-lee koen-lee commented Sep 25, 2025

See also #4349; this PR aims to change the developer experience from

ApiSdk.Models.ProblemDetails:Exception of type 'ApiSdk.Models.ProblemDetails' was thrown.
         at Microsoft.Kiota.Http.HttpClientLibrary.HttpClientRequestAdapter.ThrowIfFailedResponse....

to

ApiSdk.Models.ProblemDetails:403 Client certificate does not have required 'Write' role
         at Microsoft.Kiota.Http.HttpClientLibrary.HttpClientRequestAdapter.ThrowIfFailedResponse...

in case the OpenAPI document provides a description per status code.
The current behavior requires an extension for pretty error messages.

Before putting some tools to work to implement this for other languages, first I'd like to check whether this feature design fits the project.

It adds a constructor with a message to error classes and adds the description from the document to the error map:

            var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
            {
                { "400", (parseNode) => global::ApiSdk.Models.ValidationProblemDetails.CreateFromDiscriminatorValueWithMessage(parseNode, "400 Invalid hash format or hash mismatch") },
                { "401", (parseNode) => global::ApiSdk.Models.ProblemDetails.CreateFromDiscriminatorValueWithMessage(parseNode, "401 Missing or invalid client certificate") },
                { "403", (parseNode) => global::ApiSdk.Models.ProblemDetails.CreateFromDiscriminatorValueWithMessage(parseNode, "403 Client certificate does not have required 'Write' role") },
                { "409", (parseNode) => global::ApiSdk.Models.ProblemDetails.CreateFromDiscriminatorValueWithMessage(parseNode, "409 Upload for this hash already in progress, please retry") },
            };

The existing extension message logic is still intact, so this is used as fallback only.

@koen-lee koen-lee requested a review from a team as a code owner September 25, 2025 12:07
Copy link
Member

@baywet baywet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution!

Comment on lines 351 to 352
errorMappings.TryAdd(errorCode, type);
if (!string.IsNullOrEmpty(description))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should the try add result be used in the condition here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand it, it is quite a corner case where consumer expectations are hard to guess - for this to matter, you'd need duplicate error code mappings where one has a description and the other hasn't.
But you have seen more OpenAPI specs than I so if you say this actually happens and the current behavior is unexpected, then the answer is yes :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually I didn't notice but we're duplicating a lot of code with the existing method.
Please:

  • add the description parameter as an optional parameter to the existing method
  • move the condition to the existing method
  • update the condition to consider the try add, so we don't end up in a weird state where we have a description for an error type that doesn't exist
  • remove this new method
  • Update the GetErrorDescriptionMethod to always check whether a matching mapping is present before returning anything (same principle, safety first)

koen-lee and others added 2 commits October 2, 2025 11:46
@koen-lee
Copy link
Author

koen-lee commented Oct 2, 2025

@baywet thanks for the review; I think all your points have been addressed.
We could also add a CodeMethodKind ConstructorWithMessage but I see little value in that.

Is it OK to proceed with other languages in the same manner or is there still room for improvement?

// Then
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)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should test on the new kind that was introduced (multiple instances)

}
// For error classes with message constructor, pass the message to base constructor
else if (isConstructor && parentClass.IsErrorDefinition &&
currentMethod.Parameters.Any(p => p.IsOfKind(CodeParameterKind.ErrorMessage)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
currentMethod.Parameters.Any(p => p.IsOfKind(CodeParameterKind.ErrorMessage)))
currentMethod.Parameters.Any(static p => p.IsOfKind(CodeParameterKind.ErrorMessage)))

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's probably a static analyser trick I'm missing here that would warn contributors about lambdas that can be static?

WriteFactoryMethodBodyForIntersectionModel(codeElement, parentClass, parseNodeParameter, writer);
else if (codeElement.IsOfKind(CodeMethodKind.FactoryWithErrorMessage))
{
var messageParam = codeElement.Parameters.FirstOrDefault(p => p.IsOfKind(CodeParameterKind.ErrorMessage)) ?? throw new InvalidOperationException($"FactoryWithErrorMessage should have a message parameter");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var messageParam = codeElement.Parameters.FirstOrDefault(p => p.IsOfKind(CodeParameterKind.ErrorMessage)) ?? throw new InvalidOperationException($"FactoryWithErrorMessage should have a message parameter");
var messageParam = codeElement.Parameters.FirstOrDefault(static p => p.IsOfKind(CodeParameterKind.ErrorMessage)) ?? throw new InvalidOperationException($"FactoryWithErrorMessage should have a message parameter");

Comment on lines 351 to 352
errorMappings.TryAdd(errorCode, type);
if (!string.IsNullOrEmpty(description))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually I didn't notice but we're duplicating a lot of code with the existing method.
Please:

  • add the description parameter as an optional parameter to the existing method
  • move the condition to the existing method
  • update the condition to consider the try add, so we don't end up in a weird state where we have a description for an error type that doesn't exist
  • remove this new method
  • Update the GetErrorDescriptionMethod to always check whether a matching mapping is present before returning anything (same principle, safety first)

}

// Add message constructor if not already present
if (!codeClass.Methods.Any(static x => x.IsOfKind(CodeMethodKind.Constructor) && x.Parameters.Any(static p => "string".Equals(p.Type.Name, StringComparison.OrdinalIgnoreCase))))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should filter on the new kind instead of the name

if (errorMapping.Value.AllTypes.FirstOrDefault()?.TypeDefinition is CodeClass { IsErrorDefinition: true } errorClass)
{
var errorDescription = codeElement.GetErrorDescription(errorMapping.Key);
var statusCodeAndDescription = !string.IsNullOrEmpty(errorDescription)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@baywet I'm having second thoughts on this - I think we shouldn't include the status code key here because it may be confusing in the 4XX/5XX case.
So, just the description when provided and the old behavior when not.
The actual specific response status code has value though; #6951 would help to provide that without conflicting.

to avoid confusion with 4xx 5xx  generic codes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants