-
-
Notifications
You must be signed in to change notification settings - Fork 7k
Dart codegen bug fixes: honor prefix/suffix, handle operation return types, replaces Object with Map #12574
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
…ize modelNamePrefix and Suffix, added apiClientName option to dart
…ize modelNamePrefix and Suffix, added apiClientName option to dart
…ize modelNamePrefix and Suffix, added apiClientName option to dart
…ize modelNamePrefix and Suffix, added apiClientName option to dart
@@ -31,7 +31,7 @@ public class CodegenOperation { | |||
isRestfulIndex, isRestfulShow, isRestfulCreate, isRestfulUpdate, isRestfulDestroy, | |||
isRestful, isDeprecated, isCallbackRequest, uniqueItems, hasDefaultResponse = false, | |||
hasErrorResponseObject; // if 4xx, 5xx responses have at least one error object defined | |||
public String path, operationId, returnType, returnFormat, httpMethod, returnBaseType, | |||
public String path, operationId, returnType, returnFormat, httpMethod, returnBaseType, returnInnerType, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what extra information does returnInnerType
add ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As an example, when the returnType
is List<Map<String, String>>
then returnBaseType
will be List
but the dart code, to be type safe, will want to call .cast<Map<String, String>>()
. Therefore, we need to expose Map<String, String>
to the template somehow.
In this case, returnInnerType
will contain the string 'Map<String, String>'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why use this instead of just items property?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The generated code in the earlier version was simply wrong. Dart would fail to compile it under certain cases.
To see where it matters, look at the template in dart2
named api.mustache
on line 180. In this PR, you'll see this:
{{#isArray}}
final responseBody = await _decodeBodyBytes(response);
return (await apiClient.deserializeAsync(responseBody, '{{{returnType}}}') as List)
.cast<{{{returnInnerType}}}>()
.{{#uniqueItems}}toSet(){{/uniqueItems}}{{^uniqueItems}}toList(){{/uniqueItems}};
{{/isArray}}
Previously, it said this:
{{#isArray}}
final responseBody = await _decodeBodyBytes(response);
return (await apiClient.deserializeAsync(responseBody, '{{{returnType}}}') as List)
.cast<{{{returnBaseType}}}>()
.{{#uniqueItems}}toSet(){{/uniqueItems}}{{^uniqueItems}}toList(){{/uniqueItems}};
{{/isArray}}
In most cases, that is fine, but consider the following example from the openApi document from the Mattermost project. There is this api endpoint:
"/teams/{team_id}/groups_by_channels": {
"get": {
"tags": [
"groups"
],
"summary": "Get team groups by channels",
"description": "Retrieve the set of groups associated with the channels in the given team grouped by channel.\n\n##### Permissions\nMust have `manage_system` permission or can access only for current user\n\n__Minimum server version__: 5.11\n",
"operationId": "GetGroupsAssociatedToChannelsByTeam",
"parameters": [
{
"name": "team_id",
"in": "path",
"description": "Team GUID",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "page",
"in": "query",
"description": "The page to select.",
"schema": {
"type": "integer",
"default": 0
}
},
{
"name": "per_page",
"in": "query",
"description": "The number of groups per page.",
"schema": {
"type": "integer",
"default": 60
}
},
{
"name": "filter_allow_reference",
"in": "query",
"description": "Boolean which filters in the group entries with the `allow_reference` attribute set.",
"schema": {
"type": "boolean",
"default": false
}
},
{
"name": "paginate",
"in": "query",
"description": "Boolean to determine whether the pagination should be applied or not",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Group list retrieval successful",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/GroupsAssociatedToChannels"
}
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
},
"500": {
"$ref": "#/components/responses/InternalServerError"
},
"501": {
"$ref": "#/components/responses/NotImplemented"
}
}
}
},
In this case, the returnType
will be List<Map<String, List<GroupWithSchemeAdmin>>>
but the returnBaseType
will be Map
which will result in the following Dart code:
return (await apiClient.deserializeAsync(responseBody, 'List<Map<String, List<GroupWithSchemeAdmin>>>') as List)
.cast<Map>()
.toList();
However, this code will not compile because the .cast
operation coerces all list objects to Map
which is the same as Map<dynamic, dynamic>
and is LESS SPECIFIC than Map<String, List<GroupWithSchemeAdmin>>
. The compiler can't guarantee that this cast will be valid and so the code won't compile.
By adding the returnInnerType
variable to the java
class and by changing the mustache template, the new code will look like this:
return (await apiClient.deserializeAsync(responseBody, 'List<Map<String, List<GroupWithSchemeAdmin>>>') as List)
.cast<Map<String, List<GroupWithSchemeAdmin>>>()
.toList();
This code will compile.
Why not use the items property?
It's true that items.dataType
contains the exact same data, but that value is not available to api.mustache
in the context where it is needed.
This value must be exposed in the same context as returnType
.
@@ -122,7 +122,7 @@ class NullableClass { | |||
) | |||
|
|||
|
|||
final List<Object>? arrayNullableProp; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Object
doesn't have to be a Map
, it can also be String
,num
,List
,....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this came from my changes to AbstractDartCodegen.java
line 152 where I specify that openApi generic object
types should map to dart Map
types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is needed because the "Object" type in Dart does not equate to the "object" type in the openApi spec. Rather, openApi generic objects are equivalent to Dart "Map" types and should be treated as such.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
then it would be list<map<string, object?>>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In most cases Dart deserializes a JSON object to Map<String, dynamic>
so it could be List<Map<String, dynamic>>
to be specific, but List<Map>
should be fine. Remember, an object of type List<Map>
can contain items of Map<String, dynamic>
but an object of type List<Map<String, dynamic>>
cannot contain items of type Map
. Also remember that Map
is the same as Map<dynamic, dynamic>
.
"maven.pomfile.autoUpdateEffectivePOM": true, | ||
"workspace.isHidden": false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why are these change necessary ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For some reason, vscode was reformatting the java code to be different from what was already checked into the repo. I didn't want the diffs to look terrible, so I disabled format on save in my workspace, and vscode saved the changes to the committed workspace file.
Are there any other questions? |
@@ -37,6 +37,7 @@ public class DartDioClientOptionsProvider implements OptionsProvider { | |||
public static final String PUB_AUTHOR_VALUE = "Author"; | |||
public static final String PUB_AUTHOR_EMAIL_VALUE = "author@homepage"; | |||
public static final String PUB_HOMEPAGE_VALUE = "Homepage"; | |||
public static final String API_CLIENT_NAME_VALUE = "Homepage"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change allows apiClient
to be used in additionalProperties
which will override the default name for the top level ApiClient
class.
@jeffmikels thanks for the PR. I would suggest breaking down the PR into smaller one for easier review and merge. What about filing a separate for the following change to start with?
|
The code changes were extensive because they required a number of changes to be implemented simultaneously. The respecting of modelNamePrefix, modelNameSuffix, apiNamePrefix, and apiNameSuffix settings was a nearly trivial change. The other changes were more important and the main reason for the PR. |
@jeffmikels Do you have plans to break this in smaller pieces? Otherwise please be so kind and close the PR. |
Is there an ETA on this? I'm running into the issue of the |
I'm hitting the Just doing the |
This pull requests modifies the Dart codegen and templates to accomplish the following:
modelNamePrefix
,modelNameSuffix
,apiNamePrefix
, andapiNameSuffix
settings_
(internally using the variable nameq
)apiClientName
to override the defaultApiClient
CodegenOperation.java
was modified to expose areturnInnerType
field for those operations whose return value is an array or mapping type value.returnInnerType
value to properly handle the return type of operations.Map
as the default type for an otherwise unspecified apiobject
.@jaumard (2018/09)
@josh-burton (2019/12)
@amondnet (2019/12)
@sbu-WBT (2020/12)
@kuhnroyal (2020/12)
@agilob (2020/12)
@ahmednfwela (2021/08)
PR checklist
This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
These must match the expectations made by your contribution.
You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example
./bin/generate-samples.sh bin/configs/java*
.For Windows users, please run the script in Git BASH.
master
(6.0.1) (patch release),6.1.x
(breaking changes with fallbacks),7.0.x
(breaking changes without fallbacks)