Issue with Kiota or open api spec file?
I'm using the UPS Rating API open api spec file to generate a client. https://github.com/UPS-API/api-documentation/blob/main/Rating.json
"RateResponse": { "type": "object", "required": [ "Response", "RatedShipment" ], "properties": { "Response": { "$ref": "#/components/schemas/RateResponse_Response" }, "RatedShipment": { "oneOf": [ { "items": { "$ref": "#/components/schemas/RateResponse_RatedShipment" }, "type": "array" }, { "$ref": "#/components/schemas/RateResponse_RatedShipment" } ] } }, "xml": { "name": "RateResponse" }, "description": "Rate Response Container.", "maximum": 1 }
It's creating a class RateResponse with another class inside RatedShipment with two properties that are referencing itself instead of the actual $ref object.
Presumably, there needs to be an 's' at the end of the RatedShipments property to avoid this, is that correct?
RatedShipment is a oneOf that is challenging to support in languages that do not have union types. In order to do this we create a wrapper class that has a property for each of the schemas in the oneOf.
"RatedShipment": {
"oneOf": [
{
"items": {
"$ref": "#/components/schemas/RateResponse_RatedShipment"
},
"type": "array"
},
{
"$ref": "#/components/schemas/RateResponse_RatedShipment"
}
]
The oneOf is for either a single RatedShipment instance or an array of RatedShipment. In the deserialized response object, one of the two properties will be set depending on whether there was one or multiple RatedShipments that are returned.
The $ref is not being used in the wrapper class. It is referencing itself in the generated model. I have to change the namespace for each property because the class names are generated as the same.
Ahh, I see what you are saying. That does seem to be a bug. Thanks for the follow up.
What seems to be throwing the generation off the rails here is that component schema is:
- a union type
- matching the inline schema naming conventions
- used for the endpoint it's the conventions matches
So kiota is having a hard time understanding which is what during the generation and mushing things together. So it's most likely a very narrow edge case here.
What would be interesting is to look at the CodeDOM before refiners are applied to see whether the defect comes from the core generation engine or whether it comes from the union type refiner.
@jreidryuzaki if I give you pointers, is this something you'd like to look into?
@jreidryuzaki if I give you pointers, is this something you'd like to look into?
Absolutely.
Great! You'll probably want to set a breakpoint here as this is where the union type is generated. https://github.com/microsoft/kiota/blob/0feecd579ee7c38bad8e5078053a77c2306cacaf/src/Kiota.Builder/KiotaBuilder.cs#L1816
And another one here as this is where they are converted to wrapper classes. https://github.com/microsoft/kiota/blob/0feecd579ee7c38bad8e5078053a77c2306cacaf/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs#L470C6-L470C6
Lastly, you'll want to update the description path/url here https://github.com/microsoft/kiota/blob/0feecd579ee7c38bad8e5078053a77c2306cacaf/.vscode/launch.json#L55 (if you're using VS instead of vscode, the parameters here should be easy to transcribe to launch parameters)
The thing to be on the lookout is when a class gets added, is it actually being added, or is another one being reused because it has the same name. Let us know if you need more information.
In a case like this, are we expecting the type that implements IComposedTypeWrapper to have the suffix "Wrapper"? Or are we expecting the Targetnamespace to be different than the Targetnamespace of the IComposedTypeWrapper implementation?
The naming conventions follow these guidelines:
- Root reusable schema component: the last segment (split on dots) of the component schema name
- inline schema for a property of a reusable schema: like previous appended with _propertyName suffix
- inline schema root request body: path segment prefix + operation + RequestBody suffix (FooPostRequestBody)
- inline property for an inline request body: same as above, with _PropertyName additional suffix (FooPostRequestBody_Property)
- inline schema root response body: path segment prefix + operation + Response suffix (FooGetResponse)
- inline property for an inline response body: same as above, with _PropertyName additional suffix (FooPostResponse_Property)
Let me know if you have further questions, thanks for looking into this.
From what I can tell the naming conventions are getting followed properly.
I think what's going on is that there is no suffix on the component "RateResponse" and thus the "RatedShipment" property is causing the wrapper class to get named the same as the $ref object for the "RatedShipment" property.
I'm not sure what other solution there is here besides changing the UPS Rating spec file to either
- include a suffix for the root component
- change the name of the $ref class in the RatedShipment property
Actually here is what I think is happening: The union type is defined inline for the property, so it's name will be "ParentType_PropertyName" (RateResponse_RatedShipment). But here we already have a component schema (therefore a class) with that same exact name, and it's kind of mushing things together.
You can probably see that by debugging this code section:
https://github.com/microsoft/kiota/blob/8f5d3b8fcb950bd6606c7ef04c71355a7de7c278/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs#L493
And if you pay attention to the other two blocks, they both have a guard close to add a "Wrapper" suffix when something already exists, but not this one. I think that just adding this guard clause to that block should do the trick. Would you mind looking into this further please?
This block is never reached like you suggested. https://github.com/microsoft/kiota/blob/56c42d05528a8bf7bbd63ca70671e33d199eac7c/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs#L491-L500
This is the only block reached. The condition comparing class names is always false so no Wrapper suffix is added when it gets here. https://github.com/microsoft/kiota/blob/56c42d05528a8bf7bbd63ca70671e33d199eac7c/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs#L509-L513
Should it be hitting the middle block? If so, where should it be creating the namespaces at?
yes, I'm suggesting to replicate it at line 492.
(I just paste the "copy as link" value in)
I edited my comment with a different question. I still don't think we're on the same page, sorry to be confusing!
Forked and narrowed it down here. For my specific case, I was able to get the guard clause to be true only for the RateResponse_RatedShipment. https://github.com/microsoft/kiota/commit/74ca63c626cb17d8821da21072a80be80e3de479 - you may have to change the output path in launchsettings but the url to the UPS Rating API should be current.
Thanks for the continuous investigation on this one.
Why don't you add a unit test case and submit a pull request for it?
One of the things I can see from the approach here is that if you have multiple nested inline types, you'll get multiple _, so the solution should account for that.