-
Notifications
You must be signed in to change notification settings - Fork 1
Normalize allOf with single model to aliases #71
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
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
fbbf052
feat: normaliz allOf with single model to aliases
t-unit bde094a
Merge branch 'main' into normalize-composite-with-comment
t-unit c7c1662
Merge branch 'main' into normalize-composite-with-comment
t-unit 4471c73
chore: better align checks
t-unit File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
252 changes: 252 additions & 0 deletions
252
packages/tonik_core/lib/src/transformer/allof_normalizer.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,252 @@ | ||
| import 'package:meta/meta.dart'; | ||
| import 'package:tonik_core/tonik_core.dart'; | ||
|
|
||
| /// Normalizes AllOfModel instances with a single contained model to AliasModel. | ||
| /// | ||
| /// This transformer performs in-place replacement throughout the entire | ||
| /// document, ensuring referential consistency by memoizing transformations. | ||
| @immutable | ||
| class AllOfNormalizer { | ||
| const AllOfNormalizer(); | ||
|
|
||
| /// Normalizes allOf schemas with a single model to type aliases. | ||
| /// | ||
| /// This simplifies patterns like `allOf: [$ref, {description: ...}]` used | ||
| /// by Spotify and others to add descriptions to referenced schemas. | ||
| ApiDocument apply(ApiDocument document) { | ||
| final cache = <Model, Model>{}; | ||
|
|
||
| final transformedModels = <Model>{}; | ||
| for (final model in document.models) { | ||
| transformedModels.add(_transformModel(model, cache)); | ||
| } | ||
| document.models = transformedModels; | ||
|
|
||
| for (final model in document.models) { | ||
| if (model is ClassModel) { | ||
| for (final prop in model.properties) { | ||
| final transformed = cache[prop.model]; | ||
| if (transformed != null && transformed != prop.model) { | ||
| prop.model = transformed; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| for (final response in document.responses) { | ||
| _updateResponseModels(response, cache); | ||
| } | ||
|
|
||
| for (final operation in document.operations) { | ||
| for (final response in operation.responses.values) { | ||
| _updateResponseModels(response, cache); | ||
| } | ||
|
|
||
| final requestBody = operation.requestBody; | ||
| if (requestBody != null) { | ||
| _updateRequestBodyModels(requestBody, cache); | ||
| } | ||
|
|
||
| for (final header in operation.headers) { | ||
| _updateRequestHeaderModel(header, cache); | ||
| } | ||
|
|
||
| for (final param in operation.queryParameters) { | ||
| _updateQueryParameterModel(param, cache); | ||
| } | ||
|
|
||
| for (final param in operation.pathParameters) { | ||
| _updatePathParameterModel(param, cache); | ||
| } | ||
| } | ||
|
|
||
| for (final requestBody in document.requestBodies) { | ||
| _updateRequestBodyModels(requestBody, cache); | ||
| } | ||
|
|
||
| for (final header in document.responseHeaders) { | ||
| _updateResponseHeaderModel(header, cache); | ||
| } | ||
|
|
||
| for (final header in document.requestHeaders) { | ||
| _updateRequestHeaderModel(header, cache); | ||
| } | ||
|
|
||
| for (final param in document.queryParameters) { | ||
| _updateQueryParameterModel(param, cache); | ||
| } | ||
| for (final param in document.pathParameters) { | ||
| _updatePathParameterModel(param, cache); | ||
| } | ||
|
|
||
| return document; | ||
| } | ||
|
|
||
| /// Transforms a model, normalizing single-model AllOfModels to AliasModels. | ||
| Model _transformModel(Model model, Map<Model, Model> cache) { | ||
| if (cache.containsKey(model)) { | ||
| return cache[model]!; | ||
| } | ||
|
|
||
| // Placeholder to handle cycles | ||
| cache[model] = model; | ||
|
|
||
| final Model result; | ||
|
|
||
| if (model is AllOfModel && model.models.length == 1) { | ||
| final containedModel = _transformModel(model.models.first, cache); | ||
| result = AliasModel( | ||
| name: model.name, | ||
| model: containedModel, | ||
| context: model.context, | ||
| description: model.description, | ||
| isDeprecated: model.isDeprecated, | ||
| isNullable: model.isNullable, | ||
| nameOverride: model.nameOverride, | ||
| ); | ||
| } else if (model is AllOfModel) { | ||
| final newModels = <Model>{}; | ||
| for (final m in model.models) { | ||
| newModels.add(_transformModel(m, cache)); | ||
| } | ||
| model.models | ||
| ..clear() | ||
| ..addAll(newModels); | ||
| result = model; | ||
| } else if (model is ClassModel) { | ||
| for (final prop in model.properties) { | ||
| prop.model = _transformModel(prop.model, cache); | ||
| } | ||
| result = model; | ||
| } else if (model is OneOfModel) { | ||
| final newModels = <({String? discriminatorValue, Model model})>{}; | ||
| for (final m in model.models) { | ||
| newModels.add(( | ||
| discriminatorValue: m.discriminatorValue, | ||
| model: _transformModel(m.model, cache), | ||
| )); | ||
| } | ||
| model.models | ||
| ..clear() | ||
| ..addAll(newModels); | ||
| result = model; | ||
| } else if (model is AnyOfModel) { | ||
| final newModels = <({String? discriminatorValue, Model model})>{}; | ||
| for (final m in model.models) { | ||
| newModels.add(( | ||
| discriminatorValue: m.discriminatorValue, | ||
| model: _transformModel(m.model, cache), | ||
| )); | ||
| } | ||
| model.models | ||
| ..clear() | ||
| ..addAll(newModels); | ||
| result = model; | ||
| } else if (model is ListModel) { | ||
| model.content = _transformModel(model.content, cache); | ||
| result = model; | ||
| } else if (model is AliasModel) { | ||
| model.model = _transformModel(model.model, cache); | ||
| result = model; | ||
| } else { | ||
| result = model; | ||
| } | ||
|
|
||
| cache[model] = result; | ||
| return result; | ||
| } | ||
|
|
||
| void _updateResponseModels(Response response, Map<Model, Model> cache) { | ||
| switch (response) { | ||
| case ResponseAlias(): | ||
| _updateResponseModels(response.response, cache); | ||
| case ResponseObject(): | ||
| for (final body in response.bodies) { | ||
| final transformed = cache[body.model]; | ||
| if (transformed != null && transformed != body.model) { | ||
| body.model = transformed; | ||
| } | ||
| } | ||
| for (final header in response.headers.values) { | ||
| _updateResponseHeaderModel(header, cache); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void _updateRequestBodyModels( | ||
| RequestBody requestBody, | ||
| Map<Model, Model> cache, | ||
| ) { | ||
| switch (requestBody) { | ||
| case RequestBodyAlias(): | ||
| _updateRequestBodyModels(requestBody.requestBody, cache); | ||
| case RequestBodyObject(): | ||
| for (final content in requestBody.content) { | ||
| final transformed = cache[content.model]; | ||
| if (transformed != null && transformed != content.model) { | ||
| content.model = transformed; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void _updateResponseHeaderModel( | ||
| ResponseHeader header, | ||
| Map<Model, Model> cache, | ||
| ) { | ||
| switch (header) { | ||
| case ResponseHeaderAlias(): | ||
| _updateResponseHeaderModel(header.header, cache); | ||
| case ResponseHeaderObject(): | ||
| final transformed = cache[header.model]; | ||
| if (transformed != null && transformed != header.model) { | ||
| header.model = transformed; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void _updateRequestHeaderModel( | ||
| RequestHeader header, | ||
| Map<Model, Model> cache, | ||
| ) { | ||
| switch (header) { | ||
| case RequestHeaderAlias(): | ||
| _updateRequestHeaderModel(header.header, cache); | ||
| case RequestHeaderObject(): | ||
| final transformed = cache[header.model]; | ||
| if (transformed != null && transformed != header.model) { | ||
| header.model = transformed; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void _updateQueryParameterModel( | ||
| QueryParameter param, | ||
| Map<Model, Model> cache, | ||
| ) { | ||
| switch (param) { | ||
| case QueryParameterAlias(): | ||
| _updateQueryParameterModel(param.parameter, cache); | ||
| case QueryParameterObject(): | ||
| final transformed = cache[param.model]; | ||
| if (transformed != null && transformed != param.model) { | ||
| param.model = transformed; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void _updatePathParameterModel( | ||
| PathParameter param, | ||
| Map<Model, Model> cache, | ||
| ) { | ||
| switch (param) { | ||
| case PathParameterAlias(): | ||
| _updatePathParameterModel(param.parameter, cache); | ||
| case PathParameterObject(): | ||
| final transformed = cache[param.model]; | ||
| if (transformed != null && transformed != param.model) { | ||
| param.model = transformed; | ||
| } | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.