Repository navigation
feat(typescript-angular): add opt-in form object dot notation #24984
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| generatorName: typescript-angular | ||
| outputDir: samples/client/others/typescript-angular-v20/builds/query-param-form-dot | ||
| inputSpec: modules/openapi-generator/src/test/resources/3_0/query-param-form.yaml | ||
| templateDir: modules/openapi-generator/src/main/resources/typescript-angular | ||
| additionalProperties: | ||
| ngVersion: 20.0.0 | ||
| npmName: sample-angular-20-0-0-query-param-form-dot | ||
| supportsES6: true | ||
| useDotNotationForFormObjects: true |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -477,6 +477,29 @@ public void testEnumAsConst() throws IOException { | |||||||
| assertThat(fileContents).doesNotContain(" as Type"); | ||||||||
| } | ||||||||
|
|
||||||||
| @Test | ||||||||
| public void testFormObjectDotNotationOption() throws IOException { | ||||||||
| for (Object option : new Object[]{null, false, "false", true, "true"}) { | ||||||||
| File output = Files.createTempDirectory("angular-form-dot").toFile(); | ||||||||
| output.deleteOnExit(); | ||||||||
| CodegenConfigurator configurator = new CodegenConfigurator() | ||||||||
| .setGeneratorName("typescript-angular") | ||||||||
| .setInputSpec("src/test/resources/3_0/query-param-form.yaml") | ||||||||
| .setOutputDir(output.getAbsolutePath()); | ||||||||
| if (option != null) { | ||||||||
| configurator.addAdditionalProperty(TypeScriptAngularClientCodegen.USE_DOT_NOTATION_FOR_FORM_OBJECTS, option); | ||||||||
| } | ||||||||
| new DefaultGenerator().opts(configurator.toClientOptInput()).generate(); | ||||||||
| String service = Files.readString(output.toPath().resolve("api.base.service.ts")); | ||||||||
| if (Boolean.parseBoolean(String.valueOf(option))) { | ||||||||
| assertThat(service).contains("this.addToHttpParams(httpParams, `${key}.${k}`, value[k], paramStyle, explode)"); | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: In the enabled ( Prompt for AI agents
Suggested change
|
||||||||
| } else { | ||||||||
| assertThat(service).contains("this.addToHttpParams(httpParams, k, value[k], paramStyle, explode)"); | ||||||||
| assertThat(service).doesNotContain("`${key}.${k}`"); | ||||||||
| } | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| @Test | ||||||||
| public void testDeepObject() throws IOException { | ||||||||
| // GIVEN | ||||||||
|
|
||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| wwwroot/*.js | ||
| node_modules | ||
| typings | ||
| dist |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| # OpenAPI Generator Ignore | ||
| # Generated by openapi-generator https://github.com/openapitools/openapi-generator | ||
|
|
||
| # Use this file to prevent files from being overwritten by the generator. | ||
| # The patterns follow closely to .gitignore or .dockerignore. | ||
|
|
||
| # As an example, the C# client generator defines ApiClient.cs. | ||
| # You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: | ||
| #ApiClient.cs | ||
|
|
||
| # You can match any string of characters against a directory, file or extension with a single asterisk (*): | ||
| #foo/*/qux | ||
| # The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux | ||
|
|
||
| # You can recursively match patterns against a directory, file or extension with a double asterisk (**): | ||
| #foo/**/qux | ||
| # This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux | ||
|
|
||
| # You can also negate patterns with an exclamation (!). | ||
| # For example, you can ignore all files in a docs folder with the file extension .md: | ||
| #docs/*.md | ||
| # Then explicitly reverse the ignore rule for a single file: | ||
| #!docs/README.md |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| .gitignore | ||
| README.md | ||
| api.base.service.ts | ||
| api.module.ts | ||
| api/api.ts | ||
| api/default.service.ts | ||
| configuration.ts | ||
| encoder.ts | ||
| git_push.sh | ||
| index.ts | ||
| model/filter.ts | ||
| model/item.ts | ||
| model/models.ts | ||
| model/response.ts | ||
| ng-package.json | ||
| package.json | ||
| param.ts | ||
| provide-api.ts | ||
| query.params.ts | ||
| tsconfig.json | ||
| variables.ts |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| 7.26.0-SNAPSHOT |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,207 @@ | ||
| # sample-angular-20-0-0-query-param-form-dot@1.0.0 | ||
|
|
||
| No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) | ||
|
|
||
| The version of the OpenAPI document: 1.0.0 | ||
|
|
||
| ## Building | ||
|
|
||
| To install the required dependencies and to build the typescript sources run: | ||
|
|
||
| ```console | ||
| npm install | ||
| npm run build | ||
| ``` | ||
|
|
||
| ## Publishing | ||
|
|
||
| First build the package then run `npm publish dist` (don't forget to specify the `dist` folder!) | ||
|
|
||
| ## Consuming | ||
|
|
||
| Navigate to the folder of your consuming project and run one of next commands. | ||
|
|
||
| _published:_ | ||
|
|
||
| ```console | ||
| npm install sample-angular-20-0-0-query-param-form-dot@1.0.0 --save | ||
| ``` | ||
|
|
||
| _without publishing (not recommended):_ | ||
|
|
||
| ```console | ||
| npm install PATH_TO_GENERATED_PACKAGE/dist.tgz --save | ||
| ``` | ||
|
|
||
| _It's important to take the tgz file, otherwise you'll get trouble with links on windows_ | ||
|
|
||
| _using `npm link`:_ | ||
|
|
||
| In PATH_TO_GENERATED_PACKAGE/dist: | ||
|
|
||
| ```console | ||
| npm link | ||
| ``` | ||
|
|
||
| In your project: | ||
|
|
||
| ```console | ||
| npm link sample-angular-20-0-0-query-param-form-dot | ||
| ``` | ||
|
|
||
| __Note for Windows users:__ The Angular CLI has troubles to use linked npm packages. | ||
| Please refer to this issue <https://github.com/angular/angular-cli/issues/8284> for a solution / workaround. | ||
| Published packages are not effected by this issue. | ||
|
|
||
| ### General usage | ||
|
|
||
| In your Angular project: | ||
|
|
||
| ```typescript | ||
|
|
||
| import { ApplicationConfig } from '@angular/core'; | ||
| import { provideHttpClient } from '@angular/common/http'; | ||
| import { provideApi } from 'sample-angular-20-0-0-query-param-form-dot'; | ||
|
|
||
| export const appConfig: ApplicationConfig = { | ||
| providers: [ | ||
| // ... | ||
| provideHttpClient(), | ||
| provideApi() | ||
| ], | ||
| }; | ||
| ``` | ||
|
|
||
| **NOTE** | ||
| If you're still using `AppModule` and haven't [migrated](https://angular.dev/reference/migrations/standalone) yet, you can still import an Angular module: | ||
| ```typescript | ||
| import { ApiModule } from 'sample-angular-20-0-0-query-param-form-dot'; | ||
| ``` | ||
|
|
||
| If different from the generated base path, during app bootstrap, you can provide the base path to your service. | ||
|
|
||
| ```typescript | ||
| import { ApplicationConfig } from '@angular/core'; | ||
| import { provideHttpClient } from '@angular/common/http'; | ||
| import { provideApi } from 'sample-angular-20-0-0-query-param-form-dot'; | ||
|
|
||
| export const appConfig: ApplicationConfig = { | ||
| providers: [ | ||
| // ... | ||
| provideHttpClient(), | ||
| provideApi('http://localhost:9999') | ||
| ], | ||
| }; | ||
| ``` | ||
|
|
||
| ```typescript | ||
| // with a custom configuration | ||
| import { ApplicationConfig } from '@angular/core'; | ||
| import { provideHttpClient } from '@angular/common/http'; | ||
| import { provideApi } from 'sample-angular-20-0-0-query-param-form-dot'; | ||
|
|
||
| export const appConfig: ApplicationConfig = { | ||
| providers: [ | ||
| // ... | ||
| provideHttpClient(), | ||
| provideApi({ | ||
| withCredentials: true, | ||
| username: 'user', | ||
| password: 'password' | ||
| }) | ||
| ], | ||
| }; | ||
| ``` | ||
|
|
||
| ```typescript | ||
| // with factory building a custom configuration | ||
| import { ApplicationConfig } from '@angular/core'; | ||
| import { provideHttpClient } from '@angular/common/http'; | ||
| import { provideApi, Configuration } from 'sample-angular-20-0-0-query-param-form-dot'; | ||
|
|
||
| export const appConfig: ApplicationConfig = { | ||
| providers: [ | ||
| // ... | ||
| provideHttpClient(), | ||
| { | ||
| provide: Configuration, | ||
| useFactory: (authService: AuthService) => new Configuration({ | ||
| basePath: 'http://localhost:9999', | ||
| withCredentials: true, | ||
| username: authService.getUsername(), | ||
| password: authService.getPassword(), | ||
| }), | ||
| deps: [AuthService], | ||
| multi: false | ||
| } | ||
| ], | ||
| }; | ||
| ``` | ||
|
|
||
| ### Using multiple OpenAPI files / APIs | ||
|
|
||
| In order to use multiple APIs generated from different OpenAPI files, | ||
| you can create an alias name when importing the modules | ||
| in order to avoid naming conflicts: | ||
|
|
||
| ```typescript | ||
| import { provideApi as provideUserApi } from 'my-user-api-path'; | ||
| import { provideApi as provideAdminApi } from 'my-admin-api-path'; | ||
| import { HttpClientModule } from '@angular/common/http'; | ||
| import { environment } from '../environments/environment'; | ||
|
|
||
| export const appConfig: ApplicationConfig = { | ||
| providers: [ | ||
| // ... | ||
| provideHttpClient(), | ||
| provideUserApi(environment.basePath), | ||
| provideAdminApi(environment.basePath), | ||
| ], | ||
| }; | ||
| ``` | ||
|
|
||
| ### Customizing path parameter encoding | ||
|
|
||
| Without further customization, only [path-parameters][parameter-locations-url] of [style][style-values-url] 'simple' | ||
| and Dates for format 'date-time' are encoded correctly. | ||
|
|
||
| Other styles (e.g. "matrix") are not that easy to encode | ||
| and thus are best delegated to other libraries (e.g.: [@honoluluhenk/http-param-expander]). | ||
|
|
||
| To implement your own parameter encoding (or call another library), | ||
| pass an arrow-function or method-reference to the `encodeParam` property of the Configuration-object | ||
| (see [General Usage](#general-usage) above). | ||
|
|
||
| Example value for use in your Configuration-Provider: | ||
|
|
||
| ```typescript | ||
| new Configuration({ | ||
| encodeParam: (param: Param) => myFancyParamEncoder(param), | ||
| }) | ||
| ``` | ||
|
|
||
| [parameter-locations-url]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-locations | ||
| [style-values-url]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#style-values | ||
| [@honoluluhenk/http-param-expander]: https://www.npmjs.com/package/@honoluluhenk/http-param-expander | ||
|
|
||
| ## Form query object dot notation | ||
|
|
||
| This client was generated with `useDotNotationForFormObjects=true`. For exploded | ||
| form query objects, property names include the parameter name and all parent | ||
| properties: `filter = { name: { contains: "Alice" } }` is serialized as | ||
| `filter.name.contains=Alice`. Primitive arrays and sets retain repeated keys, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: The README advertises nested dot-notation ( Prompt for AI agents |
||
| for example `filter.ids=1&filter.ids=2`. | ||
|
|
||
| This is a server-specific convention, not standard OpenAPI form serialization. | ||
| The option defaults to false and does not affect non-exploded form parameters, | ||
| JSON, deepObject, spaceDelimited or pipeDelimited parameters. | ||
|
|
||
| ## Deep-object query parameters | ||
|
|
||
| For `style: deepObject`, nested objects are serialized using bracket notation, for example | ||
| `filter[name][contains]=Alice`. Arrays and sets use zero-based indices, for example | ||
| `filter[sort][0][field]=name`. Dates are serialized as ISO strings; null and undefined | ||
| values and empty containers are omitted. | ||
|
|
||
| OpenAPI only specifies deepObject serialization for flat objects. This recursive | ||
| encoding is a generator extension and requires a server that accepts bracket notation. | ||
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.
P3: The new dot-notation documentation is only added to README.mustache, which is used only when ngVersion >= 17. Clients generated with pre-v17 templates (README_beforeV17.mustache) still get the
useDotNotationForFormObjectsbehavior from the shared api.base.service.mustache, but their README.md never documents it. Add the equivalent section to README_beforeV17.mustache so both template variants document the option.Prompt for AI agents