├── version.json ├── src ├── index.ts ├── transformers │ ├── index.ts │ ├── singleton-unions.ts │ ├── array-union-reducer.ts │ ├── null-in-union.ts │ ├── util.ts │ ├── null-in-type-array.ts │ └── union-duplicates.ts ├── code.ts ├── allowlist.ts └── tojson.ts ├── .github ├── pull_request_template.md └── workflows │ ├── retry-automerge.yml │ ├── auto-queue.yml │ ├── auto-approve.yml │ ├── pull-request-lint.yml │ ├── build.yml │ ├── upgrade-main.yml │ ├── upgrade-dev-deps-main.yml │ ├── upgrade-cdklabs-projen-project-types-main.yml │ └── release.yml ├── .npmignore ├── test ├── code.test.ts ├── integ.test.ts ├── type-generator-for-struct.test.ts ├── util.ts ├── usage.test.ts ├── which.ts ├── bindings.test.ts ├── type-generator.naming.test.ts ├── __snapshots__ │ ├── usage.test.ts.snap │ ├── bindings.test.ts.snap │ └── tojson.test.ts.snap ├── union-duplicates.test.ts ├── fixtures │ ├── datadog.json │ ├── aqua-enterprise-enforcer.json │ └── eks.json ├── tojson.test.ts └── type-generator.test.ts ├── .projen ├── files.json ├── deps.json └── tasks.json ├── tsconfig.json ├── tsconfig.dev.json ├── CONTRIBUTING.md ├── .gitattributes ├── .projenrc.ts ├── .gitignore ├── package.json ├── .eslintrc.json ├── README.md ├── LICENSE └── CHANGELOG.md /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.217" 3 | } 4 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './type-generator'; 2 | export * from './code'; 3 | -------------------------------------------------------------------------------- /src/transformers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './null-in-type-array'; 2 | export * from './null-in-union'; 3 | export * from './union-duplicates'; 4 | export * from './singleton-unions'; 5 | export * from './array-union-reducer'; 6 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Fixes # 2 | 3 | --- 4 | 5 | ### Ask Yourself 6 | 7 | - [ ] Have you reviewed the [contribution guide](https://github.com/cdklabs/json2jsii/blob/main/CONTRIBUTING.md)? 8 | - [ ] Have you reviewed the [breaking changes guide](https://github.com/cdklabs/json2jsii/blob/main/CONTRIBUTING.md#breaking-changes)? 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | /.projen/ 3 | /test-reports/ 4 | junit.xml 5 | /coverage/ 6 | permissions-backup.acl 7 | /dist/changelog.md 8 | /dist/version.txt 9 | /test/ 10 | /tsconfig.dev.json 11 | /src/ 12 | !/lib/ 13 | !/lib/**/*.js 14 | !/lib/**/*.d.ts 15 | dist 16 | /tsconfig.json 17 | /.github/ 18 | /.vscode/ 19 | /.idea/ 20 | /.projenrc.js 21 | tsconfig.tsbuildinfo 22 | /.eslintrc.json 23 | /.gitattributes 24 | /.projenrc.ts 25 | /projenrc 26 | -------------------------------------------------------------------------------- /test/code.test.ts: -------------------------------------------------------------------------------- 1 | import { Code } from '../src/code'; 2 | 3 | test('empty newlines are not indented', () => { 4 | // GIVEN 5 | const code = new Code(); 6 | 7 | // WHEN 8 | code.openBlock('open'); 9 | code.line('before empty line'); 10 | code.line(); 11 | code.line('after empty line'); 12 | code.closeBlock(); 13 | 14 | // THEN 15 | expect(code.render()).toMatchInlineSnapshot(` 16 | "open { 17 | before empty line 18 | 19 | after empty line 20 | }" 21 | `); 22 | }); 23 | -------------------------------------------------------------------------------- /.github/workflows/retry-automerge.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: retry-automerge 4 | on: 5 | pull_request: 6 | types: 7 | - auto_merge_disabled 8 | jobs: 9 | retry-automerge: 10 | runs-on: ubuntu-latest 11 | permissions: {} 12 | steps: 13 | - name: Print github context 14 | env: 15 | GITHUB_CONTEXT: ${{ toJson(github) }} 16 | run: echo "$GITHUB_CONTEXT" 17 | - name: Print github event file 18 | run: jq . "$GITHUB_EVENT_PATH" 19 | -------------------------------------------------------------------------------- /test/integ.test.ts: -------------------------------------------------------------------------------- 1 | import { readdirSync, readFileSync } from 'fs'; 2 | import * as path from 'path'; 3 | import { generate } from './util'; 4 | import { TypeGenerator } from '../src'; 5 | 6 | jest.setTimeout(1000 * 60 * 5); 7 | 8 | test.each(readdirSync(path.join(__dirname, 'fixtures')))('generate struct for %s', async file => { 9 | const schema = JSON.parse(readFileSync(path.join(__dirname, 'fixtures', file), 'utf-8')); 10 | const gen = TypeGenerator.forStruct('MyStruct', schema); 11 | const source = await generate(gen); 12 | expect(source).toMatchSnapshot(); 13 | }); 14 | -------------------------------------------------------------------------------- /.github/workflows/auto-queue.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: auto-queue 4 | on: 5 | pull_request_target: 6 | types: 7 | - opened 8 | - reopened 9 | - ready_for_review 10 | jobs: 11 | enableAutoQueue: 12 | name: "Set AutoQueue on PR #${{ github.event.number }}" 13 | runs-on: ubuntu-latest 14 | permissions: 15 | pull-requests: write 16 | contents: write 17 | steps: 18 | - uses: peter-evans/enable-pull-request-automerge@v3 19 | with: 20 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 21 | pull-request-number: ${{ github.event.number }} 22 | merge-method: squash 23 | -------------------------------------------------------------------------------- /.github/workflows/auto-approve.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: auto-approve 4 | on: 5 | pull_request_target: 6 | types: 7 | - labeled 8 | - opened 9 | - synchronize 10 | - reopened 11 | - ready_for_review 12 | jobs: 13 | approve: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | pull-requests: write 17 | if: contains(github.event.pull_request.labels.*.name, 'auto-approve') && (github.event.pull_request.user.login == 'cdklabs-automation' || github.event.pull_request.user.login == 'dependabot[bot]') 18 | steps: 19 | - uses: hmarr/auto-approve-action@f0939ea97e9205ef24d872e76833fa908a770363 20 | with: 21 | github-token: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /src/code.ts: -------------------------------------------------------------------------------- 1 | export class Code { 2 | public readonly lines = new Array(); 3 | private indent = 0; 4 | 5 | public open(text: string = '') { 6 | this.line(text); 7 | this.indent += 2; 8 | } 9 | 10 | public close(text: string = '') { 11 | this.indent -= 2; 12 | this.line(text); 13 | } 14 | 15 | public openBlock(text: string) { 16 | this.open(`${text} {`); 17 | } 18 | 19 | public closeBlock() { 20 | this.close('}'); 21 | } 22 | 23 | public line(text: string = ''): void { 24 | const line = text.length > 0 25 | ? ' '.repeat(this.indent) + text // only indent if line has content 26 | : text; 27 | this.lines.push(line.trimEnd()); 28 | } 29 | 30 | public render(): string { 31 | return this.lines.join('\n'); 32 | } 33 | } -------------------------------------------------------------------------------- /test/type-generator-for-struct.test.ts: -------------------------------------------------------------------------------- 1 | import { JSONSchema4 } from 'json-schema'; 2 | import { TypeGenerator } from '../src'; 3 | 4 | describe('TypeGenerator.forStruct', () => { 5 | test('supports $defs references', () => { 6 | const schema: JSONSchema4 = { 7 | type: 'object', 8 | properties: { 9 | other: { 10 | $ref: '#/$defs/Other', 11 | }, 12 | }, 13 | $defs: { 14 | Other: { 15 | type: 'object', 16 | properties: { 17 | stringValue: { type: 'string' }, 18 | }, 19 | required: ['stringValue'], 20 | }, 21 | }, 22 | }; 23 | 24 | const gen = TypeGenerator.forStruct('TestType', schema); 25 | expect(() => gen.render()).not.toThrow(); 26 | }); 27 | }); 28 | 29 | -------------------------------------------------------------------------------- /.github/workflows/pull-request-lint.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: pull-request-lint 4 | on: 5 | pull_request_target: 6 | types: 7 | - labeled 8 | - opened 9 | - synchronize 10 | - reopened 11 | - ready_for_review 12 | - edited 13 | merge_group: {} 14 | jobs: 15 | validate: 16 | name: Validate PR title 17 | runs-on: ubuntu-latest 18 | permissions: 19 | pull-requests: write 20 | if: (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') 21 | steps: 22 | - uses: amannn/action-semantic-pull-request@v6 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | with: 26 | types: |- 27 | feat 28 | fix 29 | chore 30 | requireScope: false 31 | -------------------------------------------------------------------------------- /src/transformers/singleton-unions.ts: -------------------------------------------------------------------------------- 1 | import { JSONSchema4 } from 'json-schema'; 2 | import { removeUnions, safeSelectUnion } from './util'; 3 | 4 | /** 5 | * Schemas may define a type as a union of types with one of the types being `null`. 6 | * This is the same as defining an optional type. 7 | */ 8 | export function hoistSingletonUnions(def: JSONSchema4): JSONSchema4 { 9 | // definitions already has a type, can't hoist up 10 | if (def.type) { 11 | return def; 12 | } 13 | 14 | // cannot safely select a union or the union is not a singleton => we can't hoist up 15 | const union = safeSelectUnion(def); 16 | if (!union || union.length > 1) { 17 | return def; 18 | } 19 | 20 | // hoist then the only union element up 21 | const hoisted = union[0]; 22 | removeUnions(def); 23 | delete def.type; 24 | Object.assign(def, hoisted); 25 | return def; 26 | } 27 | -------------------------------------------------------------------------------- /.projen/files.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | ".eslintrc.json", 4 | ".gitattributes", 5 | ".github/pull_request_template.md", 6 | ".github/workflows/auto-approve.yml", 7 | ".github/workflows/auto-queue.yml", 8 | ".github/workflows/build.yml", 9 | ".github/workflows/pull-request-lint.yml", 10 | ".github/workflows/release.yml", 11 | ".github/workflows/retry-automerge.yml", 12 | ".github/workflows/upgrade-cdklabs-projen-project-types-main.yml", 13 | ".github/workflows/upgrade-dev-deps-main.yml", 14 | ".github/workflows/upgrade-main.yml", 15 | ".gitignore", 16 | ".npmignore", 17 | ".projen/deps.json", 18 | ".projen/files.json", 19 | ".projen/tasks.json", 20 | "LICENSE", 21 | "tsconfig.dev.json", 22 | "tsconfig.json" 23 | ], 24 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 25 | } 26 | -------------------------------------------------------------------------------- /test/util.ts: -------------------------------------------------------------------------------- 1 | import { promises as fs } from 'fs'; 2 | import * as os from 'os'; 3 | import * as path from 'path'; 4 | import { srcmak } from 'jsii-srcmak'; 5 | import { TypeGenerator } from '../src'; 6 | 7 | export async function generate(gen: TypeGenerator) { 8 | const source = gen.render(); 9 | const deps = ['@types/node'].map(d => path.dirname(require.resolve(`${d}/package.json`))); 10 | 11 | // check that the output compiles & is jsii-compatible 12 | await mkdtemp(async workdir => { 13 | await fs.writeFile(path.join(workdir, 'index.ts'), source); 14 | await srcmak(workdir, { deps }); 15 | }); 16 | 17 | return source; 18 | } 19 | 20 | async function mkdtemp(closure: (dir: string) => Promise) { 21 | const workdir = await fs.mkdtemp(path.join(os.tmpdir(), 'json2jsii-')); 22 | await closure(workdir); 23 | await fs.rm(workdir, { recursive: true, force: true }); 24 | } 25 | -------------------------------------------------------------------------------- /src/allowlist.ts: -------------------------------------------------------------------------------- 1 | export const NAMED_SYMBOLS = { 2 | EXCLAMATION: '!', 3 | AT_SIGN: '@', 4 | BACKTICK: '`', 5 | AMPERSAND: '&', 6 | APOSTROPHE: '\'', 7 | ASTERISK: '*', 8 | HYPHEN: '-', 9 | UNDERSCORE: '_', 10 | DOLLAR_SIGN: '$', 11 | POUND_SIGN: '#', 12 | TILDE: '~', 13 | PERCENT: '%', 14 | CARAT: '^', 15 | PIPE: '|', 16 | QUESTION_MARK: '?', 17 | COMMA: ',', 18 | PERIOD: '.', 19 | LESS_THAN: '<', 20 | GREATER_THAN: '>', 21 | OPEN_BRACKET: '(', 22 | CLOSE_BRACKET: ')', 23 | OPEN_BRACE: '{', 24 | CLOSE_BRACE: '}', 25 | SEMI_COLON: ';', 26 | QUOTATION_MARK: '"', 27 | FORWARD_SLASH: '/', 28 | BACKWARD_SLASH: '\\', 29 | NOT_EQUALS_TO: '!=', 30 | EQUALS_TO: '==', 31 | DEEP_EQUALS: '===', 32 | EQUAL_TILDE: '=~', 33 | NEGATION_TILDE: '!~', 34 | ROUND_BRACKETS: '()', 35 | CURLY_BRACES: '{}', 36 | ANGLE_BRACKETS: '<>', 37 | GREATER_THAN_EQUAL_TO: '>=', 38 | LESSER_THAN_EQUAL_TO: '<=', 39 | EQUALS: '=', 40 | }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | { 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "lib", 6 | "alwaysStrict": true, 7 | "declaration": true, 8 | "esModuleInterop": true, 9 | "experimentalDecorators": true, 10 | "inlineSourceMap": true, 11 | "inlineSources": true, 12 | "lib": [ 13 | "es2020" 14 | ], 15 | "module": "CommonJS", 16 | "noEmitOnError": false, 17 | "noFallthroughCasesInSwitch": true, 18 | "noImplicitAny": true, 19 | "noImplicitReturns": true, 20 | "noImplicitThis": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "resolveJsonModule": true, 24 | "strict": true, 25 | "strictNullChecks": true, 26 | "strictPropertyInitialization": true, 27 | "stripInternal": true, 28 | "target": "ES2020" 29 | }, 30 | "include": [ 31 | "src/**/*.ts" 32 | ], 33 | "exclude": [] 34 | } 35 | -------------------------------------------------------------------------------- /tsconfig.dev.json: -------------------------------------------------------------------------------- 1 | // ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | { 3 | "compilerOptions": { 4 | "alwaysStrict": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "inlineSourceMap": true, 9 | "inlineSources": true, 10 | "lib": [ 11 | "es2020" 12 | ], 13 | "module": "CommonJS", 14 | "noEmitOnError": false, 15 | "noFallthroughCasesInSwitch": true, 16 | "noImplicitAny": true, 17 | "noImplicitReturns": true, 18 | "noImplicitThis": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "resolveJsonModule": true, 22 | "strict": true, 23 | "strictNullChecks": true, 24 | "strictPropertyInitialization": true, 25 | "stripInternal": true, 26 | "target": "ES2020" 27 | }, 28 | "include": [ 29 | "src/**/*.ts", 30 | "test/**/*.ts", 31 | ".projenrc.ts", 32 | "projenrc/**/*.ts" 33 | ], 34 | "exclude": [ 35 | "node_modules" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /test/usage.test.ts: -------------------------------------------------------------------------------- 1 | import { TypeGenerator } from '../src'; 2 | 3 | test('readme example', () => { 4 | const g = TypeGenerator.forStruct('Person', { 5 | definitions: { 6 | Name: { 7 | description: 'Represents a name of a person', 8 | required: ['FirstName', 'last_name'], 9 | properties: { 10 | FirstName: { 11 | type: 'string', 12 | description: 'The first name of the person', 13 | }, 14 | last_name: { 15 | type: 'string', 16 | description: 'The last name of the person', 17 | }, 18 | }, 19 | }, 20 | }, 21 | required: ['name'], 22 | properties: { 23 | name: { 24 | description: 'The person\'s name', 25 | $ref: '#/definitions/Name', 26 | }, 27 | favorite_color: { 28 | description: 'Favorite color. Default is green', 29 | enum: ['red', 'green', 'blue', 'yellow'], 30 | }, 31 | }, 32 | }); 33 | 34 | expect(g.render()).toMatchSnapshot(); 35 | }); 36 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guide 2 | 3 | ## Breaking Changes 4 | 5 | Consumers of this library use it to generate code which is then exposed to customers. 6 | 7 | > See https://github.com/cdk8s-team/cdk8s-cli 8 | 9 | Therefore, breaking changes in this library will most likely have an affect on the public API of its consumers. This means that it might be impossible for consumers to pull in such changes. 10 | 11 | > Note that this differs from regular libraries, where consumers might have to change the way they interact with the library, but can still preserve their own public API. 12 | 13 | In such cases, said consumers are left behind and can no longer take advantage of further enhancement to this library, even though they might be non breaking. For this reason, we treat breaking changes extra carefully. 14 | 15 | As a rule of thumb, if your PR introduces breaking changes: 16 | 17 | - Is it crucial to change default behavior? Can we make it a configuration option instead? 18 | - If a default behavior change is needed, try to still provide a configuration option that reverts to the existing behavior. -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | * text=auto eol=lf 4 | *.snap linguist-generated 5 | /.eslintrc.json linguist-generated 6 | /.gitattributes linguist-generated 7 | /.github/pull_request_template.md linguist-generated 8 | /.github/workflows/auto-approve.yml linguist-generated 9 | /.github/workflows/auto-queue.yml linguist-generated 10 | /.github/workflows/build.yml linguist-generated 11 | /.github/workflows/pull-request-lint.yml linguist-generated 12 | /.github/workflows/release.yml linguist-generated 13 | /.github/workflows/retry-automerge.yml linguist-generated 14 | /.github/workflows/upgrade-cdklabs-projen-project-types-main.yml linguist-generated 15 | /.github/workflows/upgrade-dev-deps-main.yml linguist-generated 16 | /.github/workflows/upgrade-main.yml linguist-generated 17 | /.gitignore linguist-generated 18 | /.npmignore linguist-generated 19 | /.projen/** linguist-generated 20 | /.projen/deps.json linguist-generated 21 | /.projen/files.json linguist-generated 22 | /.projen/tasks.json linguist-generated 23 | /LICENSE linguist-generated 24 | /package.json linguist-generated 25 | /tsconfig.dev.json linguist-generated 26 | /tsconfig.json linguist-generated 27 | /yarn.lock linguist-generated -------------------------------------------------------------------------------- /.projenrc.ts: -------------------------------------------------------------------------------- 1 | import { CdklabsTypeScriptProject } from 'cdklabs-projen-project-types'; 2 | 3 | const project = new CdklabsTypeScriptProject({ 4 | private: false, 5 | enablePRAutoMerge: true, 6 | name: 'json2jsii', 7 | projenrcTs: true, 8 | setNodeEngineVersion: false, 9 | description: 'Generates jsii structs from JSON schemas', 10 | authorName: 'Elad Ben-Israel', 11 | authorEmail: 'elad.benisrael@gmail.com', 12 | repository: 'https://github.com/cdklabs/json2jsii', 13 | deps: ['json-schema', 'camelcase', 'snake-case'], 14 | devDeps: ['@types/json-schema', 'jsii-srcmak', 'prettier', 'cdklabs-projen-project-types'], 15 | releaseToNpm: true, 16 | defaultReleaseBranch: 'main', 17 | autoApproveUpgrades: true, 18 | pullRequestTemplate: true, 19 | pullRequestTemplateContents: [ 20 | 'Fixes #', 21 | '', 22 | '---', 23 | '', 24 | '### Ask Yourself', 25 | '', 26 | '- [ ] Have you reviewed the [contribution guide](https://github.com/cdklabs/json2jsii/blob/main/CONTRIBUTING.md)?', 27 | '- [ ] Have you reviewed the [breaking changes guide](https://github.com/cdklabs/json2jsii/blob/main/CONTRIBUTING.md#breaking-changes)?', 28 | '', 29 | ], 30 | }); 31 | 32 | project.gitignore.exclude('.vscode/'); 33 | 34 | project.synth(); 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | !/.gitattributes 3 | !/.projen/tasks.json 4 | !/.projen/deps.json 5 | !/.projen/files.json 6 | !/.github/workflows/pull-request-lint.yml 7 | !/.github/workflows/auto-approve.yml 8 | !/package.json 9 | !/LICENSE 10 | !/.npmignore 11 | logs 12 | *.log 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | lerna-debug.log* 17 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | lib-cov 23 | coverage 24 | *.lcov 25 | .nyc_output 26 | build/Release 27 | node_modules/ 28 | jspm_packages/ 29 | *.tsbuildinfo 30 | .eslintcache 31 | *.tgz 32 | .yarn-integrity 33 | .cache 34 | /test-reports/ 35 | junit.xml 36 | /coverage/ 37 | !/.github/workflows/build.yml 38 | /dist/changelog.md 39 | /dist/version.txt 40 | !/.github/workflows/release.yml 41 | !/.github/pull_request_template.md 42 | !/test/ 43 | !/tsconfig.json 44 | !/tsconfig.dev.json 45 | !/src/ 46 | /lib 47 | /dist/ 48 | !/.eslintrc.json 49 | !/.github/workflows/retry-automerge.yml 50 | !/.github/workflows/auto-queue.yml 51 | !/.github/workflows/upgrade-cdklabs-projen-project-types-main.yml 52 | !/.github/workflows/upgrade-main.yml 53 | !/.github/workflows/upgrade-dev-deps-main.yml 54 | .vscode/ 55 | !/.projenrc.ts 56 | -------------------------------------------------------------------------------- /src/transformers/array-union-reducer.ts: -------------------------------------------------------------------------------- 1 | import { JSONSchema4 } from 'json-schema'; 2 | import { removeUnions, safeSelectUnion } from './util'; 3 | 4 | /** 5 | * When a schema defines a union between an array type and its element type, 6 | * we can simplify it to just the array type since in jsii this would be 'any' anyway. 7 | * For example: string | string[] becomes just string[] 8 | */ 9 | export function reduceArrayUnions(def: JSONSchema4): JSONSchema4 { 10 | // bail if we cannot safely select a union or the union is not a tuple 11 | const union = safeSelectUnion(def); 12 | if (!union || union.length !== 2) { 13 | return def; 14 | } 15 | 16 | // Look for a pattern where one type is an array and the other is its element type 17 | const arrayType = union.find(t => t.type === 'array'); 18 | const elementType = union.find(t => t !== arrayType); 19 | 20 | // bail, if we don't match the required pattern 21 | if (!arrayType || !elementType || !arrayType.items) { 22 | return def; 23 | } 24 | 25 | // Check if the element type matches the array items type 26 | if (JSON.stringify(arrayType.items) === JSON.stringify(elementType)) { 27 | // Replace the union with just the array type 28 | removeUnions(def); 29 | delete def.type; 30 | Object.assign(def, arrayType); 31 | } 32 | 33 | return def; 34 | } 35 | -------------------------------------------------------------------------------- /test/which.ts: -------------------------------------------------------------------------------- 1 | import { JSONSchema4 } from 'json-schema'; 2 | import { TypeGenerator, TypeGeneratorOptions } from '../src'; 3 | import { generate } from './util'; 4 | 5 | type WhichParams = Parameters<(name: string, schema: JSONSchema4, options?: TypeGeneratorOptions) => void>; 6 | 7 | interface WhichFunction { 8 | (...args: WhichParams): void; 9 | usingTransforms: (...transforms: string[]) => (...args: WhichParams) => void; 10 | } 11 | 12 | export const which: WhichFunction = (name: string, schema: JSONSchema4, options?: TypeGeneratorOptions) => { 13 | test(name, async () => { 14 | const gen = new TypeGenerator(options); 15 | gen.emitType('TestType', JSON.parse(JSON.stringify(schema)), 'fqn.of.TestType'); 16 | expect(await generate(gen)).toMatchSnapshot(); 17 | }); 18 | }; 19 | 20 | which.usingTransforms = (...transforms: string[]) => { 21 | return (...args: WhichParams) => { 22 | describe(args[0], () => { 23 | for (const transform of transforms) { 24 | describe(transform, () => { 25 | which('enabled', args[1], { 26 | ...args[2], 27 | transformations: { 28 | [transform]: true, 29 | }, 30 | }); 31 | which('disabled', args[1], { 32 | ...args[2], 33 | transformations: { 34 | [transform]: false, 35 | }, 36 | }); 37 | }); 38 | } 39 | }); 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /test/bindings.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as os from 'os'; 3 | import * as path from 'path'; 4 | import { srcmak } from 'jsii-srcmak'; 5 | import { TypeGenerator } from '../src'; 6 | 7 | jest.setTimeout(5 * 60 * 1000); 8 | 9 | test('language bindings', async () => { 10 | const g = new TypeGenerator(); 11 | 12 | g.addType('Name', { 13 | properties: { 14 | first: { type: 'string' }, 15 | middle: { type: 'string' }, 16 | last: { type: 'string' }, 17 | }, 18 | required: ['first', 'last'], 19 | }); 20 | 21 | const workdir = fs.mkdtempSync(path.join(os.tmpdir(), 'json2jsii')); 22 | 23 | const src = path.join(workdir, 'tyepscript'); 24 | fs.mkdirSync(src); 25 | fs.writeFileSync(path.join(src, 'index.ts'), await g.render()); 26 | 27 | await srcmak(src, { 28 | java: { 29 | outdir: path.join(workdir, 'java'), 30 | package: 'org.myorg', 31 | }, 32 | python: { 33 | outdir: path.join(workdir, 'python'), 34 | moduleName: 'myorg', 35 | }, 36 | }); 37 | 38 | expect(readFile(path.join(workdir, 'python/myorg/__init__.py'))).toMatchSnapshot(); 39 | expect(readFile(path.join(workdir, 'java/src/main/java/org/myorg/Name.java'), ['@javax.annotation.Generated'])).toMatchSnapshot(); 40 | }); 41 | 42 | function readFile(filePath: string, ignoreLines: string[] = []) { 43 | const lines = (fs.readFileSync(filePath, 'utf-8')).split('\n'); 44 | const shouldInclude = (line: string) => !ignoreLines.find(pattern => line.includes(pattern)); 45 | return lines.filter(shouldInclude).join('\n'); 46 | } -------------------------------------------------------------------------------- /src/transformers/null-in-union.ts: -------------------------------------------------------------------------------- 1 | import { JSONSchema4 } from 'json-schema'; 2 | 3 | /** 4 | * Schemas may define a type as a union of types with one of the types being `null`. 5 | * This is the same as defining an optional type. 6 | */ 7 | function removeNull(def: JSONSchema4, combinator: 'anyOf' | 'oneOf'): JSONSchema4 { 8 | const union = def[combinator]; 9 | if (!(union && Array.isArray(union))) { 10 | return def; 11 | } 12 | 13 | const nullType = union.some(t => t.type === 'null'); 14 | const nonNullTypes = new Set(union.filter(t => t.type !== 'null')); 15 | 16 | if (nullType) { 17 | def.required = false; 18 | } 19 | 20 | if (nonNullTypes.size === 0) { 21 | def[combinator] = [{ type: 'null' }]; 22 | } else { 23 | // if its a union of non null types we just drop null 24 | def[combinator] = Array.from(nonNullTypes); 25 | } 26 | 27 | return def; 28 | } 29 | 30 | 31 | /** 32 | * Many schemas define a type as an array of types to indicate union types. 33 | * To avoid having the type generator be aware of that, we transform those types 34 | * into their corresponding typescript definitions. 35 | * 36 | * -------------------------------------------------- 37 | * 38 | * The null union can be in one of these three fields: type, anyOf or oneOf 39 | */ 40 | export function reduceNullFromUnions(def: JSONSchema4): JSONSchema4 { 41 | const transformers: Array<(definition: JSONSchema4) => JSONSchema4> = [ 42 | (d: JSONSchema4) => removeNull(d, 'anyOf'), 43 | (d: JSONSchema4) => removeNull(d, 'oneOf'), 44 | ]; 45 | 46 | return transformers.reduce((input, transform) => transform(input), def); 47 | } 48 | -------------------------------------------------------------------------------- /src/transformers/util.ts: -------------------------------------------------------------------------------- 1 | import { JSONSchema4 } from 'json-schema'; 2 | 3 | type Union = 'anyOf' | 'oneOf' | 'allOf'; 4 | 5 | /** 6 | * Safely selects a union type if exactly one union type is defined. 7 | * 8 | * @returns the selected union type or undefined if no union type is defined or if more than one union type is defined 9 | */ 10 | export function safeSelectUnion(def: JSONSchema4, allow: Union[] = ['anyOf', 'oneOf', 'allOf']): JSONSchema4[] | undefined { 11 | // Ensure we have exactly one of the possible unions, otherwise behavior is undefined 12 | const union = exactlyOneUnion(def, allow); 13 | if (!union) { 14 | return undefined; 15 | } 16 | 17 | // select the union in question 18 | const unionDef = def[union!]; 19 | 20 | // if the union is not an array something is wrong 21 | if (!Array.isArray(unionDef)) { 22 | return undefined; 23 | } 24 | 25 | return unionDef; 26 | } 27 | 28 | /** 29 | * Returns the name of the union type if exactly one union type is defined. 30 | */ 31 | export function exactlyOneUnion(def: JSONSchema4, allow: Union[] = ['anyOf', 'oneOf', 'allOf']): Union | undefined { 32 | // check we have exactly one of the allowed union types 33 | const count = allow.reduce((sum, union) => sum + (def[union] ? 1 : 0), 0); 34 | if (count !== 1) { 35 | return undefined; 36 | } 37 | 38 | // find the name of the union we have and return 39 | return allow.find((union) => Boolean(def[union])); 40 | } 41 | 42 | /** 43 | * Removes any of the union types from the schema. 44 | */ 45 | export function removeUnions(def: JSONSchema4, allow: Union[] = ['anyOf', 'oneOf', 'allOf']): JSONSchema4 { 46 | allow.forEach((union) => delete def[union]); 47 | return def; 48 | } 49 | -------------------------------------------------------------------------------- /src/transformers/null-in-type-array.ts: -------------------------------------------------------------------------------- 1 | import { JSONSchema4 } from 'json-schema'; 2 | 3 | /** 4 | * Many schemas define a type as an array of types to indicate union types. 5 | * To avoid having the type generator be aware of that, we transform those types 6 | * into their corresponding typescript definitions. 7 | * 8 | * -------------------------------------------------- 9 | * 10 | * Strictly speaking, these definitions are meant to allow the literal 'null' value 11 | * to be used in addition to the actual types. However, since union types are not supported 12 | * in jsii, allowing this would mean falling back to 'any' and loosing all type safety for such 13 | * properties. Transforming it into a single concrete optional type provides both type safety and 14 | * the option to omit the property. What it doesn't allow is explicitly passing 'null', which might 15 | * be desired in some cases. For now we prefer type safety over that. 16 | * 17 | * 1. ['null', ''] -> optional '' 18 | * 2. ['null', '', ''] -> optional 'any' 19 | * 20 | * This is the normal jsii conversion, nothing much we can do here. 21 | * 22 | * 3. ['', ''] -> 'any' 23 | */ 24 | export function reduceNullTypeArray(def: JSONSchema4): JSONSchema4 { 25 | if (!Array.isArray(def.type)) { 26 | return def; 27 | } 28 | 29 | const nullType = def.type.some(t => t === 'null'); 30 | const nonNullTypes = new Set(def.type.filter(t => t !== 'null')); 31 | 32 | if (nullType) { 33 | def.required = false; 34 | } 35 | 36 | if (nonNullTypes.size === 0) { 37 | def.type = 'null'; 38 | } else { 39 | // if its a union of non null types we use 'any' to be jsii compliant 40 | def.type = nonNullTypes.size > 1 ? 'any' : nonNullTypes.values().next().value; 41 | } 42 | 43 | return def; 44 | } 45 | -------------------------------------------------------------------------------- /test/type-generator.naming.test.ts: -------------------------------------------------------------------------------- 1 | import { TypeGenerator } from '../src'; 2 | 3 | describe('normalizeTypeName', () => { 4 | test('no normalization needed', () => { 5 | expect(TypeGenerator.normalizeTypeName('Foo')).toEqual('Foo'); 6 | expect(TypeGenerator.normalizeTypeName('FooBar')).toEqual('FooBar'); 7 | expect(TypeGenerator.normalizeTypeName('Implement')).toEqual('Implement'); 8 | }); 9 | 10 | test('TLAs are converted to PascalCase', () => { 11 | expect(TypeGenerator.normalizeTypeName('ICQResource')).toEqual('IcqResource'); 12 | expect(TypeGenerator.normalizeTypeName('IXXXFoo')).toEqual('IxxxFoo'); 13 | expect(TypeGenerator.normalizeTypeName('IXXFoo')).toEqual('IxxFoo'); 14 | expect(TypeGenerator.normalizeTypeName('STARTFooBARZingSOCalEND')).toEqual('StartFooBarZingSoCalEnd'); 15 | expect(TypeGenerator.normalizeTypeName('VPC')).toEqual('Vpc'); 16 | expect(TypeGenerator.normalizeTypeName('StorageIO')).toEqual('StorageIo'); 17 | expect(TypeGenerator.normalizeTypeName('AFoo')).toEqual('AFoo'); 18 | }); 19 | 20 | test('Lowercase words are converted to PascalCase', () => { 21 | expect(TypeGenerator.normalizeTypeName('attributemanifests')).toEqual('Attributemanifests'); 22 | expect(TypeGenerator.normalizeTypeName('attributemanifestsOptions')).toEqual('AttributemanifestsOptions'); 23 | expect(TypeGenerator.normalizeTypeName('attributeMANIFESTSOptions')).toEqual('AttributeManifestsOptions'); 24 | }); 25 | test('Kebab-case names are converted to PascalCase', () => { 26 | expect(TypeGenerator.normalizeTypeName('AttributeManifests')).toEqual('AttributeManifests'); 27 | expect(TypeGenerator.normalizeTypeName('Attribute-Manifests')).toEqual('AttributeManifests'); 28 | expect(TypeGenerator.normalizeTypeName('Attribute-manifests')).toEqual('AttributeManifests'); 29 | expect(TypeGenerator.normalizeTypeName('attribute-manifests')).toEqual('AttributeManifests'); 30 | expect(TypeGenerator.normalizeTypeName('attribute-manifestsOptions')).toEqual('AttributeManifestsOptions'); 31 | expect(TypeGenerator.normalizeTypeName('attribute-MANIFESTSOptions')).toEqual('AttributeManifestsOptions'); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/tojson.ts: -------------------------------------------------------------------------------- 1 | import { Code } from './code'; 2 | 3 | export class ToJsonFunction { 4 | /** 5 | * The name the toJson function for a struct. 6 | */ 7 | public readonly functionName: string; 8 | 9 | private readonly fields: Record = {}; 10 | 11 | constructor(private readonly baseType: string, private readonly internal: boolean = false) { 12 | this.functionName = `toJson_${baseType}`; 13 | } 14 | 15 | /** 16 | * Adds a field to the struct. 17 | * 18 | * @param schemaName The name of the property in the schema ("to") 19 | * @param propertyName The name of the TypeScript property ("from") 20 | * @param toJson A function used to convert a value from JavaScript to schema 21 | * format. This could be `x => x` if no conversion is required. 22 | */ 23 | public addField(schemaName: string, propertyName: string, toJson: ToJson) { 24 | this.fields[schemaName] = toJson(`obj.${propertyName}`); 25 | } 26 | 27 | public emit(code: Code) { 28 | const disabledEslintRules = [ 29 | 'max-len', 30 | '@stylistic/max-len', 31 | 'quote-props', 32 | '@stylistic/quote-props', 33 | ]; 34 | 35 | code.line(); 36 | code.line('/**'); 37 | code.line(` * Converts an object of type '${this.baseType}' to JSON representation.`); 38 | if (this.internal) { 39 | code.line(' * @internal'); 40 | } 41 | code.line(' */'); 42 | code.line(`/* eslint-disable ${disabledEslintRules.join(', ')} */`); 43 | code.openBlock(`export function ${this.functionName}(obj: ${this.baseType} | undefined): Record | undefined`); 44 | code.line('if (obj === undefined) { return undefined; }'); 45 | 46 | code.open('const result = {'); 47 | for (const [k, v] of Object.entries(this.fields)) { 48 | code.line(`'${k}': ${v},`); 49 | } 50 | code.close('};'); 51 | 52 | code.line('// filter undefined values'); 53 | code.line('return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {});'); 54 | 55 | code.closeBlock(); 56 | code.line(`/* eslint-enable ${disabledEslintRules.join(', ')} */`); 57 | } 58 | } 59 | 60 | /** 61 | * A function that converts an expression from JavaScript to schema format. 62 | * 63 | * @example x => x 64 | * @example x => x?.map(y => toJson_Foo(y)) 65 | */ 66 | export type ToJson = (expression: string) => string; 67 | -------------------------------------------------------------------------------- /.projen/deps.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | { 4 | "name": "@stylistic/eslint-plugin", 5 | "version": "^2", 6 | "type": "build" 7 | }, 8 | { 9 | "name": "@types/jest", 10 | "type": "build" 11 | }, 12 | { 13 | "name": "@types/json-schema", 14 | "type": "build" 15 | }, 16 | { 17 | "name": "@types/node", 18 | "version": "^18", 19 | "type": "build" 20 | }, 21 | { 22 | "name": "@typescript-eslint/eslint-plugin", 23 | "version": "^8", 24 | "type": "build" 25 | }, 26 | { 27 | "name": "@typescript-eslint/parser", 28 | "version": "^8", 29 | "type": "build" 30 | }, 31 | { 32 | "name": "cdklabs-projen-project-types", 33 | "type": "build" 34 | }, 35 | { 36 | "name": "commit-and-tag-version", 37 | "version": "^12", 38 | "type": "build" 39 | }, 40 | { 41 | "name": "constructs", 42 | "version": "^10.0.0", 43 | "type": "build" 44 | }, 45 | { 46 | "name": "eslint-import-resolver-typescript", 47 | "type": "build" 48 | }, 49 | { 50 | "name": "eslint-plugin-import", 51 | "type": "build" 52 | }, 53 | { 54 | "name": "eslint", 55 | "version": "^9", 56 | "type": "build" 57 | }, 58 | { 59 | "name": "jest", 60 | "type": "build" 61 | }, 62 | { 63 | "name": "jest-junit", 64 | "version": "^16", 65 | "type": "build" 66 | }, 67 | { 68 | "name": "jsii-srcmak", 69 | "type": "build" 70 | }, 71 | { 72 | "name": "prettier", 73 | "type": "build" 74 | }, 75 | { 76 | "name": "projen", 77 | "type": "build" 78 | }, 79 | { 80 | "name": "ts-jest", 81 | "type": "build" 82 | }, 83 | { 84 | "name": "ts-node", 85 | "type": "build" 86 | }, 87 | { 88 | "name": "typescript", 89 | "type": "build" 90 | }, 91 | { 92 | "name": "camelcase", 93 | "type": "runtime" 94 | }, 95 | { 96 | "name": "json-schema", 97 | "type": "runtime" 98 | }, 99 | { 100 | "name": "snake-case", 101 | "type": "runtime" 102 | } 103 | ], 104 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 105 | } 106 | -------------------------------------------------------------------------------- /test/__snapshots__/usage.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`readme example 1`] = ` 4 | "/** 5 | * @schema Person 6 | */ 7 | export interface Person { 8 | /** 9 | * The person's name 10 | * 11 | * @schema Person#name 12 | */ 13 | readonly name: Name; 14 | 15 | /** 16 | * Favorite color. Default is green 17 | * 18 | * @default green 19 | * @schema Person#favorite_color 20 | */ 21 | readonly favoriteColor?: PersonFavoriteColor; 22 | } 23 | 24 | /** 25 | * Converts an object of type 'Person' to JSON representation. 26 | */ 27 | /* eslint-disable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 28 | export function toJson_Person(obj: Person | undefined): Record | undefined { 29 | if (obj === undefined) { return undefined; } 30 | const result = { 31 | 'name': toJson_Name(obj.name), 32 | 'favorite_color': obj.favoriteColor, 33 | }; 34 | // filter undefined values 35 | return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {}); 36 | } 37 | /* eslint-enable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 38 | 39 | /** 40 | * Represents a name of a person 41 | * 42 | * @schema Name 43 | */ 44 | export interface Name { 45 | /** 46 | * The first name of the person 47 | * 48 | * @schema Name#FirstName 49 | */ 50 | readonly firstName: string; 51 | 52 | /** 53 | * The last name of the person 54 | * 55 | * @schema Name#last_name 56 | */ 57 | readonly lastName: string; 58 | } 59 | 60 | /** 61 | * Converts an object of type 'Name' to JSON representation. 62 | */ 63 | /* eslint-disable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 64 | export function toJson_Name(obj: Name | undefined): Record | undefined { 65 | if (obj === undefined) { return undefined; } 66 | const result = { 67 | 'FirstName': obj.firstName, 68 | 'last_name': obj.lastName, 69 | }; 70 | // filter undefined values 71 | return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {}); 72 | } 73 | /* eslint-enable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 74 | 75 | /** 76 | * Favorite color. Default is green 77 | * 78 | * @default green 79 | * @schema PersonFavoriteColor 80 | */ 81 | export enum PersonFavoriteColor { 82 | /** red */ 83 | RED = \\"red\\", 84 | /** green */ 85 | GREEN = \\"green\\", 86 | /** blue */ 87 | BLUE = \\"blue\\", 88 | /** yellow */ 89 | YELLOW = \\"yellow\\", 90 | } 91 | " 92 | `; 93 | -------------------------------------------------------------------------------- /src/transformers/union-duplicates.ts: -------------------------------------------------------------------------------- 1 | import { JSONSchema4, JSONSchema4Type } from 'json-schema'; 2 | 3 | const PRIMITIVE_TYPES = new Set(['string', 'number', 'integer', 'boolean']); 4 | 5 | /** 6 | * Reduces multiple occurrences of the same type in oneOf/anyOf into a single type. 7 | * Only reduces primitive types and $refs with matching references. 8 | * For enums, combines all enum values into a single enum. 9 | */ 10 | export function reduceDuplicateTypesInUnion(def: JSONSchema4): JSONSchema4 { 11 | // If there's no oneOf or anyOf, return as is 12 | if (!def.oneOf && !def.anyOf) { 13 | return def; 14 | } 15 | 16 | const union = def.oneOf || def.anyOf; 17 | if (!Array.isArray(union)) { 18 | return def; 19 | } 20 | 21 | const reducedUnion: JSONSchema4[] = []; 22 | const typeGroups = new Map(); 23 | 24 | // First pass: Group types by their type property or $ref 25 | for (const item of union) { 26 | // Handle $ref types 27 | if (item.$ref) { 28 | if (!typeGroups.has(item.$ref)) { 29 | typeGroups.set(item.$ref, []); 30 | } 31 | typeGroups.get(item.$ref)!.push(item); 32 | continue; 33 | } 34 | 35 | // Only group primitive types 36 | const type = item.type; 37 | if (!type || (typeof type === 'string' && !PRIMITIVE_TYPES.has(type))) { 38 | reducedUnion.push(item); 39 | continue; 40 | } 41 | 42 | const key = type.toString(); 43 | if (!typeGroups.has(key)) { 44 | typeGroups.set(key, []); 45 | } 46 | typeGroups.get(key)!.push(item); 47 | } 48 | 49 | // For each group, if there are multiple items: 50 | // - For enums: combine all enum values 51 | // - For primitive types or matching $refs: keep only one instance 52 | for (const [_type, items] of typeGroups.entries()) { 53 | if (items.length === 1) { 54 | reducedUnion.push(items[0]); 55 | continue; 56 | } 57 | 58 | // If these are enums, combine their values 59 | if (items.every(item => Array.isArray(item.enum))) { 60 | const combinedEnum = new Set(); 61 | for (const item of items) { 62 | for (const value of item.enum!) { 63 | combinedEnum.add(value); 64 | } 65 | } 66 | reducedUnion.push({ 67 | type: items[0].type, // Use the type from the first item 68 | enum: Array.from(combinedEnum), 69 | }); 70 | } else { 71 | // For primitive types or matching $refs, just keep the first one 72 | reducedUnion.push(items[0]); 73 | } 74 | } 75 | 76 | // Update the original definition 77 | if (def.oneOf) { 78 | def.oneOf = reducedUnion; 79 | } else { 80 | def.anyOf = reducedUnion; 81 | } 82 | 83 | return def; 84 | } 85 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: build 4 | on: 5 | pull_request: {} 6 | workflow_dispatch: {} 7 | merge_group: {} 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | outputs: 14 | self_mutation_happened: ${{ steps.self_mutation.outputs.self_mutation_happened }} 15 | env: 16 | CI: "true" 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v5 20 | with: 21 | ref: ${{ github.event.pull_request.head.ref }} 22 | repository: ${{ github.event.pull_request.head.repo.full_name }} 23 | - name: Setup Node.js 24 | uses: actions/setup-node@v5 25 | with: 26 | node-version: lts/* 27 | - name: Install dependencies 28 | run: yarn install --check-files 29 | - name: build 30 | run: npx projen build 31 | - name: Find mutations 32 | id: self_mutation 33 | run: |- 34 | git add . 35 | git diff --staged --patch --exit-code > repo.patch || echo "self_mutation_happened=true" >> $GITHUB_OUTPUT 36 | shell: bash 37 | working-directory: ./ 38 | - name: Upload patch 39 | if: steps.self_mutation.outputs.self_mutation_happened 40 | uses: actions/upload-artifact@v4.6.2 41 | with: 42 | name: repo.patch 43 | path: repo.patch 44 | overwrite: true 45 | - name: Fail build on mutation 46 | if: steps.self_mutation.outputs.self_mutation_happened 47 | run: |- 48 | echo "::error::Files were changed during build (see build log). If this was triggered from a fork, you will need to update your branch." 49 | cat repo.patch 50 | exit 1 51 | self-mutation: 52 | needs: build 53 | runs-on: ubuntu-latest 54 | permissions: 55 | contents: write 56 | if: always() && needs.build.outputs.self_mutation_happened && !(github.event.pull_request.head.repo.full_name != github.repository) 57 | steps: 58 | - name: Checkout 59 | uses: actions/checkout@v5 60 | with: 61 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 62 | ref: ${{ github.event.pull_request.head.ref }} 63 | repository: ${{ github.event.pull_request.head.repo.full_name }} 64 | - name: Download patch 65 | uses: actions/download-artifact@v5 66 | with: 67 | name: repo.patch 68 | path: ${{ runner.temp }} 69 | - name: Apply patch 70 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 71 | - name: Set git identity 72 | run: |- 73 | git config user.name "github-actions[bot]" 74 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 75 | - name: Push changes 76 | env: 77 | PULL_REQUEST_REF: ${{ github.event.pull_request.head.ref }} 78 | run: |- 79 | git add . 80 | git commit -s -m "chore: self mutation" 81 | git push origin "HEAD:$PULL_REQUEST_REF" 82 | -------------------------------------------------------------------------------- /test/union-duplicates.test.ts: -------------------------------------------------------------------------------- 1 | import { JSONSchema4 } from 'json-schema'; 2 | import { reduceDuplicateTypesInUnion } from '../src/transformers/union-duplicates'; 3 | 4 | describe('reduceDuplicateTypesInUnion', () => { 5 | test('should reduce duplicate primitive types', () => { 6 | const schema: JSONSchema4 = { 7 | oneOf: [ 8 | { type: 'string' }, 9 | { type: 'string' }, 10 | { type: 'number' }, 11 | { type: 'number' }, 12 | { type: 'boolean' }, 13 | ], 14 | }; 15 | 16 | const result = reduceDuplicateTypesInUnion(schema); 17 | expect(result.oneOf).toHaveLength(3); 18 | expect(result.oneOf).toEqual([ 19 | { type: 'string' }, 20 | { type: 'number' }, 21 | { type: 'boolean' }, 22 | ]); 23 | }); 24 | 25 | test('should not reduce non-primitive types', () => { 26 | const schema: JSONSchema4 = { 27 | oneOf: [ 28 | { type: 'object', properties: { foo: { type: 'string' } } }, 29 | { type: 'object', properties: { foo: { type: 'string' } } }, 30 | { type: 'array', items: { type: 'string' } }, 31 | { type: 'array', items: { type: 'string' } }, 32 | ], 33 | }; 34 | 35 | const result = reduceDuplicateTypesInUnion(schema); 36 | expect(result.oneOf).toHaveLength(4); 37 | expect(result.oneOf).toEqual([ 38 | { type: 'object', properties: { foo: { type: 'string' } } }, 39 | { type: 'object', properties: { foo: { type: 'string' } } }, 40 | { type: 'array', items: { type: 'string' } }, 41 | { type: 'array', items: { type: 'string' } }, 42 | ]); 43 | }); 44 | 45 | test('should reduce duplicate $refs', () => { 46 | const schema: JSONSchema4 = { 47 | oneOf: [ 48 | { $ref: '#/definitions/MyType' }, 49 | { $ref: '#/definitions/MyType' }, 50 | { $ref: '#/definitions/OtherType' }, 51 | ], 52 | }; 53 | 54 | const result = reduceDuplicateTypesInUnion(schema); 55 | expect(result.oneOf).toHaveLength(2); 56 | expect(result.oneOf).toEqual([ 57 | { $ref: '#/definitions/MyType' }, 58 | { $ref: '#/definitions/OtherType' }, 59 | ]); 60 | }); 61 | 62 | test('should handle mixed primitive and non-primitive types', () => { 63 | const schema: JSONSchema4 = { 64 | oneOf: [ 65 | { type: 'string' }, 66 | { type: 'string' }, 67 | { type: 'object', properties: { foo: { type: 'string' } } }, 68 | { type: 'object', properties: { foo: { type: 'string' } } }, 69 | ], 70 | }; 71 | 72 | const result = reduceDuplicateTypesInUnion(schema); 73 | expect(result.oneOf).toHaveLength(3); 74 | expect(result.oneOf).toEqual([ 75 | { type: 'object', properties: { foo: { type: 'string' } } }, 76 | { type: 'object', properties: { foo: { type: 'string' } } }, 77 | { type: 'string' }, 78 | ]); 79 | }); 80 | 81 | test('should handle enums correctly', () => { 82 | const schema: JSONSchema4 = { 83 | oneOf: [ 84 | { type: 'string', enum: ['a', 'b'] }, 85 | { type: 'string', enum: ['b', 'c'] }, 86 | { type: 'number', enum: [1, 2] }, 87 | { type: 'number', enum: [2, 3] }, 88 | ], 89 | }; 90 | 91 | const result = reduceDuplicateTypesInUnion(schema); 92 | expect(result.oneOf).toHaveLength(2); 93 | expect(result.oneOf).toEqual([ 94 | { type: 'string', enum: ['a', 'b', 'c'] }, 95 | { type: 'number', enum: [1, 2, 3] }, 96 | ]); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-main 4 | on: 5 | workflow_dispatch: {} 6 | schedule: 7 | - cron: 0 18 * * 1 8 | jobs: 9 | upgrade: 10 | name: Upgrade 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | outputs: 15 | patch_created: ${{ steps.create_patch.outputs.patch_created }} 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v5 19 | with: 20 | ref: main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v5 23 | with: 24 | node-version: lts/* 25 | - name: Install dependencies 26 | run: yarn install --check-files --frozen-lockfile 27 | - name: Upgrade dependencies 28 | run: npx projen upgrade 29 | - name: Find mutations 30 | id: create_patch 31 | run: |- 32 | git add . 33 | git diff --staged --patch --exit-code > repo.patch || echo "patch_created=true" >> $GITHUB_OUTPUT 34 | shell: bash 35 | working-directory: ./ 36 | - name: Upload patch 37 | if: steps.create_patch.outputs.patch_created 38 | uses: actions/upload-artifact@v4.6.2 39 | with: 40 | name: repo.patch 41 | path: repo.patch 42 | overwrite: true 43 | pr: 44 | name: Create Pull Request 45 | needs: upgrade 46 | runs-on: ubuntu-latest 47 | permissions: 48 | contents: read 49 | if: ${{ needs.upgrade.outputs.patch_created }} 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v5 53 | with: 54 | ref: main 55 | - name: Download patch 56 | uses: actions/download-artifact@v5 57 | with: 58 | name: repo.patch 59 | path: ${{ runner.temp }} 60 | - name: Apply patch 61 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 62 | - name: Set git identity 63 | run: |- 64 | git config user.name "github-actions[bot]" 65 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 66 | - name: Create Pull Request 67 | id: create-pr 68 | uses: peter-evans/create-pull-request@v7 69 | with: 70 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 71 | commit-message: |- 72 | fix(deps): upgrade dependencies 73 | 74 | Upgrades project dependencies. See details in [workflow run]. 75 | 76 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 77 | 78 | ------ 79 | 80 | *Automatically created by projen via the "upgrade-main" workflow* 81 | branch: github-actions/upgrade-main 82 | title: "fix(deps): upgrade dependencies" 83 | labels: auto-approve 84 | body: |- 85 | Upgrades project dependencies. See details in [workflow run]. 86 | 87 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 88 | 89 | ------ 90 | 91 | *Automatically created by projen via the "upgrade-main" workflow* 92 | author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 93 | committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 94 | signoff: true 95 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-dev-deps-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-dev-deps-main 4 | on: 5 | workflow_dispatch: {} 6 | schedule: 7 | - cron: 0 22 * * 1 8 | jobs: 9 | upgrade: 10 | name: Upgrade 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | outputs: 15 | patch_created: ${{ steps.create_patch.outputs.patch_created }} 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v5 19 | with: 20 | ref: main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v5 23 | with: 24 | node-version: lts/* 25 | - name: Install dependencies 26 | run: yarn install --check-files --frozen-lockfile 27 | - name: Upgrade dependencies 28 | run: npx projen upgrade-dev-deps 29 | - name: Find mutations 30 | id: create_patch 31 | run: |- 32 | git add . 33 | git diff --staged --patch --exit-code > repo.patch || echo "patch_created=true" >> $GITHUB_OUTPUT 34 | shell: bash 35 | working-directory: ./ 36 | - name: Upload patch 37 | if: steps.create_patch.outputs.patch_created 38 | uses: actions/upload-artifact@v4.6.2 39 | with: 40 | name: repo.patch 41 | path: repo.patch 42 | overwrite: true 43 | pr: 44 | name: Create Pull Request 45 | needs: upgrade 46 | runs-on: ubuntu-latest 47 | permissions: 48 | contents: read 49 | if: ${{ needs.upgrade.outputs.patch_created }} 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v5 53 | with: 54 | ref: main 55 | - name: Download patch 56 | uses: actions/download-artifact@v5 57 | with: 58 | name: repo.patch 59 | path: ${{ runner.temp }} 60 | - name: Apply patch 61 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 62 | - name: Set git identity 63 | run: |- 64 | git config user.name "github-actions[bot]" 65 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 66 | - name: Create Pull Request 67 | id: create-pr 68 | uses: peter-evans/create-pull-request@v7 69 | with: 70 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 71 | commit-message: |- 72 | chore(deps): upgrade dev dependencies 73 | 74 | Upgrades project dependencies. See details in [workflow run]. 75 | 76 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 77 | 78 | ------ 79 | 80 | *Automatically created by projen via the "upgrade-dev-deps-main" workflow* 81 | branch: github-actions/upgrade-dev-deps-main 82 | title: "chore(deps): upgrade dev dependencies" 83 | labels: auto-approve 84 | body: |- 85 | Upgrades project dependencies. See details in [workflow run]. 86 | 87 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 88 | 89 | ------ 90 | 91 | *Automatically created by projen via the "upgrade-dev-deps-main" workflow* 92 | author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 93 | committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 94 | signoff: true 95 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-cdklabs-projen-project-types-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-cdklabs-projen-project-types-main 4 | on: 5 | workflow_dispatch: {} 6 | jobs: 7 | upgrade: 8 | name: Upgrade 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | outputs: 13 | patch_created: ${{ steps.create_patch.outputs.patch_created }} 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v5 17 | with: 18 | ref: main 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v5 21 | - name: Install dependencies 22 | run: yarn install --check-files --frozen-lockfile 23 | - name: Upgrade dependencies 24 | run: npx projen upgrade-cdklabs-projen-project-types 25 | - name: Find mutations 26 | id: create_patch 27 | run: |- 28 | git add . 29 | git diff --staged --patch --exit-code > repo.patch || echo "patch_created=true" >> $GITHUB_OUTPUT 30 | shell: bash 31 | working-directory: ./ 32 | - name: Upload patch 33 | if: steps.create_patch.outputs.patch_created 34 | uses: actions/upload-artifact@v4.6.2 35 | with: 36 | name: repo.patch 37 | path: repo.patch 38 | overwrite: true 39 | pr: 40 | name: Create Pull Request 41 | needs: upgrade 42 | runs-on: ubuntu-latest 43 | permissions: 44 | contents: read 45 | if: ${{ needs.upgrade.outputs.patch_created }} 46 | steps: 47 | - name: Checkout 48 | uses: actions/checkout@v5 49 | with: 50 | ref: main 51 | - name: Download patch 52 | uses: actions/download-artifact@v5 53 | with: 54 | name: repo.patch 55 | path: ${{ runner.temp }} 56 | - name: Apply patch 57 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 58 | - name: Set git identity 59 | run: |- 60 | git config user.name "github-actions[bot]" 61 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 62 | - name: Create Pull Request 63 | id: create-pr 64 | uses: peter-evans/create-pull-request@v7 65 | with: 66 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 67 | commit-message: |- 68 | chore(deps): upgrade cdklabs-projen-project-types 69 | 70 | Upgrades project dependencies. See details in [workflow run]. 71 | 72 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 73 | 74 | ------ 75 | 76 | *Automatically created by projen via the "upgrade-cdklabs-projen-project-types-main" workflow* 77 | branch: github-actions/upgrade-cdklabs-projen-project-types-main 78 | title: "chore(deps): upgrade cdklabs-projen-project-types" 79 | labels: auto-approve 80 | body: |- 81 | Upgrades project dependencies. See details in [workflow run]. 82 | 83 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 84 | 85 | ------ 86 | 87 | *Automatically created by projen via the "upgrade-cdklabs-projen-project-types-main" workflow* 88 | author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 89 | committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 90 | signoff: true 91 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json2jsii", 3 | "description": "Generates jsii structs from JSON schemas", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/cdklabs/json2jsii" 7 | }, 8 | "scripts": { 9 | "build": "npx projen build", 10 | "bump": "npx projen bump", 11 | "clobber": "npx projen clobber", 12 | "compile": "npx projen compile", 13 | "default": "npx projen default", 14 | "eject": "npx projen eject", 15 | "eslint": "npx projen eslint", 16 | "package": "npx projen package", 17 | "post-compile": "npx projen post-compile", 18 | "post-upgrade": "npx projen post-upgrade", 19 | "pre-compile": "npx projen pre-compile", 20 | "release": "npx projen release", 21 | "test": "npx projen test", 22 | "test:watch": "npx projen test:watch", 23 | "unbump": "npx projen unbump", 24 | "upgrade": "npx projen upgrade", 25 | "upgrade-cdklabs-projen-project-types": "npx projen upgrade-cdklabs-projen-project-types", 26 | "upgrade-dev-deps": "npx projen upgrade-dev-deps", 27 | "watch": "npx projen watch", 28 | "projen": "npx projen" 29 | }, 30 | "author": { 31 | "name": "Amazon Web Services", 32 | "email": "aws-cdk-dev@amazon.com", 33 | "organization": true 34 | }, 35 | "devDependencies": { 36 | "@stylistic/eslint-plugin": "^2", 37 | "@types/jest": "^26.0.24", 38 | "@types/json-schema": "^7.0.15", 39 | "@types/node": "^18", 40 | "@typescript-eslint/eslint-plugin": "^8", 41 | "@typescript-eslint/parser": "^8", 42 | "cdklabs-projen-project-types": "^0.3.7", 43 | "commit-and-tag-version": "^12", 44 | "constructs": "^10.0.0", 45 | "eslint": "^9", 46 | "eslint-import-resolver-typescript": "^2.7.1", 47 | "eslint-plugin-import": "^2.32.0", 48 | "jest": "^27", 49 | "jest-junit": "^16", 50 | "jsii-srcmak": "^0.1.1332", 51 | "prettier": "^2.8.8", 52 | "projen": "^0.98.4", 53 | "ts-jest": "^27", 54 | "ts-node": "^10.9.2", 55 | "typescript": "~4.9.5" 56 | }, 57 | "dependencies": { 58 | "camelcase": "^6.3.0", 59 | "json-schema": "^0.4.0", 60 | "snake-case": "^3.0.4" 61 | }, 62 | "main": "lib/index.js", 63 | "license": "Apache-2.0", 64 | "publishConfig": { 65 | "access": "public" 66 | }, 67 | "version": "0.0.0", 68 | "jest": { 69 | "coverageProvider": "v8", 70 | "testMatch": [ 71 | "/@(src|test)/**/*(*.)@(spec|test).ts?(x)", 72 | "/@(src|test)/**/__tests__/**/*.ts?(x)", 73 | "/@(projenrc)/**/*(*.)@(spec|test).ts?(x)", 74 | "/@(projenrc)/**/__tests__/**/*.ts?(x)" 75 | ], 76 | "clearMocks": true, 77 | "collectCoverage": true, 78 | "coverageReporters": [ 79 | "json", 80 | "lcov", 81 | "clover", 82 | "cobertura", 83 | "text" 84 | ], 85 | "coverageDirectory": "coverage", 86 | "coveragePathIgnorePatterns": [ 87 | "/node_modules/" 88 | ], 89 | "testPathIgnorePatterns": [ 90 | "/node_modules/" 91 | ], 92 | "watchPathIgnorePatterns": [ 93 | "/node_modules/" 94 | ], 95 | "reporters": [ 96 | "default", 97 | [ 98 | "jest-junit", 99 | { 100 | "outputDirectory": "test-reports" 101 | } 102 | ] 103 | ], 104 | "preset": "ts-jest", 105 | "globals": { 106 | "ts-jest": { 107 | "tsconfig": "tsconfig.dev.json" 108 | } 109 | } 110 | }, 111 | "types": "lib/index.d.ts", 112 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 113 | } 114 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: release 4 | on: 5 | push: 6 | branches: 7 | - main 8 | workflow_dispatch: {} 9 | concurrency: 10 | group: ${{ github.workflow }} 11 | cancel-in-progress: false 12 | jobs: 13 | release: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: write 17 | outputs: 18 | latest_commit: ${{ steps.git_remote.outputs.latest_commit }} 19 | tag_exists: ${{ steps.check_tag_exists.outputs.exists }} 20 | env: 21 | CI: "true" 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v5 25 | with: 26 | fetch-depth: 0 27 | - name: Set git identity 28 | run: |- 29 | git config user.name "github-actions[bot]" 30 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 31 | - name: Setup Node.js 32 | uses: actions/setup-node@v5 33 | with: 34 | node-version: lts/* 35 | - name: Install dependencies 36 | run: yarn install --check-files --frozen-lockfile 37 | - name: release 38 | run: npx projen release 39 | - name: Check if version has already been tagged 40 | id: check_tag_exists 41 | run: |- 42 | TAG=$(cat dist/releasetag.txt) 43 | ([ ! -z "$TAG" ] && git ls-remote -q --exit-code --tags origin $TAG && (echo "exists=true" >> $GITHUB_OUTPUT)) || (echo "exists=false" >> $GITHUB_OUTPUT) 44 | cat $GITHUB_OUTPUT 45 | - name: Check for new commits 46 | id: git_remote 47 | run: |- 48 | echo "latest_commit=$(git ls-remote origin -h ${{ github.ref }} | cut -f1)" >> $GITHUB_OUTPUT 49 | cat $GITHUB_OUTPUT 50 | shell: bash 51 | - name: Backup artifact permissions 52 | if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} 53 | run: cd dist && getfacl -R . > permissions-backup.acl 54 | continue-on-error: true 55 | - name: Upload artifact 56 | if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} 57 | uses: actions/upload-artifact@v4.6.2 58 | with: 59 | name: build-artifact 60 | path: dist 61 | overwrite: true 62 | release_github: 63 | name: Publish to GitHub Releases 64 | needs: 65 | - release 66 | - release_npm 67 | runs-on: ubuntu-latest 68 | permissions: 69 | contents: write 70 | environment: release 71 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 72 | steps: 73 | - uses: actions/setup-node@v5 74 | with: 75 | node-version: lts/* 76 | - name: Download build artifacts 77 | uses: actions/download-artifact@v5 78 | with: 79 | name: build-artifact 80 | path: dist 81 | - name: Restore build artifact permissions 82 | run: cd dist && setfacl --restore=permissions-backup.acl 83 | continue-on-error: true 84 | - name: Release 85 | env: 86 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 87 | run: errout=$(mktemp); gh release create $(cat dist/releasetag.txt) -R $GITHUB_REPOSITORY -F dist/changelog.md -t $(cat dist/releasetag.txt) --target $GITHUB_SHA 2> $errout && true; exitcode=$?; if [ $exitcode -ne 0 ] && ! grep -q "Release.tag_name already exists" $errout; then cat $errout; exit $exitcode; fi 88 | release_npm: 89 | name: Publish to npm 90 | needs: release 91 | runs-on: ubuntu-latest 92 | permissions: 93 | id-token: write 94 | contents: read 95 | environment: release 96 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 97 | steps: 98 | - uses: actions/setup-node@v5 99 | with: 100 | node-version: 24.x 101 | - name: Download build artifacts 102 | uses: actions/download-artifact@v5 103 | with: 104 | name: build-artifact 105 | path: dist 106 | - name: Restore build artifact permissions 107 | run: cd dist && setfacl --restore=permissions-backup.acl 108 | continue-on-error: true 109 | - name: Release 110 | env: 111 | NPM_DIST_TAG: latest 112 | NPM_REGISTRY: registry.npmjs.org 113 | NPM_CONFIG_PROVENANCE: "true" 114 | NPM_TRUSTED_PUBLISHER: "true" 115 | run: npx -p publib@latest publib-npm 116 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | // ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | { 3 | "env": { 4 | "jest": true, 5 | "node": true 6 | }, 7 | "root": true, 8 | "plugins": [ 9 | "@typescript-eslint", 10 | "import", 11 | "@stylistic" 12 | ], 13 | "parser": "@typescript-eslint/parser", 14 | "parserOptions": { 15 | "ecmaVersion": 2018, 16 | "sourceType": "module", 17 | "project": "./tsconfig.dev.json" 18 | }, 19 | "extends": [ 20 | "plugin:import/typescript" 21 | ], 22 | "settings": { 23 | "import/parsers": { 24 | "@typescript-eslint/parser": [ 25 | ".ts", 26 | ".tsx" 27 | ] 28 | }, 29 | "import/resolver": { 30 | "node": {}, 31 | "typescript": { 32 | "project": "./tsconfig.dev.json", 33 | "alwaysTryTypes": true 34 | } 35 | } 36 | }, 37 | "ignorePatterns": [ 38 | "*.js", 39 | "*.d.ts", 40 | "node_modules/", 41 | "*.generated.ts", 42 | "coverage", 43 | "!.projenrc.ts", 44 | "!projenrc/**/*.ts" 45 | ], 46 | "rules": { 47 | "@stylistic/indent": [ 48 | "error", 49 | 2 50 | ], 51 | "@stylistic/quotes": [ 52 | "error", 53 | "single", 54 | { 55 | "avoidEscape": true 56 | } 57 | ], 58 | "@stylistic/comma-dangle": [ 59 | "error", 60 | "always-multiline" 61 | ], 62 | "@stylistic/comma-spacing": [ 63 | "error", 64 | { 65 | "before": false, 66 | "after": true 67 | } 68 | ], 69 | "@stylistic/no-multi-spaces": [ 70 | "error", 71 | { 72 | "ignoreEOLComments": false 73 | } 74 | ], 75 | "@stylistic/array-bracket-spacing": [ 76 | "error", 77 | "never" 78 | ], 79 | "@stylistic/array-bracket-newline": [ 80 | "error", 81 | "consistent" 82 | ], 83 | "@stylistic/object-curly-spacing": [ 84 | "error", 85 | "always" 86 | ], 87 | "@stylistic/object-curly-newline": [ 88 | "error", 89 | { 90 | "multiline": true, 91 | "consistent": true 92 | } 93 | ], 94 | "@stylistic/object-property-newline": [ 95 | "error", 96 | { 97 | "allowAllPropertiesOnSameLine": true 98 | } 99 | ], 100 | "@stylistic/keyword-spacing": [ 101 | "error" 102 | ], 103 | "@stylistic/brace-style": [ 104 | "error", 105 | "1tbs", 106 | { 107 | "allowSingleLine": true 108 | } 109 | ], 110 | "@stylistic/space-before-blocks": [ 111 | "error" 112 | ], 113 | "@stylistic/member-delimiter-style": [ 114 | "error" 115 | ], 116 | "@stylistic/semi": [ 117 | "error", 118 | "always" 119 | ], 120 | "@stylistic/max-len": [ 121 | "error", 122 | { 123 | "code": 150, 124 | "ignoreUrls": true, 125 | "ignoreStrings": true, 126 | "ignoreTemplateLiterals": true, 127 | "ignoreComments": true, 128 | "ignoreRegExpLiterals": true 129 | } 130 | ], 131 | "@stylistic/quote-props": [ 132 | "error", 133 | "consistent-as-needed" 134 | ], 135 | "@stylistic/key-spacing": [ 136 | "error" 137 | ], 138 | "@stylistic/no-multiple-empty-lines": [ 139 | "error" 140 | ], 141 | "@stylistic/no-trailing-spaces": [ 142 | "error" 143 | ], 144 | "curly": [ 145 | "error", 146 | "multi-line", 147 | "consistent" 148 | ], 149 | "@typescript-eslint/no-require-imports": "error", 150 | "import/no-extraneous-dependencies": [ 151 | "error", 152 | { 153 | "devDependencies": [ 154 | "**/test/**", 155 | "**/build-tools/**", 156 | ".projenrc.ts", 157 | "projenrc/**/*.ts" 158 | ], 159 | "optionalDependencies": false, 160 | "peerDependencies": true 161 | } 162 | ], 163 | "import/no-unresolved": [ 164 | "error" 165 | ], 166 | "import/order": [ 167 | "warn", 168 | { 169 | "groups": [ 170 | "builtin", 171 | "external" 172 | ], 173 | "alphabetize": { 174 | "order": "asc", 175 | "caseInsensitive": true 176 | } 177 | } 178 | ], 179 | "import/no-duplicates": [ 180 | "error" 181 | ], 182 | "no-shadow": [ 183 | "off" 184 | ], 185 | "@typescript-eslint/no-shadow": "error", 186 | "@typescript-eslint/no-floating-promises": "error", 187 | "no-return-await": [ 188 | "off" 189 | ], 190 | "@typescript-eslint/return-await": "error", 191 | "dot-notation": [ 192 | "error" 193 | ], 194 | "no-bitwise": [ 195 | "error" 196 | ], 197 | "@typescript-eslint/member-ordering": [ 198 | "error", 199 | { 200 | "default": [ 201 | "public-static-field", 202 | "public-static-method", 203 | "protected-static-field", 204 | "protected-static-method", 205 | "private-static-field", 206 | "private-static-method", 207 | "field", 208 | "constructor", 209 | "method" 210 | ] 211 | } 212 | ] 213 | }, 214 | "overrides": [ 215 | { 216 | "files": [ 217 | ".projenrc.ts" 218 | ], 219 | "rules": { 220 | "@typescript-eslint/no-require-imports": "off", 221 | "import/no-extraneous-dependencies": "off" 222 | } 223 | } 224 | ] 225 | } 226 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # json2jsii 2 | 3 | > Generates jsii-compatible structs from JSON schemas 4 | 5 | ## Usage 6 | 7 | ```ts 8 | const g = TypeGenerator.forStruct('Person', { 9 | definitions: { 10 | Name: { 11 | description: 'Represents a name of a person', 12 | required: ['FirstName', 'last_name'], 13 | properties: { 14 | FirstName: { 15 | type: 'string', 16 | description: 'The first name of the person', 17 | }, 18 | last_name: { 19 | type: 'string', 20 | description: 'The last name of the person', 21 | }, 22 | }, 23 | }, 24 | }, 25 | required: ['name'], 26 | properties: { 27 | name: { 28 | description: 'The person\'s name', 29 | $ref: '#/definitions/Name', 30 | }, 31 | favorite_color: { 32 | description: 'Favorite color. Default is green', 33 | enum: ['red', 'green', 'blue', 'yellow'], 34 | }, 35 | }, 36 | }); 37 | 38 | fs.writeFileSync('person.ts', g.render()); 39 | ``` 40 | 41 |
42 | person.ts 43 | 44 | ```ts 45 | /** 46 | * @schema Person 47 | */ 48 | export interface Person { 49 | /** 50 | * The person's name 51 | * 52 | * @schema Person#name 53 | */ 54 | readonly name: Name; 55 | 56 | /** 57 | * Favorite color. Default is green 58 | * 59 | * @default green 60 | * @schema Person#favorite_color 61 | */ 62 | readonly favoriteColor?: PersonFavoriteColor; 63 | 64 | } 65 | 66 | /** 67 | * Converts an object of type 'Person' to JSON representation. 68 | */ 69 | /* eslint-disable max-len, quote-props */ 70 | export function toJson_Person(obj: Person | undefined): Record | undefined { 71 | if (obj === undefined) { return undefined; } 72 | const result = { 73 | 'name': toJson_Name(obj.name), 74 | 'favorite_color': obj.favoriteColor, 75 | }; 76 | // filter undefined values 77 | return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {}); 78 | } 79 | /* eslint-enable max-len, quote-props */ 80 | 81 | /** 82 | * Represents a name of a person 83 | * 84 | * @schema Name 85 | */ 86 | export interface Name { 87 | /** 88 | * The first name of the person 89 | * 90 | * @schema Name#FirstName 91 | */ 92 | readonly firstName: string; 93 | 94 | /** 95 | * The last name of the person 96 | * 97 | * @schema Name#last_name 98 | */ 99 | readonly lastName: string; 100 | 101 | } 102 | 103 | /** 104 | * Converts an object of type 'Name' to JSON representation. 105 | */ 106 | /* eslint-disable max-len, quote-props */ 107 | export function toJson_Name(obj: Name | undefined): Record | undefined { 108 | if (obj === undefined) { return undefined; } 109 | const result = { 110 | 'FirstName': obj.firstName, 111 | 'last_name': obj.lastName, 112 | }; 113 | // filter undefined values 114 | return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {}); 115 | } 116 | /* eslint-enable max-len, quote-props */ 117 | 118 | /** 119 | * Favorite color. Default is green 120 | * 121 | * @default green 122 | * @schema PersonFavoriteColor 123 | */ 124 | export enum PersonFavoriteColor { 125 | /** red */ 126 | RED = 'red', 127 | /** green */ 128 | GREEN = 'green', 129 | /** blue */ 130 | BLUE = 'blue', 131 | /** yellow */ 132 | YELLOW = 'yellow', 133 | } 134 | ``` 135 | 136 |
137 | 138 | The generated code includes JSII structs (TypeScript interfaces) and enums based 139 | on the schema (`Person`, `Name` and `PersonFavoriteColor`) as well as a function 140 | `toJson_Xyz()` for each struct. 141 | 142 | The `toJson()` functions are required in order to serialize objects back to their 143 | original schema format. 144 | 145 | For example, the following expression: 146 | 147 | ```ts 148 | toJson_Person({ 149 | name: { 150 | firstName: 'Jordan', 151 | lastName: 'McJordan' 152 | }, 153 | favoriteColor: PersonFavoriteColor.GREEN 154 | }) 155 | ``` 156 | 157 | Will return: 158 | 159 | ```json 160 | { 161 | "name": { 162 | "FirstName": "Jordan", 163 | "last_name": "McJordan" 164 | }, 165 | "favorite_color": "green" 166 | } 167 | ``` 168 | 169 | ## Use cases 170 | 171 | ### Type aliases 172 | 173 | It is possible to offer an alias to a type definition using `addAlias(from, 174 | to)`. The type generator will resolve any references to the original type with 175 | the alias: 176 | 177 | ```ts 178 | const gen = new TypeGenerator(); 179 | gen.addDefinition('TypeA', { type: 'object', properties: { ref: { $ref: '#/definitions/TypeB' } } } ); 180 | gen.addDefinition('TypeC', { type: 'object', properties: { field: { type: 'string' } } }); 181 | gen.addAlias('TypeB', 'TypeC'); 182 | 183 | gen.emitType('TypeA'); 184 | ``` 185 | 186 | This will output: 187 | 188 | ```ts 189 | interface TypeA { 190 | readonly ref: TypeC; 191 | } 192 | 193 | interface TypeC { 194 | readonly field: string; 195 | } 196 | ``` 197 | 198 | ## Language bindings 199 | 200 | Once you generate jsii-compatible TypeScript source (such as `person.ts` above), 201 | you can use [jsii-srcmak](https://github.com/eladb/jsii-srcmak) in order to 202 | produce source code in any of the jsii supported languages. 203 | 204 | The following command will produce Python sources for the `Person` types: 205 | 206 | ```shell 207 | $ jsii-srcmak gen/ts \ 208 | --python-outdir gen/py --python-module-name person \ 209 | --java-outdir gen/java --java-package person 210 | ``` 211 | 212 | See the [jsii-srcmak](https://github.com/eladb/jsii-srcmak) for library usage. 213 | 214 | ## Contributions 215 | 216 | All contributions are celebrated. 217 | 218 | ## License 219 | 220 | Distributed under the [Apache 2.0](./LICENSE) license. 221 | 222 | -------------------------------------------------------------------------------- /test/fixtures/datadog.json: -------------------------------------------------------------------------------- 1 | { 2 | "typeName": "Datadog::SLOs::SLO", 3 | "description": "Datadog SLO 1.0.0", 4 | "typeConfiguration": { 5 | "properties": { 6 | "DatadogCredentials": { 7 | "$ref": "#/definitions/DatadogCredentials" 8 | } 9 | }, 10 | "additionalProperties": false 11 | }, 12 | "definitions": { 13 | "Creator": { 14 | "type": "object", 15 | "additionalProperties": false, 16 | "properties": { 17 | "Name": { 18 | "description": "Name of the creator of the slo", 19 | "type": "string" 20 | }, 21 | "Handle": { 22 | "description": "Handle of the creator of the slo", 23 | "type": "string" 24 | }, 25 | "Email": { 26 | "description": "Email of the creator of the slo", 27 | "type": "string" 28 | } 29 | } 30 | }, 31 | "Threshold": { 32 | "type": "object", 33 | "additionalProperties": false, 34 | "properties": { 35 | "Target": { 36 | "description": "The target value for the service level indicator within the corresponding timeframe.", 37 | "type": "number" 38 | }, 39 | "TargetDisplay": { 40 | "description": "A string representation of the target that indicates its precision.(e.g. 98.00)", 41 | "type": "string" 42 | }, 43 | "Timeframe": { 44 | "description": "The SLO time window options. Allowed enum values: 7d,30d,90d", 45 | "type": "string", 46 | "enum": [ 47 | "7d", 48 | "30d", 49 | "90d" 50 | ] 51 | }, 52 | "Warning": { 53 | "description": "The warning value for the service level objective.", 54 | "type": "number" 55 | }, 56 | "WarningDisplay": { 57 | "description": "A string representation of the warning target.(e.g. 98.00)", 58 | "type": "string" 59 | } 60 | } 61 | }, 62 | "Query": { 63 | "type": "object", 64 | "additionalProperties": false, 65 | "properties": { 66 | "Numerator": { 67 | "description": "A Datadog metric query for total (valid) events.", 68 | "type": "string" 69 | }, 70 | "Denominator": { 71 | "description": "A Datadog metric query for good events.", 72 | "type": "string" 73 | } 74 | } 75 | }, 76 | "DatadogCredentials": { 77 | "description": "Credentials for the Datadog API", 78 | "properties": { 79 | "ApiKey": { 80 | "description": "Datadog API key", 81 | "type": "string" 82 | }, 83 | "ApplicationKey": { 84 | "description": "Datadog application key", 85 | "type": "string" 86 | }, 87 | "ApiURL": { 88 | "description": "Datadog API URL (defaults to https://api.datadoghq.com) Use https://api.datadoghq.eu for EU accounts.", 89 | "type": "string" 90 | } 91 | }, 92 | "required": [ 93 | "ApiKey", 94 | "ApplicationKey" 95 | ], 96 | "type": "object", 97 | "additionalProperties": false 98 | } 99 | }, 100 | "properties": { 101 | "Creator": { 102 | "$ref": "#/definitions/Creator" 103 | }, 104 | "Description": { 105 | "description": "Description of the slo", 106 | "type": "string" 107 | }, 108 | "Groups": { 109 | "description": "A list of (up to 20) monitor groups that narrow the scope of a monitor service level objective.", 110 | "type": "array", 111 | "items": { 112 | "type": "string" 113 | } 114 | }, 115 | "Id": { 116 | "description": "ID of the slo", 117 | "type": "string" 118 | }, 119 | "MonitorIds": { 120 | "description": "A list of monitor ids that defines the scope of a monitor service level objective. Required if type is monitor.", 121 | "type": "array", 122 | "items": { 123 | "type": "integer" 124 | } 125 | }, 126 | "Name": { 127 | "description": "Name of the slo", 128 | "type": "string" 129 | }, 130 | "Query": { 131 | "$ref": "#/definitions/Query" 132 | }, 133 | "Tags": { 134 | "description": "Tags associated with the slo", 135 | "type": "array", 136 | "items": { 137 | "type": "string" 138 | } 139 | }, 140 | "Thresholds": { 141 | "type": "array", 142 | "items": { 143 | "$ref": "#/definitions/Threshold" 144 | } 145 | }, 146 | "Type": { 147 | "type": "string", 148 | "description": "The type of the slo", 149 | "enum": [ 150 | "monitor", 151 | "metric" 152 | ] 153 | }, 154 | "Created": { 155 | "description": "Date of creation of the slo", 156 | "type": "string", 157 | "format": "date-time" 158 | }, 159 | "Deleted": { 160 | "description": "Date of deletion of the slo", 161 | "type": "string", 162 | "format": "date-time" 163 | }, 164 | "Modified": { 165 | "description": "Date of modification of the slo", 166 | "type": "string", 167 | "format": "date-time" 168 | } 169 | }, 170 | "required": [ 171 | "Name", 172 | "Thresholds", 173 | "Type" 174 | ], 175 | "primaryIdentifier": [ 176 | "/properties/Id" 177 | ], 178 | "readOnlyProperties": [ 179 | "/properties/Modified", 180 | "/properties/Id", 181 | "/properties/Deleted", 182 | "/properties/State", 183 | "/properties/OverallState", 184 | "/properties/Creator", 185 | "/properties/Created" 186 | ], 187 | "additionalProperties": false, 188 | "handlers": { 189 | "create": { 190 | "permissions": [ 191 | "" 192 | ] 193 | }, 194 | "read": { 195 | "permissions": [ 196 | "" 197 | ] 198 | }, 199 | "update": { 200 | "permissions": [ 201 | "" 202 | ] 203 | }, 204 | "delete": { 205 | "permissions": [ 206 | "" 207 | ] 208 | } 209 | } 210 | } -------------------------------------------------------------------------------- /test/fixtures/aqua-enterprise-enforcer.json: -------------------------------------------------------------------------------- 1 | { 2 | "typeName": "Aqua::Enterprise::Enforcer", 3 | "description": "A resource provider for Aqua Enterprise Enforcer.", 4 | "sourceUrl": "https://github.com/aquasecurity/aqua-helm.git", 5 | "definitions": { 6 | "Arn": { 7 | "type": "string", 8 | "pattern": "^arn:aws(-(cn|us-gov))?:[a-z-]+:(([a-z]+-)+[0-9])?:([0-9]{12})?:[^.]+$" 9 | } 10 | }, 11 | "properties": { 12 | "ClusterID": { 13 | "description": "EKS cluster name", 14 | "type": "string" 15 | }, 16 | "KubeConfig": { 17 | "description": "Secrets Manager ARN for kubeconfig file", 18 | "$ref": "#/definitions/Arn" 19 | }, 20 | "RoleArn": { 21 | "description": "IAM to use with EKS cluster authentication, if not resource execution role will be used", 22 | "$ref": "#/definitions/Arn" 23 | }, 24 | "Namespace": { 25 | "description": "Namespace to use with helm. Created if doesn't exist and default will be used if not provided", 26 | "type": "string" 27 | }, 28 | "Name": { 29 | "description": "Name for the helm release", 30 | "type": "string" 31 | }, 32 | "Values": { 33 | "description": "Custom Values can optionally be specified", 34 | "type": "object", 35 | "additionalProperties": false, 36 | "patternProperties": { 37 | "^.+$": { 38 | "type": "string" 39 | } 40 | } 41 | }, 42 | "ValueYaml": { 43 | "description": "String representation of a values.yaml file", 44 | "type": "string" 45 | }, 46 | "Version": { 47 | "description": "Version can be specified, if not latest will be used", 48 | "type": "string" 49 | }, 50 | "ValueOverrideURL": { 51 | "description": "Custom Value Yaml file can optionally be specified", 52 | "type": "string", 53 | "pattern": "^[sS]3://[0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z])(:[0-9]*)*([?/#].*)?$" 54 | }, 55 | "ID": { 56 | "description": "Primary identifier for Cloudformation", 57 | "type": "string" 58 | }, 59 | "TimeOut": { 60 | "description": "Timeout for resource provider. Default 60 mins", 61 | "type": "integer" 62 | }, 63 | "VPCConfiguration": { 64 | "type": "object", 65 | "description": "For network connectivity to Cluster inside VPC", 66 | "additionalProperties": false, 67 | "properties": { 68 | "SecurityGroupIds": { 69 | "description": "Specify one or more security groups", 70 | "type": "array", 71 | "items": { 72 | "type": "string" 73 | } 74 | }, 75 | "SubnetIds": { 76 | "description": "Specify one or more subnets", 77 | "type": "array", 78 | "items": { 79 | "type": "string" 80 | } 81 | } 82 | } 83 | } 84 | }, 85 | "additionalProperties": false, 86 | "readOnlyProperties": [ 87 | "/properties/Resources", 88 | "/properties/ID" 89 | ], 90 | "primaryIdentifier": [ 91 | "/properties/ID" 92 | ], 93 | "createOnlyProperties": [ 94 | "/properties/Name", 95 | "/properties/Namespace", 96 | "/properties/ClusterID" 97 | ], 98 | "handlers": { 99 | "create": { 100 | "permissions": [ 101 | "secretsmanager:GetSecretValue", 102 | "kms:Decrypt", 103 | "eks:DescribeCluster", 104 | "s3:GetObject", 105 | "sts:AssumeRole", 106 | "iam:PassRole", 107 | "ec2:CreateNetworkInterface", 108 | "ec2:DescribeNetworkInterfaces", 109 | "ec2:DeleteNetworkInterface", 110 | "ec2:DescribeVpcs", 111 | "ec2:DescribeSubnets", 112 | "ec2:DescribeRouteTables", 113 | "ec2:DescribeSecurityGroups", 114 | "logs:CreateLogGroup", 115 | "logs:CreateLogStream", 116 | "logs:PutLogEvents", 117 | "lambda:UpdateFunctionConfiguration", 118 | "lambda:DeleteFunction", 119 | "lambda:GetFunction", 120 | "lambda:InvokeFunction", 121 | "lambda:CreateFunction", 122 | "lambda:UpdateFunctionCode" 123 | ] 124 | }, 125 | "read": { 126 | "permissions": [ 127 | "secretsmanager:GetSecretValue", 128 | "kms:Decrypt", 129 | "eks:DescribeCluster", 130 | "s3:GetObject", 131 | "sts:AssumeRole", 132 | "iam:PassRole", 133 | "ec2:CreateNetworkInterface", 134 | "ec2:DescribeNetworkInterfaces", 135 | "ec2:DeleteNetworkInterface", 136 | "ec2:DescribeVpcs", 137 | "ec2:DescribeSubnets", 138 | "ec2:DescribeRouteTables", 139 | "ec2:DescribeSecurityGroups", 140 | "logs:CreateLogGroup", 141 | "logs:CreateLogStream", 142 | "logs:PutLogEvents", 143 | "lambda:UpdateFunctionConfiguration", 144 | "lambda:DeleteFunction", 145 | "lambda:GetFunction", 146 | "lambda:InvokeFunction", 147 | "lambda:CreateFunction", 148 | "lambda:UpdateFunctionCode" 149 | ] 150 | }, 151 | "update": { 152 | "permissions": [ 153 | "secretsmanager:GetSecretValue", 154 | "kms:Decrypt", 155 | "eks:DescribeCluster", 156 | "s3:GetObject", 157 | "sts:AssumeRole", 158 | "iam:PassRole", 159 | "ec2:CreateNetworkInterface", 160 | "ec2:DescribeNetworkInterfaces", 161 | "ec2:DeleteNetworkInterface", 162 | "ec2:DescribeVpcs", 163 | "ec2:DescribeSubnets", 164 | "ec2:DescribeRouteTables", 165 | "ec2:DescribeSecurityGroups", 166 | "logs:CreateLogGroup", 167 | "logs:CreateLogStream", 168 | "logs:PutLogEvents", 169 | "lambda:UpdateFunctionConfiguration", 170 | "lambda:DeleteFunction", 171 | "lambda:GetFunction", 172 | "lambda:InvokeFunction", 173 | "lambda:CreateFunction", 174 | "lambda:UpdateFunctionCode" 175 | ] 176 | }, 177 | "delete": { 178 | "permissions": [ 179 | "secretsmanager:GetSecretValue", 180 | "kms:Decrypt", 181 | "eks:DescribeCluster", 182 | "s3:GetObject", 183 | "sts:AssumeRole", 184 | "iam:PassRole", 185 | "ec2:CreateNetworkInterface", 186 | "ec2:DescribeNetworkInterfaces", 187 | "ec2:DeleteNetworkInterface", 188 | "ec2:DescribeVpcs", 189 | "ec2:DescribeSubnets", 190 | "ec2:DescribeRouteTables", 191 | "ec2:DescribeSecurityGroups", 192 | "logs:CreateLogGroup", 193 | "logs:CreateLogStream", 194 | "logs:PutLogEvents", 195 | "lambda:UpdateFunctionConfiguration", 196 | "lambda:DeleteFunction", 197 | "lambda:GetFunction", 198 | "lambda:InvokeFunction", 199 | "lambda:CreateFunction", 200 | "lambda:UpdateFunctionCode" 201 | ] 202 | } 203 | } 204 | } -------------------------------------------------------------------------------- /.projen/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "build": { 4 | "name": "build", 5 | "description": "Full release build", 6 | "steps": [ 7 | { 8 | "spawn": "default" 9 | }, 10 | { 11 | "spawn": "pre-compile" 12 | }, 13 | { 14 | "spawn": "compile" 15 | }, 16 | { 17 | "spawn": "post-compile" 18 | }, 19 | { 20 | "spawn": "test" 21 | }, 22 | { 23 | "spawn": "package" 24 | } 25 | ] 26 | }, 27 | "bump": { 28 | "name": "bump", 29 | "description": "Bumps version based on latest git tag and generates a changelog entry", 30 | "env": { 31 | "OUTFILE": "package.json", 32 | "CHANGELOG": "dist/changelog.md", 33 | "BUMPFILE": "dist/version.txt", 34 | "RELEASETAG": "dist/releasetag.txt", 35 | "RELEASE_TAG_PREFIX": "", 36 | "BUMP_PACKAGE": "commit-and-tag-version@^12", 37 | "RELEASABLE_COMMITS": "git log --no-merges --oneline $LATEST_TAG..HEAD -E --grep \"^(feat|fix){1}(\\([^()[:space:]]+\\))?(!)?:[[:blank:]]+.+\"" 38 | }, 39 | "steps": [ 40 | { 41 | "builtin": "release/bump-version" 42 | } 43 | ], 44 | "condition": "git log --oneline -1 | grep -qv \"chore(release):\"" 45 | }, 46 | "clobber": { 47 | "name": "clobber", 48 | "description": "hard resets to HEAD of origin and cleans the local repo", 49 | "env": { 50 | "BRANCH": "$(git branch --show-current)" 51 | }, 52 | "steps": [ 53 | { 54 | "exec": "git checkout -b scratch", 55 | "name": "save current HEAD in \"scratch\" branch" 56 | }, 57 | { 58 | "exec": "git checkout $BRANCH" 59 | }, 60 | { 61 | "exec": "git fetch origin", 62 | "name": "fetch latest changes from origin" 63 | }, 64 | { 65 | "exec": "git reset --hard origin/$BRANCH", 66 | "name": "hard reset to origin commit" 67 | }, 68 | { 69 | "exec": "git clean -fdx", 70 | "name": "clean all untracked files" 71 | }, 72 | { 73 | "say": "ready to rock! (unpushed commits are under the \"scratch\" branch)" 74 | } 75 | ], 76 | "condition": "git diff --exit-code > /dev/null" 77 | }, 78 | "compile": { 79 | "name": "compile", 80 | "description": "Only compile", 81 | "steps": [ 82 | { 83 | "exec": "tsc --build" 84 | } 85 | ] 86 | }, 87 | "default": { 88 | "name": "default", 89 | "description": "Synthesize project files", 90 | "steps": [ 91 | { 92 | "exec": "ts-node --project tsconfig.dev.json .projenrc.ts" 93 | } 94 | ] 95 | }, 96 | "eject": { 97 | "name": "eject", 98 | "description": "Remove projen from the project", 99 | "env": { 100 | "PROJEN_EJECTING": "true" 101 | }, 102 | "steps": [ 103 | { 104 | "spawn": "default" 105 | } 106 | ] 107 | }, 108 | "eslint": { 109 | "name": "eslint", 110 | "description": "Runs eslint against the codebase", 111 | "env": { 112 | "ESLINT_USE_FLAT_CONFIG": "false", 113 | "NODE_NO_WARNINGS": "1" 114 | }, 115 | "steps": [ 116 | { 117 | "exec": "eslint --ext .ts,.tsx --fix --no-error-on-unmatched-pattern $@ src test build-tools projenrc .projenrc.ts", 118 | "receiveArgs": true 119 | } 120 | ] 121 | }, 122 | "install": { 123 | "name": "install", 124 | "description": "Install project dependencies and update lockfile (non-frozen)", 125 | "steps": [ 126 | { 127 | "exec": "yarn install --check-files" 128 | } 129 | ] 130 | }, 131 | "install:ci": { 132 | "name": "install:ci", 133 | "description": "Install project dependencies using frozen lockfile", 134 | "steps": [ 135 | { 136 | "exec": "yarn install --check-files --frozen-lockfile" 137 | } 138 | ] 139 | }, 140 | "package": { 141 | "name": "package", 142 | "description": "Creates the distribution package", 143 | "steps": [ 144 | { 145 | "exec": "mkdir -p dist/js" 146 | }, 147 | { 148 | "exec": "npm pack --pack-destination dist/js" 149 | } 150 | ] 151 | }, 152 | "post-compile": { 153 | "name": "post-compile", 154 | "description": "Runs after successful compilation" 155 | }, 156 | "post-upgrade": { 157 | "name": "post-upgrade", 158 | "description": "Runs after upgrading dependencies" 159 | }, 160 | "pre-compile": { 161 | "name": "pre-compile", 162 | "description": "Prepare the project for compilation" 163 | }, 164 | "release": { 165 | "name": "release", 166 | "description": "Prepare a release from \"main\" branch", 167 | "env": { 168 | "RELEASE": "true" 169 | }, 170 | "steps": [ 171 | { 172 | "exec": "rm -fr dist" 173 | }, 174 | { 175 | "spawn": "bump" 176 | }, 177 | { 178 | "spawn": "build" 179 | }, 180 | { 181 | "spawn": "unbump" 182 | }, 183 | { 184 | "exec": "git diff --ignore-space-at-eol --exit-code" 185 | } 186 | ] 187 | }, 188 | "test": { 189 | "name": "test", 190 | "description": "Run tests", 191 | "steps": [ 192 | { 193 | "exec": "jest --passWithNoTests --updateSnapshot", 194 | "receiveArgs": true 195 | }, 196 | { 197 | "spawn": "eslint" 198 | } 199 | ] 200 | }, 201 | "test:watch": { 202 | "name": "test:watch", 203 | "description": "Run jest in watch mode", 204 | "steps": [ 205 | { 206 | "exec": "jest --watch" 207 | } 208 | ] 209 | }, 210 | "unbump": { 211 | "name": "unbump", 212 | "description": "Restores version to 0.0.0", 213 | "env": { 214 | "OUTFILE": "package.json", 215 | "CHANGELOG": "dist/changelog.md", 216 | "BUMPFILE": "dist/version.txt", 217 | "RELEASETAG": "dist/releasetag.txt", 218 | "RELEASE_TAG_PREFIX": "", 219 | "BUMP_PACKAGE": "commit-and-tag-version@^12", 220 | "RELEASABLE_COMMITS": "git log --no-merges --oneline $LATEST_TAG..HEAD -E --grep \"^(feat|fix){1}(\\([^()[:space:]]+\\))?(!)?:[[:blank:]]+.+\"" 221 | }, 222 | "steps": [ 223 | { 224 | "builtin": "release/reset-version" 225 | } 226 | ] 227 | }, 228 | "upgrade": { 229 | "name": "upgrade", 230 | "description": "upgrade dependencies", 231 | "env": { 232 | "CI": "0" 233 | }, 234 | "steps": [ 235 | { 236 | "exec": "npx npm-check-updates@16 --upgrade --target=minor --peer --no-deprecated --dep=prod --filter=camelcase,json-schema,snake-case" 237 | }, 238 | { 239 | "exec": "yarn install --check-files" 240 | }, 241 | { 242 | "exec": "yarn upgrade camelcase json-schema snake-case" 243 | }, 244 | { 245 | "exec": "npx projen" 246 | }, 247 | { 248 | "spawn": "post-upgrade" 249 | } 250 | ] 251 | }, 252 | "upgrade-cdklabs-projen-project-types": { 253 | "name": "upgrade-cdklabs-projen-project-types", 254 | "description": "upgrade cdklabs-projen-project-types", 255 | "env": { 256 | "CI": "0" 257 | }, 258 | "steps": [ 259 | { 260 | "exec": "npx npm-check-updates@16 --upgrade --target=latest --peer --no-deprecated --dep=dev,peer,prod,optional --filter=cdklabs-projen-project-types,projen" 261 | }, 262 | { 263 | "exec": "yarn install --check-files" 264 | }, 265 | { 266 | "exec": "yarn upgrade cdklabs-projen-project-types projen" 267 | }, 268 | { 269 | "exec": "npx projen" 270 | }, 271 | { 272 | "spawn": "post-upgrade" 273 | } 274 | ] 275 | }, 276 | "upgrade-dev-deps": { 277 | "name": "upgrade-dev-deps", 278 | "description": "upgrade dev dependencies", 279 | "env": { 280 | "CI": "0" 281 | }, 282 | "steps": [ 283 | { 284 | "exec": "npx npm-check-updates@16 --upgrade --target=minor --peer --no-deprecated --dep=dev --filter=@types/jest,@types/json-schema,eslint-import-resolver-typescript,eslint-plugin-import,jest,jsii-srcmak,prettier,ts-jest,ts-node,typescript" 285 | }, 286 | { 287 | "exec": "yarn install --check-files" 288 | }, 289 | { 290 | "exec": "yarn upgrade @stylistic/eslint-plugin @types/jest @types/json-schema @types/node @typescript-eslint/eslint-plugin @typescript-eslint/parser commit-and-tag-version constructs eslint-import-resolver-typescript eslint-plugin-import eslint jest jest-junit jsii-srcmak prettier ts-jest ts-node typescript" 291 | }, 292 | { 293 | "exec": "npx projen" 294 | }, 295 | { 296 | "spawn": "post-upgrade" 297 | } 298 | ] 299 | }, 300 | "watch": { 301 | "name": "watch", 302 | "description": "Watch & compile in the background", 303 | "steps": [ 304 | { 305 | "exec": "tsc --build -w" 306 | } 307 | ] 308 | } 309 | }, 310 | "env": { 311 | "PATH": "$(npx -c \"node --print process.env.PATH\")" 312 | }, 313 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 314 | } 315 | -------------------------------------------------------------------------------- /test/tojson.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import { spawnSync } from 'child_process'; 3 | import { mkdtempSync, writeFileSync } from 'fs'; 4 | import { tmpdir } from 'os'; 5 | import { join } from 'path'; 6 | import { JSONSchema4 } from 'json-schema'; 7 | import { TypeGenerator } from '../src'; 8 | 9 | test('primitives', () => { 10 | const toJson = generateToJson({ 11 | properties: { 12 | StringProperty: { type: 'string' }, 13 | BooleanProperty: { type: 'boolean' }, 14 | NumberProperty: { type: 'number' }, 15 | With_UnderScore: { type: 'integer' }, 16 | }, 17 | required: [ 18 | 'StringProperty', 19 | ], 20 | }); 21 | 22 | expect(toJson({ 23 | stringProperty: 'hello', booleanProperty: false, numberProperty: 1234, withUnderScore: 8989, 24 | })).toStrictEqual({ 25 | StringProperty: 'hello', BooleanProperty: false, NumberProperty: 1234, With_UnderScore: 8989, 26 | }); 27 | 28 | expect(toJson({ 29 | stringProperty: 'hello, world', numberProperty: undefined, 30 | })).toStrictEqual({ 31 | StringProperty: 'hello, world', 32 | }); 33 | }); 34 | 35 | test('names can get crazy', () => { 36 | const toJson = generateToJson({ 37 | properties: { 38 | 'hyphen-name': { type: 'string' }, 39 | '$nameWithDolar': { type: 'string' }, 40 | 'name with spaces': { type: 'string' }, 41 | 'x-extension': { type: 'string' }, 42 | }, 43 | }); 44 | 45 | expect(toJson({ 46 | hyphenName: 'boomboom', 47 | nameWithDolar: 'hey', 48 | nameWithSpaces: 'glow', 49 | xExtension: 'booom', 50 | })).toStrictEqual({ 51 | 'hyphen-name': 'boomboom', 52 | '$nameWithDolar': 'hey', 53 | 'name with spaces': 'glow', 54 | 'x-extension': 'booom', 55 | }); 56 | }); 57 | 58 | test('complex types', () => { 59 | const toJson = generateToJson({ 60 | definitions: { 61 | YourType: { 62 | properties: { 63 | John: { type: 'array', items: { type: 'string' } }, 64 | }, 65 | }, 66 | MyType: { 67 | properties: { 68 | Foo: { type: 'number' }, 69 | Bar: { type: 'string' }, 70 | Nested: { $ref: '#/definitions/YourType' }, 71 | }, 72 | required: ['Nested'], 73 | }, 74 | }, 75 | properties: { 76 | ComplexType: { $ref: '#/definitions/MyType' }, 77 | }, 78 | }); 79 | 80 | expect(toJson({ 81 | complexType: { foo: 1234, bar: 'hey there', nested: { john: ['foo'] } }, 82 | })).toStrictEqual({ 83 | ComplexType: { Foo: 1234, Bar: 'hey there', Nested: { John: ['foo'] } }, 84 | }); 85 | }); 86 | 87 | describe('arrays', () => { 88 | 89 | test('of primitives', () => { 90 | const toJson = generateToJson({ 91 | properties: { 92 | ArrayProp: { type: 'array', items: { type: 'string' } }, 93 | }, 94 | }); 95 | 96 | expect(toJson({ 97 | arrayProp: ['bello', 'aoll'], 98 | })).toStrictEqual({ 99 | ArrayProp: ['bello', 'aoll'], 100 | }); 101 | }); 102 | 103 | test('of complex types', () => { 104 | const toJson = generateToJson({ 105 | definitions: { 106 | MyType: { 107 | properties: { 108 | Foo: { type: 'number' }, 109 | Bar: { type: 'string' }, 110 | }, 111 | }, 112 | }, 113 | properties: { 114 | Array_Of_complex: { type: 'array', items: { $ref: '#/definitions/MyType' } }, 115 | }, 116 | }); 117 | 118 | expect(toJson({ 119 | arrayOfComplex: [{ foo: 122 }, { bar: 'hello' }, { foo: 88, bar: 'world' }], 120 | })).toStrictEqual({ 121 | Array_Of_complex: [{ Foo: 122 }, { Bar: 'hello' }, { Foo: 88, Bar: 'world' }], 122 | }); 123 | }); 124 | 125 | test('without items', () => { 126 | const toJson = generateToJson({ 127 | properties: { 128 | ArrayProp: { type: 'array' }, 129 | }, 130 | }); 131 | 132 | expect(toJson({ 133 | arrayProp: ['bello', { foo: 88, bar: 'world' }], 134 | })).toStrictEqual({ 135 | ArrayProp: ['bello', { foo: 88, bar: 'world' }], 136 | }); 137 | }); 138 | 139 | test('with items', () => { 140 | const toJson = generateToJson({ 141 | properties: { 142 | ArrayProp: { 143 | type: 'array', 144 | items: { 145 | type: 'string', 146 | }, 147 | }, 148 | }, 149 | }); 150 | 151 | expect(toJson({ 152 | arrayProp: ['bello', { foo: 88, bar: 'world' }], 153 | })).toStrictEqual({ 154 | ArrayProp: ['bello', { foo: 88, bar: 'world' }], 155 | }); 156 | }); 157 | 158 | }); 159 | 160 | test('any', () => { 161 | const toJson = generateToJson({ 162 | properties: { 163 | MyAny: { type: 'object' }, 164 | }, 165 | }); 166 | 167 | expect(toJson({ myAny: 177 })).toStrictEqual({ MyAny: 177 }); 168 | expect(toJson({ myAny: 'hello' })).toStrictEqual({ MyAny: 'hello' }); 169 | expect(toJson({ myAny: ['hello', 889] })).toStrictEqual({ MyAny: ['hello', 889] }); 170 | }); 171 | 172 | 173 | describe('maps', () => { 174 | test('of primitives', () => { 175 | const toJson = generateToJson({ 176 | properties: { 177 | StringMap: { additionalProperties: { type: 'string' } }, 178 | NumberMap: { additionalProperties: { type: 'number' } }, 179 | }, 180 | }); 181 | 182 | expect(toJson({ 183 | stringMap: { 184 | hello: 'string', 185 | world: 'strong', 186 | }, 187 | numberMap: { 188 | hello: 10, 189 | world: 20, 190 | }, 191 | })).toStrictEqual({ 192 | StringMap: { 193 | hello: 'string', 194 | world: 'strong', 195 | }, 196 | NumberMap: { 197 | hello: 10, 198 | world: 20, 199 | }, 200 | }); 201 | 202 | expect(toJson({ 203 | stringMap: undefined, 204 | numberMap: { 205 | hello: undefined, 206 | world: 11, 207 | }, 208 | })).toStrictEqual({ 209 | NumberMap: { 210 | world: 11, 211 | }, 212 | }); 213 | }); 214 | 215 | test('of complex', () => { 216 | const toJson = generateToJson({ 217 | definitions: { 218 | MyType: { 219 | properties: { 220 | Foo: { type: 'number' }, 221 | Bar: { type: 'string' }, 222 | }, 223 | }, 224 | }, 225 | properties: { 226 | ComplexMap: { additionalProperties: { $ref: '#/definitions/MyType' } }, 227 | }, 228 | }); 229 | 230 | expect(toJson({ 231 | complexMap: { 232 | foya: { foo: 123, bar: 'barbar' }, 233 | hello_world: { foo: 3333 }, 234 | }, 235 | })).toStrictEqual({ 236 | ComplexMap: { 237 | foya: { Foo: 123, Bar: 'barbar' }, 238 | hello_world: { Foo: 3333 }, 239 | }, 240 | }); 241 | }); 242 | }); 243 | 244 | test('enums', () => { 245 | const toJson = generateToJson({ 246 | properties: { 247 | MyEnum: { enum: ['one', 'two', 'three'] }, 248 | YourEnum: { enum: ['jo', 'shmo'] }, 249 | }, 250 | }); 251 | 252 | expect(toJson({ 253 | myEnum: 'two', 254 | yourEnum: undefined, 255 | })).toStrictEqual({ 256 | MyEnum: 'two', 257 | }); 258 | }); 259 | 260 | test('date', () => { 261 | const toJson = generateToJson({ 262 | properties: { 263 | DateTime: { type: 'string', format: 'date-time' }, 264 | }, 265 | }); 266 | 267 | expect(toJson({ 268 | dateTime: new Date('2021-07-19T12:08:52.210Z'), 269 | })).toStrictEqual({ 270 | DateTime: '2021-07-19T12:08:52.210Z', 271 | }); 272 | 273 | expect(toJson({ dateTime: undefined })).toStrictEqual({}); 274 | }); 275 | 276 | test('union types', () => { 277 | const module = generateModule('MyStruct', { 278 | definitions: { 279 | ComplexType: { 280 | properties: { 281 | Ref_to_union: { $ref: '#/definitions/IntOrString' }, 282 | }, 283 | }, 284 | IntOrString: { 285 | oneOf: [ 286 | { type: 'integer' }, 287 | { type: 'string' }, 288 | ], 289 | }, 290 | }, 291 | properties: { 292 | ref_to_complex: { $ref: '#/definitions/ComplexType' }, 293 | ReusedType: { $ref: '#/definitions/IntOrString' }, 294 | Haver: { 295 | anyOf: [ 296 | { type: 'integer' }, 297 | { type: 'string' }, 298 | ], 299 | }, 300 | }, 301 | }); 302 | 303 | const toJson = module.toJson_MyStruct; 304 | const MyStructHaver = module.MyStructHaver; 305 | const IntOrString = module.IntOrString; 306 | 307 | expect(toJson({ 308 | haver: MyStructHaver.fromString('hello'), 309 | refToComplex: { 310 | refToUnion: IntOrString.fromNumber(1234), 311 | }, 312 | })).toStrictEqual({ 313 | Haver: 'hello', 314 | ref_to_complex: { 315 | Ref_to_union: 1234, 316 | }, 317 | }); 318 | }); 319 | 320 | /** 321 | * Generates structs from this JSON schema and returns a compiled versio of the 322 | * toJson method for the top-level struct. 323 | */ 324 | function generateToJson(schema: JSONSchema4): (x: any) => any { 325 | return generateModule('MyStruct', schema).toJson_MyStruct; 326 | } 327 | 328 | /** 329 | * Generates structs from this JSON schema and returns a compiled versio of the 330 | * toJson method for the top-level struct. 331 | */ 332 | function generateModule(structName: string, schema: JSONSchema4): any { 333 | // generate code into workdir 334 | const workdir = mkdtempSync(join(tmpdir(), 'tojson.')); 335 | 336 | const gen = TypeGenerator.forStruct(structName, schema); 337 | const code = gen.render(); 338 | expect(code).toMatchSnapshot(); 339 | 340 | writeFileSync(join(workdir, 'index.ts'), code); 341 | 342 | try { 343 | // compile using the typescript compiler 344 | const tsc = require.resolve('typescript/bin/tsc'); 345 | spawnSync(tsc, ['index.ts'], { cwd: workdir }); 346 | 347 | // import the compiled javascript code into this process (wow) 348 | const source = join(workdir, 'index.js'); 349 | 350 | 351 | // eslint-disable-next-line @typescript-eslint/no-require-imports 352 | return require(source); 353 | } catch (e) { 354 | throw new Error(`Compilation error: ${e}. Workdir: ${workdir}`); 355 | } 356 | } -------------------------------------------------------------------------------- /test/__snapshots__/bindings.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`language bindings 1`] = ` 4 | "from pkgutil import extend_path 5 | __path__ = extend_path(__path__, __name__) 6 | 7 | import abc 8 | import builtins 9 | import datetime 10 | import enum 11 | import typing 12 | 13 | import jsii 14 | import publication 15 | import typing_extensions 16 | 17 | import typeguard 18 | from importlib.metadata import version as _metadata_package_version 19 | TYPEGUARD_MAJOR_VERSION = int(_metadata_package_version('typeguard').split('.')[0]) 20 | 21 | def check_type(argname: str, value: object, expected_type: typing.Any) -> typing.Any: 22 | if TYPEGUARD_MAJOR_VERSION <= 2: 23 | return typeguard.check_type(argname=argname, value=value, expected_type=expected_type) # type:ignore 24 | else: 25 | if isinstance(value, jsii._reference_map.InterfaceDynamicProxy): # pyright: ignore [reportAttributeAccessIssue] 26 | pass 27 | else: 28 | if TYPEGUARD_MAJOR_VERSION == 3: 29 | typeguard.config.collection_check_strategy = typeguard.CollectionCheckStrategy.ALL_ITEMS # type:ignore 30 | typeguard.check_type(value=value, expected_type=expected_type) # type:ignore 31 | else: 32 | typeguard.check_type(value=value, expected_type=expected_type, collection_check_strategy=typeguard.CollectionCheckStrategy.ALL_ITEMS) # type:ignore 33 | 34 | from ._jsii import * 35 | 36 | 37 | @jsii.data_type( 38 | jsii_type=\\"1bc04b5291c26a46d918139138b992d2de976d6851d0893b0476b85bfbdfc6e6.Name\\", 39 | jsii_struct_bases=[], 40 | name_mapping={\\"first\\": \\"first\\", \\"last\\": \\"last\\", \\"middle\\": \\"middle\\"}, 41 | ) 42 | class Name: 43 | def __init__( 44 | self, 45 | *, 46 | first: builtins.str, 47 | last: builtins.str, 48 | middle: typing.Optional[builtins.str] = None, 49 | ) -> None: 50 | ''' 51 | :param first: 52 | :param last: 53 | :param middle: 54 | 55 | :schema: Name 56 | ''' 57 | if __debug__: 58 | type_hints = typing.get_type_hints(_typecheckingstub__a733313bf214654e62b2f9f69d233474d4c6277e0f71f4fd3acee58a8fc3c20d) 59 | check_type(argname=\\"argument first\\", value=first, expected_type=type_hints[\\"first\\"]) 60 | check_type(argname=\\"argument last\\", value=last, expected_type=type_hints[\\"last\\"]) 61 | check_type(argname=\\"argument middle\\", value=middle, expected_type=type_hints[\\"middle\\"]) 62 | self._values: typing.Dict[builtins.str, typing.Any] = { 63 | \\"first\\": first, 64 | \\"last\\": last, 65 | } 66 | if middle is not None: 67 | self._values[\\"middle\\"] = middle 68 | 69 | @builtins.property 70 | def first(self) -> builtins.str: 71 | ''' 72 | :schema: Name#first 73 | ''' 74 | result = self._values.get(\\"first\\") 75 | assert result is not None, \\"Required property 'first' is missing\\" 76 | return typing.cast(builtins.str, result) 77 | 78 | @builtins.property 79 | def last(self) -> builtins.str: 80 | ''' 81 | :schema: Name#last 82 | ''' 83 | result = self._values.get(\\"last\\") 84 | assert result is not None, \\"Required property 'last' is missing\\" 85 | return typing.cast(builtins.str, result) 86 | 87 | @builtins.property 88 | def middle(self) -> typing.Optional[builtins.str]: 89 | ''' 90 | :schema: Name#middle 91 | ''' 92 | result = self._values.get(\\"middle\\") 93 | return typing.cast(typing.Optional[builtins.str], result) 94 | 95 | def __eq__(self, rhs: typing.Any) -> builtins.bool: 96 | return isinstance(rhs, self.__class__) and rhs._values == self._values 97 | 98 | def __ne__(self, rhs: typing.Any) -> builtins.bool: 99 | return not (rhs == self) 100 | 101 | def __repr__(self) -> str: 102 | return \\"Name(%s)\\" % \\", \\".join( 103 | k + \\"=\\" + repr(v) for k, v in self._values.items() 104 | ) 105 | 106 | 107 | __all__ = [ 108 | \\"Name\\", 109 | ] 110 | 111 | publication.publish() 112 | 113 | def _typecheckingstub__a733313bf214654e62b2f9f69d233474d4c6277e0f71f4fd3acee58a8fc3c20d( 114 | *, 115 | first: builtins.str, 116 | last: builtins.str, 117 | middle: typing.Optional[builtins.str] = None, 118 | ) -> None: 119 | \\"\\"\\"Type checking stubs\\"\\"\\" 120 | pass 121 | " 122 | `; 123 | 124 | exports[`language bindings 2`] = ` 125 | "package org.myorg; 126 | 127 | /** 128 | */ 129 | @software.amazon.jsii.Jsii(module = org.myorg.$Module.class, fqn = \\"1bc04b5291c26a46d918139138b992d2de976d6851d0893b0476b85bfbdfc6e6.Name\\") 130 | @software.amazon.jsii.Jsii.Proxy(Name.Jsii$Proxy.class) 131 | public interface Name extends software.amazon.jsii.JsiiSerializable { 132 | 133 | /** 134 | */ 135 | @org.jetbrains.annotations.NotNull java.lang.String getFirst(); 136 | 137 | /** 138 | */ 139 | @org.jetbrains.annotations.NotNull java.lang.String getLast(); 140 | 141 | /** 142 | */ 143 | default @org.jetbrains.annotations.Nullable java.lang.String getMiddle() { 144 | return null; 145 | } 146 | 147 | /** 148 | * @return a {@link Builder} of {@link Name} 149 | */ 150 | static Builder builder() { 151 | return new Builder(); 152 | } 153 | /** 154 | * A builder for {@link Name} 155 | */ 156 | public static final class Builder implements software.amazon.jsii.Builder { 157 | java.lang.String first; 158 | java.lang.String last; 159 | java.lang.String middle; 160 | 161 | /** 162 | * Sets the value of {@link Name#getFirst} 163 | * @param first the value to be set. This parameter is required. 164 | * @return {@code this} 165 | */ 166 | public Builder first(java.lang.String first) { 167 | this.first = first; 168 | return this; 169 | } 170 | 171 | /** 172 | * Sets the value of {@link Name#getLast} 173 | * @param last the value to be set. This parameter is required. 174 | * @return {@code this} 175 | */ 176 | public Builder last(java.lang.String last) { 177 | this.last = last; 178 | return this; 179 | } 180 | 181 | /** 182 | * Sets the value of {@link Name#getMiddle} 183 | * @param middle the value to be set. 184 | * @return {@code this} 185 | */ 186 | public Builder middle(java.lang.String middle) { 187 | this.middle = middle; 188 | return this; 189 | } 190 | 191 | /** 192 | * Builds the configured instance. 193 | * @return a new instance of {@link Name} 194 | * @throws NullPointerException if any required attribute was not provided 195 | */ 196 | @Override 197 | public Name build() { 198 | return new Jsii$Proxy(this); 199 | } 200 | } 201 | 202 | /** 203 | * An implementation for {@link Name} 204 | */ 205 | @software.amazon.jsii.Internal 206 | final class Jsii$Proxy extends software.amazon.jsii.JsiiObject implements Name { 207 | private final java.lang.String first; 208 | private final java.lang.String last; 209 | private final java.lang.String middle; 210 | 211 | /** 212 | * Constructor that initializes the object based on values retrieved from the JsiiObject. 213 | * @param objRef Reference to the JSII managed object. 214 | */ 215 | protected Jsii$Proxy(final software.amazon.jsii.JsiiObjectRef objRef) { 216 | super(objRef); 217 | this.first = software.amazon.jsii.Kernel.get(this, \\"first\\", software.amazon.jsii.NativeType.forClass(java.lang.String.class)); 218 | this.last = software.amazon.jsii.Kernel.get(this, \\"last\\", software.amazon.jsii.NativeType.forClass(java.lang.String.class)); 219 | this.middle = software.amazon.jsii.Kernel.get(this, \\"middle\\", software.amazon.jsii.NativeType.forClass(java.lang.String.class)); 220 | } 221 | 222 | /** 223 | * Constructor that initializes the object based on literal property values passed by the {@link Builder}. 224 | */ 225 | protected Jsii$Proxy(final Builder builder) { 226 | super(software.amazon.jsii.JsiiObject.InitializationMode.JSII); 227 | this.first = java.util.Objects.requireNonNull(builder.first, \\"first is required\\"); 228 | this.last = java.util.Objects.requireNonNull(builder.last, \\"last is required\\"); 229 | this.middle = builder.middle; 230 | } 231 | 232 | @Override 233 | public final java.lang.String getFirst() { 234 | return this.first; 235 | } 236 | 237 | @Override 238 | public final java.lang.String getLast() { 239 | return this.last; 240 | } 241 | 242 | @Override 243 | public final java.lang.String getMiddle() { 244 | return this.middle; 245 | } 246 | 247 | @Override 248 | @software.amazon.jsii.Internal 249 | public com.fasterxml.jackson.databind.JsonNode $jsii$toJson() { 250 | final com.fasterxml.jackson.databind.ObjectMapper om = software.amazon.jsii.JsiiObjectMapper.INSTANCE; 251 | final com.fasterxml.jackson.databind.node.ObjectNode data = com.fasterxml.jackson.databind.node.JsonNodeFactory.instance.objectNode(); 252 | 253 | data.set(\\"first\\", om.valueToTree(this.getFirst())); 254 | data.set(\\"last\\", om.valueToTree(this.getLast())); 255 | if (this.getMiddle() != null) { 256 | data.set(\\"middle\\", om.valueToTree(this.getMiddle())); 257 | } 258 | 259 | final com.fasterxml.jackson.databind.node.ObjectNode struct = com.fasterxml.jackson.databind.node.JsonNodeFactory.instance.objectNode(); 260 | struct.set(\\"fqn\\", om.valueToTree(\\"1bc04b5291c26a46d918139138b992d2de976d6851d0893b0476b85bfbdfc6e6.Name\\")); 261 | struct.set(\\"data\\", data); 262 | 263 | final com.fasterxml.jackson.databind.node.ObjectNode obj = com.fasterxml.jackson.databind.node.JsonNodeFactory.instance.objectNode(); 264 | obj.set(\\"$jsii.struct\\", struct); 265 | 266 | return obj; 267 | } 268 | 269 | @Override 270 | public final boolean equals(final java.lang.Object o) { 271 | if (this == o) return true; 272 | if (o == null || getClass() != o.getClass()) return false; 273 | 274 | Name.Jsii$Proxy that = (Name.Jsii$Proxy) o; 275 | 276 | if (!first.equals(that.first)) return false; 277 | if (!last.equals(that.last)) return false; 278 | return this.middle != null ? this.middle.equals(that.middle) : that.middle == null; 279 | } 280 | 281 | @Override 282 | public final int hashCode() { 283 | int result = this.first.hashCode(); 284 | result = 31 * result + (this.last.hashCode()); 285 | result = 31 * result + (this.middle != null ? this.middle.hashCode() : 0); 286 | return result; 287 | } 288 | } 289 | } 290 | " 291 | `; 292 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /test/fixtures/eks.json: -------------------------------------------------------------------------------- 1 | { 2 | "typeName": "AWSQS::EKS::Cluster", 3 | "description": "A resource that creates Amazon Elastic Kubernetes Service (Amazon EKS) clusters.", 4 | "sourceUrl": "https://github.com/aws-quickstart/quickstart-amazon-eks-cluster-resource-provider.git", 5 | "documentationUrl": "https://github.com/aws-quickstart/quickstart-amazon-eks-cluster-resource-provider/blob/main/README.md", 6 | "definitions": { 7 | "KubernetesApiAccessEntry": { 8 | "type": "object", 9 | "additionalProperties": false, 10 | "properties": { 11 | "Arn": {"type": "string"}, 12 | "Username": {"type": "string"}, 13 | "Groups": {"type": "array", "items": {"type": "string"}} 14 | } 15 | }, 16 | "Provider": { 17 | "description": "AWS Key Management Service (AWS KMS) customer master key (CMK). Either the ARN or the alias can be used.", 18 | "type": "object", 19 | "additionalProperties": false, 20 | "properties": { 21 | "KeyArn": { 22 | "description": "Amazon Resource Name (ARN) or alias of the customer master key (CMK). The CMK must be symmetric, created in the same region as the cluster, and if the CMK was created in a different account, the user must have access to the CMK.", 23 | "type": "string" 24 | } 25 | } 26 | }, 27 | "EncryptionConfigEntry": { 28 | "description": "The encryption configuration for the cluster.", 29 | "type": "object", 30 | "additionalProperties": false, 31 | "properties": { 32 | "Resources": { 33 | "type": "array", 34 | "description": "Specifies the resources to be encrypted. The only supported value is \"secrets\".", 35 | "items": { 36 | "description": "Specifies the resources to be encrypted. The only supported value is \"secrets\".", 37 | "type": "string" 38 | } 39 | }, 40 | "Provider": { 41 | "$ref": "#/definitions/Provider" 42 | } 43 | } 44 | } 45 | }, 46 | "properties": { 47 | "Name": { 48 | "description": "A unique name for your cluster.", 49 | "type": "string", 50 | "minLength": 1 51 | }, 52 | "RoleArn": { 53 | "description": "Amazon Resource Name (ARN) of the AWS Identity and Access Management (IAM) role. This provides permissions for Amazon EKS to call other AWS APIs.", 54 | "type": "string" 55 | }, 56 | "LambdaRoleName": { 57 | "description": "Name of the AWS Identity and Access Management (IAM) role used for clusters that have the public endpoint disabled. this provides permissions for Lambda to be invoked and attach to the cluster VPC", 58 | "type": "string", 59 | "default": "CloudFormation-Kubernetes-VPC" 60 | }, 61 | "Version": { 62 | "description": "Desired Kubernetes version for your cluster. If you don't specify this value, the cluster uses the latest version from Amazon EKS.", 63 | "type": "string", 64 | "default": "1.19" 65 | }, 66 | "KubernetesNetworkConfig": { 67 | "description": "Network configuration for Amazon EKS cluster.\n\n", 68 | "type": "object", 69 | "additionalProperties": false, 70 | "properties": { 71 | "ServiceIpv4Cidr": { 72 | "description": "Specify the range from which cluster services will receive IPv4 addresses.", 73 | "type": "string" 74 | } 75 | } 76 | }, 77 | "ResourcesVpcConfig": { 78 | "description": "An object that represents the virtual private cloud (VPC) configuration to use for an Amazon EKS cluster.", 79 | "type": "object", 80 | "properties": { 81 | "SecurityGroupIds": { 82 | "description": "Specify one or more security groups for the cross-account elastic network interfaces that Amazon EKS creates to use to allow communication between your worker nodes and the Kubernetes control plane. If you don't specify a security group, the default security group for your VPC is used.", 83 | "type": "array", 84 | "items": {"type": "string"} 85 | }, 86 | "SubnetIds": { 87 | "description": "Specify subnets for your Amazon EKS worker nodes. Amazon EKS creates cross-account elastic network interfaces in these subnets to allow communication between your worker nodes and the Kubernetes control plane.", 88 | "type": "array", 89 | "items": {"type": "string"} 90 | }, 91 | "EndpointPublicAccess": { 92 | "description": "Set this value to false to disable public access to your cluster's Kubernetes API server endpoint. If you disable public access, your cluster's Kubernetes API server can only receive requests from within the cluster VPC. The default value for this parameter is true , which enables public access for your Kubernetes API server.", 93 | "type": "boolean" 94 | }, 95 | "EndpointPrivateAccess": { 96 | "description": "Set this value to true to enable private access for your cluster's Kubernetes API server endpoint. If you enable private access, Kubernetes API requests from within your cluster's VPC use the private VPC endpoint. The default value for this parameter is false , which disables private access for your Kubernetes API server. If you disable private access and you have worker nodes or AWS Fargate pods in the cluster, then ensure that publicAccessCidrs includes the necessary CIDR blocks for communication with the worker nodes or Fargate pods.", 97 | "type": "boolean" 98 | }, 99 | "PublicAccessCidrs": { 100 | "description": "The CIDR blocks that are allowed access to your cluster's public Kubernetes API server endpoint. Communication to the endpoint from addresses outside of the CIDR blocks that you specify is denied. The default value is 0.0.0.0/0 . If you've disabled private endpoint access and you have worker nodes or AWS Fargate pods in the cluster, then ensure that you specify the necessary CIDR blocks.", 101 | "type": "array", 102 | "items": {"type": "string"} 103 | } 104 | }, 105 | "required": ["SubnetIds"], 106 | "additionalProperties": false 107 | }, 108 | "EnabledClusterLoggingTypes": { 109 | "description": "Enables exporting of logs from the Kubernetes control plane to Amazon CloudWatch Logs. By default, logs from the cluster control plane are not exported to CloudWatch Logs. The valid log types are api, audit, authenticator, controllerManager, and scheduler.", 110 | "type": "array", 111 | "items": {"type": "string", "pattern": "^api$|^audit$|^authenticator$|^controllerManager$|^scheduler$"} 112 | }, 113 | "EncryptionConfig": { 114 | "description": "Encryption configuration for the cluster.", 115 | "type": "array", 116 | "items": { 117 | "$ref": "#/definitions/EncryptionConfigEntry" 118 | } 119 | }, 120 | "KubernetesApiAccess": { 121 | "type": "object", 122 | "additionalProperties": false, 123 | "properties": { 124 | "Roles": { 125 | "type": "array", 126 | "items": { 127 | "$ref": "#/definitions/KubernetesApiAccessEntry" 128 | } 129 | }, 130 | "Users": { 131 | "type": "array", 132 | "items": { 133 | "$ref": "#/definitions/KubernetesApiAccessEntry" 134 | } 135 | } 136 | } 137 | }, 138 | "Arn": { 139 | "description": "ARN of the cluster (e.g., `arn:aws:eks:us-west-2:666666666666:cluster/prod`).", 140 | "type": "string" 141 | }, 142 | "CertificateAuthorityData": { 143 | "description": "Certificate authority data for your cluster.", 144 | "type": "string" 145 | }, 146 | "ClusterSecurityGroupId": { 147 | "description": "Security group that was created by Amazon EKS for your cluster. Managed-node groups use this security group for control-plane-to-data-plane communications.", 148 | "type": "string" 149 | }, 150 | "Endpoint": { 151 | "description": "Endpoint for your Kubernetes API server (e.g., https://5E1D0CEXAMPLEA591B746AFC5AB30262.yl4.us-west-2.eks.amazonaws.com).", 152 | "type": "string" 153 | }, 154 | "EncryptionConfigKeyArn": { 155 | "description": "ARN or alias of the customer master key (CMK).", 156 | "type": "string" 157 | }, 158 | "OIDCIssuerURL": { 159 | "description": "Issuer URL for the OpenID Connect identity provider.", 160 | "type": "string" 161 | }, 162 | "Tags": { 163 | "type": "array", 164 | "uniqueItems": false, 165 | "items": { 166 | "type": "object", 167 | "additionalProperties": false, 168 | "properties": { 169 | "Value": { 170 | "type": "string" 171 | }, 172 | "Key": { 173 | "type": "string" 174 | } 175 | }, 176 | "required": [ 177 | "Value", 178 | "Key" 179 | ] 180 | } 181 | } 182 | }, 183 | "additionalProperties": false, 184 | "required": [ 185 | "RoleArn", 186 | "ResourcesVpcConfig" 187 | ], 188 | "readOnlyProperties": [ 189 | "/properties/Arn", 190 | "/properties/Endpoint", 191 | "/properties/ClusterSecurityGroupId", 192 | "/properties/CertificateAuthorityData", 193 | "/properties/EncryptionConfigKeyArn", 194 | "/properties/OIDCIssuerURL" 195 | ], 196 | "createOnlyProperties": [ 197 | "/properties/Name", 198 | "/properties/KubernetesNetworkConfig/ServiceIpv4Cidr", 199 | "/properties/RoleArn", 200 | "/properties/ResourcesVpcConfig/SubnetIds", 201 | "/properties/ResourcesVpcConfig/SecurityGroupIds" 202 | ], 203 | "primaryIdentifier": [ 204 | "/properties/Name" 205 | ], 206 | "handlers": { 207 | "create": { 208 | "permissions": [ 209 | "sts:GetCallerIdentity", 210 | "eks:CreateCluster", 211 | "eks:DescribeCluster", 212 | "eks:ListTagsForResource", 213 | "eks:TagResource", 214 | "iam:PassRole", 215 | "sts:AssumeRole", 216 | "lambda:UpdateFunctionConfiguration", 217 | "lambda:DeleteFunction", 218 | "lambda:GetFunction", 219 | "lambda:InvokeFunction", 220 | "lambda:CreateFunction", 221 | "lambda:UpdateFunctionCode", 222 | "ec2:DescribeVpcs", 223 | "ec2:DescribeSubnets", 224 | "ec2:DescribeSecurityGroups", 225 | "iam:PassRole", 226 | "cloudformation:ListExports", 227 | "kms:DescribeKey", 228 | "kms:CreateGrant" 229 | ] 230 | }, 231 | "read": { 232 | "permissions": [ 233 | "sts:GetCallerIdentity", 234 | "eks:DescribeCluster", 235 | "eks:ListTagsForResource", 236 | "lambda:UpdateFunctionConfiguration", 237 | "lambda:DeleteFunction", 238 | "lambda:GetFunction", 239 | "lambda:InvokeFunction", 240 | "lambda:CreateFunction", 241 | "lambda:UpdateFunctionCode", 242 | "ec2:DescribeVpcs", 243 | "ec2:DescribeSubnets", 244 | "ec2:DescribeSecurityGroups", 245 | "iam:PassRole", 246 | "cloudformation:ListExports", 247 | "kms:DescribeKey", 248 | "kms:CreateGrant" 249 | ] 250 | }, 251 | "update": { 252 | "permissions": [ 253 | "sts:GetCallerIdentity", 254 | "eks:DescribeCluster", 255 | "eks:UpdateClusterVersion", 256 | "eks:UpdateClusterConfig", 257 | "eks:ListTagsForResource", 258 | "eks:TagResource", 259 | "eks:UntagResource", 260 | "iam:PassRole", 261 | "lambda:UpdateFunctionConfiguration", 262 | "lambda:DeleteFunction", 263 | "lambda:GetFunction", 264 | "lambda:InvokeFunction", 265 | "lambda:CreateFunction", 266 | "lambda:UpdateFunctionCode", 267 | "ec2:DescribeVpcs", 268 | "ec2:DescribeSubnets", 269 | "ec2:DescribeSecurityGroups", 270 | "cloudformation:ListExports", 271 | "kms:DescribeKey", 272 | "kms:CreateGrant" 273 | ] 274 | }, 275 | "delete": { 276 | "permissions": [ 277 | "sts:GetCallerIdentity", 278 | "eks:DescribeCluster", 279 | "eks:ListTagsForResource", 280 | "eks:DeleteCluster", 281 | "lambda:UpdateFunctionConfiguration", 282 | "lambda:DeleteFunction", 283 | "lambda:GetFunction", 284 | "lambda:InvokeFunction", 285 | "lambda:CreateFunction", 286 | "lambda:UpdateFunctionCode", 287 | "ec2:DescribeVpcs", 288 | "ec2:DescribeSubnets", 289 | "ec2:DescribeSecurityGroups", 290 | "iam:PassRole", 291 | "cloudformation:ListExports", 292 | "kms:DescribeKey", 293 | "kms:CreateGrant" 294 | ] 295 | } 296 | } 297 | } 298 | 299 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [0.1.217](https://github.com/aws/json2jsii/compare/v0.1.216...v0.1.217) (2021-03-08) 6 | 7 | ### [0.1.216](https://github.com/aws/json2jsii/compare/v0.1.215...v0.1.216) (2021-03-08) 8 | 9 | ### [0.1.215](https://github.com/aws/json2jsii/compare/v0.1.214...v0.1.215) (2021-03-06) 10 | 11 | ### [0.1.214](https://github.com/aws/json2jsii/compare/v0.1.213...v0.1.214) (2021-03-05) 12 | 13 | ### [0.1.213](https://github.com/aws/json2jsii/compare/v0.1.212...v0.1.213) (2021-03-05) 14 | 15 | ### [0.1.212](https://github.com/aws/json2jsii/compare/v0.1.211...v0.1.212) (2021-03-05) 16 | 17 | ### [0.1.211](https://github.com/aws/json2jsii/compare/v0.1.210...v0.1.211) (2021-03-04) 18 | 19 | ### [0.1.210](https://github.com/aws/json2jsii/compare/v0.1.209...v0.1.210) (2021-03-04) 20 | 21 | ### [0.1.209](https://github.com/aws/json2jsii/compare/v0.1.208...v0.1.209) (2021-03-03) 22 | 23 | ### [0.1.208](https://github.com/aws/json2jsii/compare/v0.1.207...v0.1.208) (2021-03-03) 24 | 25 | ### [0.1.207](https://github.com/aws/json2jsii/compare/v0.1.206...v0.1.207) (2021-03-02) 26 | 27 | ### [0.1.206](https://github.com/aws/json2jsii/compare/v0.1.205...v0.1.206) (2021-03-02) 28 | 29 | ### [0.1.205](https://github.com/aws/json2jsii/compare/v0.1.204...v0.1.205) (2021-03-02) 30 | 31 | ### [0.1.204](https://github.com/aws/json2jsii/compare/v0.1.203...v0.1.204) (2021-03-01) 32 | 33 | ### [0.1.203](https://github.com/aws/json2jsii/compare/v0.1.202...v0.1.203) (2021-02-27) 34 | 35 | ### [0.1.202](https://github.com/aws/json2jsii/compare/v0.1.201...v0.1.202) (2021-02-26) 36 | 37 | ### [0.1.201](https://github.com/aws/json2jsii/compare/v0.1.200...v0.1.201) (2021-02-26) 38 | 39 | ### [0.1.200](https://github.com/aws/json2jsii/compare/v0.1.199...v0.1.200) (2021-02-25) 40 | 41 | ### [0.1.199](https://github.com/aws/json2jsii/compare/v0.1.198...v0.1.199) (2021-02-25) 42 | 43 | ### [0.1.198](https://github.com/aws/json2jsii/compare/v0.1.197...v0.1.198) (2021-02-24) 44 | 45 | ### [0.1.197](https://github.com/aws/json2jsii/compare/v0.1.196...v0.1.197) (2021-02-24) 46 | 47 | ### [0.1.196](https://github.com/aws/json2jsii/compare/v0.1.195...v0.1.196) (2021-02-24) 48 | 49 | ### [0.1.195](https://github.com/aws/json2jsii/compare/v0.1.194...v0.1.195) (2021-02-23) 50 | 51 | ### [0.1.194](https://github.com/aws/json2jsii/compare/v0.1.193...v0.1.194) (2021-02-23) 52 | 53 | ### [0.1.193](https://github.com/aws/json2jsii/compare/v0.1.192...v0.1.193) (2021-02-23) 54 | 55 | ### [0.1.192](https://github.com/aws/json2jsii/compare/v0.1.191...v0.1.192) (2021-02-22) 56 | 57 | ### [0.1.191](https://github.com/aws/json2jsii/compare/v0.1.190...v0.1.191) (2021-02-22) 58 | 59 | ### [0.1.190](https://github.com/aws/json2jsii/compare/v0.1.189...v0.1.190) (2021-02-22) 60 | 61 | ### [0.1.189](https://github.com/aws/json2jsii/compare/v0.1.188...v0.1.189) (2021-02-22) 62 | 63 | ### [0.1.188](https://github.com/aws/json2jsii/compare/v0.1.187...v0.1.188) (2021-01-15) 64 | 65 | ### [0.1.187](https://github.com/aws/json2jsii/compare/v0.1.186...v0.1.187) (2021-01-15) 66 | 67 | ### [0.1.186](https://github.com/aws/json2jsii/compare/v0.1.185...v0.1.186) (2021-01-14) 68 | 69 | ### [0.1.185](https://github.com/aws/json2jsii/compare/v0.1.184...v0.1.185) (2021-01-14) 70 | 71 | ### [0.1.184](https://github.com/aws/json2jsii/compare/v0.1.183...v0.1.184) (2021-01-13) 72 | 73 | ### [0.1.183](https://github.com/aws/json2jsii/compare/v0.1.181...v0.1.183) (2021-01-12) 74 | 75 | ### [0.1.182](https://github.com/aws/json2jsii/compare/v0.1.181...v0.1.182) (2021-01-12) 76 | 77 | ### [0.1.181](https://github.com/aws/json2jsii/compare/v0.1.180...v0.1.181) (2021-01-11) 78 | 79 | ### [0.1.180](https://github.com/aws/json2jsii/compare/v0.1.179...v0.1.180) (2021-01-11) 80 | 81 | ### [0.1.179](https://github.com/aws/json2jsii/compare/v0.1.178...v0.1.179) (2021-01-09) 82 | 83 | ### [0.1.178](https://github.com/aws/json2jsii/compare/v0.1.176...v0.1.178) (2021-01-08) 84 | 85 | ### [0.1.177](https://github.com/aws/json2jsii/compare/v0.1.176...v0.1.177) (2021-01-08) 86 | 87 | ### [0.1.176](https://github.com/aws/json2jsii/compare/v0.1.175...v0.1.176) (2021-01-07) 88 | 89 | ### [0.1.175](https://github.com/aws/json2jsii/compare/v0.1.174...v0.1.175) (2021-01-07) 90 | 91 | ### [0.1.174](https://github.com/aws/json2jsii/compare/v0.1.173...v0.1.174) (2021-01-06) 92 | 93 | ### [0.1.173](https://github.com/aws/json2jsii/compare/v0.1.172...v0.1.173) (2021-01-06) 94 | 95 | ### [0.1.172](https://github.com/aws/json2jsii/compare/v0.1.170...v0.1.172) (2021-01-05) 96 | 97 | ### [0.1.171](https://github.com/aws/json2jsii/compare/v0.1.170...v0.1.171) (2021-01-05) 98 | 99 | ### [0.1.170](https://github.com/aws/json2jsii/compare/v0.1.168...v0.1.170) (2021-01-04) 100 | 101 | ### [0.1.169](https://github.com/aws/json2jsii/compare/v0.1.168...v0.1.169) (2021-01-04) 102 | 103 | ### [0.1.168](https://github.com/aws/json2jsii/compare/v0.1.167...v0.1.168) (2021-01-02) 104 | 105 | ### [0.1.167](https://github.com/aws/json2jsii/compare/v0.1.166...v0.1.167) (2021-01-01) 106 | 107 | ### [0.1.166](https://github.com/aws/json2jsii/compare/v0.1.165...v0.1.166) (2021-01-01) 108 | 109 | ### [0.1.165](https://github.com/aws/json2jsii/compare/v0.1.164...v0.1.165) (2020-12-31) 110 | 111 | ### [0.1.164](https://github.com/aws/json2jsii/compare/v0.1.163...v0.1.164) (2020-12-31) 112 | 113 | ### [0.1.163](https://github.com/aws/json2jsii/compare/v0.1.162...v0.1.163) (2020-12-30) 114 | 115 | ### [0.1.162](https://github.com/aws/json2jsii/compare/v0.1.161...v0.1.162) (2020-12-30) 116 | 117 | ### [0.1.161](https://github.com/aws/json2jsii/compare/v0.1.159...v0.1.161) (2020-12-29) 118 | 119 | ### [0.1.160](https://github.com/aws/json2jsii/compare/v0.1.159...v0.1.160) (2020-12-29) 120 | 121 | ### [0.1.159](https://github.com/aws/json2jsii/compare/v0.1.158...v0.1.159) (2020-12-28) 122 | 123 | ### [0.1.158](https://github.com/aws/json2jsii/compare/v0.1.157...v0.1.158) (2020-12-28) 124 | 125 | ### [0.1.157](https://github.com/aws/json2jsii/compare/v0.1.156...v0.1.157) (2020-12-26) 126 | 127 | ### 0.1.156 (2020-12-25) 128 | 129 | 130 | ### Features 131 | 132 | * type aliases ([#183](https://github.com/aws/json2jsii/issues/183)) ([d39695d](https://github.com/aws/json2jsii/commit/d39695dcdef3a6cee0723b28d0d85e1868a56200)), closes [awslabs/cdk8s#370](https://github.com/awslabs/cdk8s/issues/370) 133 | 134 | 135 | ### Bug Fixes 136 | 137 | * empty lines are indented ([5c4928e](https://github.com/aws/json2jsii/commit/5c4928ef3d154c90978e14692bfd83b1d2e86e68)) 138 | * export `Code` ([#134](https://github.com/aws/json2jsii/issues/134)) ([2d3fb58](https://github.com/aws/json2jsii/commit/2d3fb58575357934f8dd51ca7639f08ddbc5728b)) 139 | * Pascal case for fully-lowercase types ([#2](https://github.com/aws/json2jsii/issues/2)) ([c1e9170](https://github.com/aws/json2jsii/commit/c1e91708b6e08120bfb61b7927927ed978d63a51)) 140 | 141 | ### 0.1.155 (2020-12-24) 142 | 143 | 144 | ### Features 145 | 146 | * type aliases ([#183](https://github.com/aws/json2jsii/issues/183)) ([d39695d](https://github.com/aws/json2jsii/commit/d39695dcdef3a6cee0723b28d0d85e1868a56200)), closes [awslabs/cdk8s#370](https://github.com/awslabs/cdk8s/issues/370) 147 | 148 | 149 | ### Bug Fixes 150 | 151 | * empty lines are indented ([5c4928e](https://github.com/aws/json2jsii/commit/5c4928ef3d154c90978e14692bfd83b1d2e86e68)) 152 | * export `Code` ([#134](https://github.com/aws/json2jsii/issues/134)) ([2d3fb58](https://github.com/aws/json2jsii/commit/2d3fb58575357934f8dd51ca7639f08ddbc5728b)) 153 | * Pascal case for fully-lowercase types ([#2](https://github.com/aws/json2jsii/issues/2)) ([c1e9170](https://github.com/aws/json2jsii/commit/c1e91708b6e08120bfb61b7927927ed978d63a51)) 154 | 155 | ### 0.1.154 (2020-12-24) 156 | 157 | 158 | ### Features 159 | 160 | * type aliases ([#183](https://github.com/aws/json2jsii/issues/183)) ([d39695d](https://github.com/aws/json2jsii/commit/d39695dcdef3a6cee0723b28d0d85e1868a56200)), closes [awslabs/cdk8s#370](https://github.com/awslabs/cdk8s/issues/370) 161 | 162 | 163 | ### Bug Fixes 164 | 165 | * empty lines are indented ([5c4928e](https://github.com/aws/json2jsii/commit/5c4928ef3d154c90978e14692bfd83b1d2e86e68)) 166 | * export `Code` ([#134](https://github.com/aws/json2jsii/issues/134)) ([2d3fb58](https://github.com/aws/json2jsii/commit/2d3fb58575357934f8dd51ca7639f08ddbc5728b)) 167 | * Pascal case for fully-lowercase types ([#2](https://github.com/aws/json2jsii/issues/2)) ([c1e9170](https://github.com/aws/json2jsii/commit/c1e91708b6e08120bfb61b7927927ed978d63a51)) 168 | 169 | ### 0.1.153 (2020-12-23) 170 | 171 | 172 | ### Features 173 | 174 | * type aliases ([#183](https://github.com/aws/json2jsii/issues/183)) ([d39695d](https://github.com/aws/json2jsii/commit/d39695dcdef3a6cee0723b28d0d85e1868a56200)), closes [awslabs/cdk8s#370](https://github.com/awslabs/cdk8s/issues/370) 175 | 176 | 177 | ### Bug Fixes 178 | 179 | * empty lines are indented ([5c4928e](https://github.com/aws/json2jsii/commit/5c4928ef3d154c90978e14692bfd83b1d2e86e68)) 180 | * export `Code` ([#134](https://github.com/aws/json2jsii/issues/134)) ([2d3fb58](https://github.com/aws/json2jsii/commit/2d3fb58575357934f8dd51ca7639f08ddbc5728b)) 181 | * Pascal case for fully-lowercase types ([#2](https://github.com/aws/json2jsii/issues/2)) ([c1e9170](https://github.com/aws/json2jsii/commit/c1e91708b6e08120bfb61b7927927ed978d63a51)) 182 | 183 | ### 0.1.152 (2020-12-22) 184 | 185 | 186 | ### Features 187 | 188 | * type aliases ([#183](https://github.com/aws/json2jsii/issues/183)) ([d39695d](https://github.com/aws/json2jsii/commit/d39695dcdef3a6cee0723b28d0d85e1868a56200)), closes [awslabs/cdk8s#370](https://github.com/awslabs/cdk8s/issues/370) 189 | 190 | 191 | ### Bug Fixes 192 | 193 | * empty lines are indented ([5c4928e](https://github.com/aws/json2jsii/commit/5c4928ef3d154c90978e14692bfd83b1d2e86e68)) 194 | * export `Code` ([#134](https://github.com/aws/json2jsii/issues/134)) ([2d3fb58](https://github.com/aws/json2jsii/commit/2d3fb58575357934f8dd51ca7639f08ddbc5728b)) 195 | * Pascal case for fully-lowercase types ([#2](https://github.com/aws/json2jsii/issues/2)) ([c1e9170](https://github.com/aws/json2jsii/commit/c1e91708b6e08120bfb61b7927927ed978d63a51)) 196 | 197 | ### 0.1.151 (2020-12-22) 198 | 199 | ### 0.1.150 (2020-12-22) 200 | 201 | ### 0.1.149 (2020-12-21) 202 | 203 | ### 0.1.148 (2020-12-20) 204 | 205 | ### 0.1.147 (2020-12-18) 206 | 207 | ### 0.1.146 (2020-12-18) 208 | 209 | ### 0.1.145 (2020-12-17) 210 | 211 | ### 0.1.144 (2020-12-17) 212 | 213 | ### 0.1.143 (2020-12-16) 214 | 215 | ### 0.1.142 (2020-12-15) 216 | 217 | ### 0.1.141 (2020-12-15) 218 | 219 | ### 0.1.140 (2020-12-14) 220 | 221 | ### 0.1.139 (2020-12-12) 222 | 223 | ### 0.1.138 (2020-12-11) 224 | 225 | ### 0.1.137 (2020-12-11) 226 | 227 | ### 0.1.136 (2020-12-10) 228 | 229 | ### 0.1.135 (2020-12-09) 230 | 231 | ### 0.1.134 (2020-12-08) 232 | 233 | ### 0.1.133 (2020-12-08) 234 | 235 | ### 0.1.132 (2020-12-08) 236 | 237 | ### 0.1.131 (2020-12-07) 238 | 239 | ### 0.1.130 (2020-12-07) 240 | 241 | ### 0.1.129 (2020-12-06) 242 | 243 | ### 0.1.128 (2020-12-04) 244 | 245 | ### 0.1.127 (2020-12-04) 246 | 247 | ### 0.1.126 (2020-12-03) 248 | 249 | ### 0.1.125 (2020-12-03) 250 | 251 | ### 0.1.124 (2020-12-02) 252 | 253 | ### 0.1.123 (2020-12-01) 254 | 255 | ### 0.1.122 (2020-11-30) 256 | 257 | ### 0.1.121 (2020-11-30) 258 | 259 | ### 0.1.120 (2020-11-28) 260 | 261 | ### 0.1.119 (2020-11-27) 262 | 263 | ### 0.1.118 (2020-11-27) 264 | 265 | ### 0.1.117 (2020-11-26) 266 | 267 | ### 0.1.116 (2020-11-26) 268 | 269 | ### 0.1.115 (2020-11-25) 270 | 271 | ### 0.1.114 (2020-11-25) 272 | 273 | ### 0.1.113 (2020-11-24) 274 | 275 | ### 0.1.112 (2020-11-24) 276 | 277 | ### 0.1.111 (2020-11-23) 278 | 279 | ### 0.1.110 (2020-11-20) 280 | 281 | ### 0.1.109 (2020-11-19) 282 | 283 | ### 0.1.108 (2020-11-19) 284 | 285 | ### 0.1.107 (2020-11-18) 286 | 287 | ### 0.1.106 (2020-11-18) 288 | 289 | ### 0.1.105 (2020-11-17) 290 | 291 | ### 0.1.104 (2020-11-16) 292 | 293 | 294 | ### Features 295 | 296 | * type aliases ([#183](https://github.com/aws/json2jsii/issues/183)) ([d39695d](https://github.com/aws/json2jsii/commit/d39695dcdef3a6cee0723b28d0d85e1868a56200)), closes [awslabs/cdk8s#370](https://github.com/awslabs/cdk8s/issues/370) 297 | 298 | ### 0.1.103 (2020-11-16) 299 | 300 | ### 0.1.102 (2020-11-16) 301 | 302 | ### 0.1.101 (2020-11-13) 303 | 304 | ### 0.1.100 (2020-11-13) 305 | 306 | ### 0.1.99 (2020-11-12) 307 | 308 | ### 0.1.98 (2020-11-12) 309 | 310 | ### 0.1.97 (2020-11-11) 311 | 312 | ### 0.1.96 (2020-11-11) 313 | 314 | ### 0.1.95 (2020-11-10) 315 | 316 | ### 0.1.94 (2020-11-10) 317 | 318 | ### 0.1.93 (2020-11-09) 319 | 320 | ### 0.1.92 (2020-11-09) 321 | 322 | ### 0.1.91 (2020-11-08) 323 | 324 | ### 0.1.90 (2020-11-07) 325 | 326 | ### 0.1.89 (2020-11-06) 327 | 328 | ### 0.1.88 (2020-11-06) 329 | 330 | ### 0.1.87 (2020-11-05) 331 | 332 | ### 0.1.86 (2020-11-04) 333 | 334 | ### 0.1.85 (2020-11-04) 335 | 336 | ### 0.1.84 (2020-11-03) 337 | 338 | ### 0.1.83 (2020-11-03) 339 | 340 | ### 0.1.82 (2020-11-02) 341 | 342 | ### 0.1.81 (2020-11-02) 343 | 344 | ### 0.1.80 (2020-10-30) 345 | 346 | ### 0.1.79 (2020-10-30) 347 | 348 | ### 0.1.78 (2020-10-29) 349 | 350 | ### 0.1.77 (2020-10-29) 351 | 352 | ### 0.1.76 (2020-10-28) 353 | 354 | ### 0.1.75 (2020-10-28) 355 | 356 | ### 0.1.74 (2020-10-27) 357 | 358 | ### 0.1.73 (2020-10-27) 359 | 360 | ### 0.1.72 (2020-10-26) 361 | 362 | ### 0.1.71 (2020-10-26) 363 | 364 | ### 0.1.70 (2020-10-26) 365 | 366 | ### 0.1.69 (2020-10-25) 367 | 368 | 369 | ### Bug Fixes 370 | 371 | * export `Code` ([#134](https://github.com/aws/json2jsii/issues/134)) ([2d3fb58](https://github.com/aws/json2jsii/commit/2d3fb58575357934f8dd51ca7639f08ddbc5728b)) 372 | 373 | ### 0.1.68 (2020-10-24) 374 | 375 | ### 0.1.67 (2020-10-23) 376 | 377 | ### 0.1.66 (2020-10-23) 378 | 379 | ### 0.1.65 (2020-10-22) 380 | 381 | ### 0.1.64 (2020-10-22) 382 | 383 | ### 0.1.63 (2020-10-21) 384 | 385 | ### 0.1.62 (2020-10-21) 386 | 387 | ### 0.1.61 (2020-10-20) 388 | 389 | ### 0.1.60 (2020-10-20) 390 | 391 | ### 0.1.59 (2020-10-19) 392 | 393 | ### 0.1.58 (2020-10-18) 394 | 395 | ### 0.1.57 (2020-10-16) 396 | 397 | ### 0.1.56 (2020-10-16) 398 | 399 | ### 0.1.55 (2020-10-15) 400 | 401 | ### 0.1.54 (2020-10-15) 402 | 403 | ### 0.1.53 (2020-10-14) 404 | 405 | ### 0.1.52 (2020-10-14) 406 | 407 | ### 0.1.51 (2020-10-13) 408 | 409 | ### 0.1.50 (2020-10-13) 410 | 411 | ### 0.1.49 (2020-10-12) 412 | 413 | ### 0.1.48 (2020-10-12) 414 | 415 | ### 0.1.47 (2020-10-11) 416 | 417 | ### 0.1.46 (2020-10-09) 418 | 419 | ### 0.1.45 (2020-10-09) 420 | 421 | ### 0.1.44 (2020-10-08) 422 | 423 | ### 0.1.43 (2020-10-07) 424 | 425 | ### 0.1.42 (2020-10-06) 426 | 427 | ### 0.1.41 (2020-10-06) 428 | 429 | ### 0.1.40 (2020-10-05) 430 | 431 | ### 0.1.39 (2020-10-04) 432 | 433 | ### 0.1.38 (2020-10-01) 434 | 435 | ### 0.1.37 (2020-09-30) 436 | 437 | ### 0.1.36 (2020-09-28) 438 | 439 | ### 0.1.35 (2020-09-22) 440 | 441 | ### 0.1.34 (2020-09-21) 442 | 443 | ### 0.1.33 (2020-09-17) 444 | 445 | ### 0.1.32 (2020-09-14) 446 | 447 | ### 0.1.31 (2020-09-14) 448 | 449 | ### 0.1.30 (2020-09-11) 450 | 451 | ### 0.1.29 (2020-09-09) 452 | 453 | ### 0.1.28 (2020-09-07) 454 | 455 | ### 0.1.27 (2020-09-03) 456 | 457 | ### 0.1.26 (2020-09-03) 458 | 459 | ### 0.1.25 (2020-09-02) 460 | 461 | ### 0.1.24 (2020-09-02) 462 | 463 | ### 0.1.23 (2020-09-01) 464 | 465 | ### 0.1.22 (2020-08-28) 466 | 467 | ### 0.1.21 (2020-08-27) 468 | 469 | ### 0.1.20 (2020-08-26) 470 | 471 | ### 0.1.19 (2020-08-26) 472 | 473 | ### 0.1.18 (2020-08-25) 474 | 475 | ### 0.1.17 (2020-08-24) 476 | 477 | ### 0.1.16 (2020-08-21) 478 | 479 | ### 0.1.15 (2020-08-19) 480 | 481 | ### 0.1.14 (2020-08-17) 482 | 483 | ### 0.1.13 (2020-08-17) 484 | 485 | ### 0.1.12 (2020-08-13) 486 | 487 | ### 0.1.11 (2020-08-12) 488 | 489 | ### 0.1.10 (2020-08-12) 490 | 491 | ### 0.1.9 (2020-08-11) 492 | 493 | ### 0.1.8 (2020-08-06) 494 | 495 | ### 0.1.7 (2020-08-05) 496 | 497 | ### 0.1.6 (2020-08-03) 498 | 499 | ### 0.1.5 (2020-07-29) 500 | 501 | ### [0.1.4](https://github.com/joe/schmo/compare/v0.1.3...v0.1.4) (2020-07-16) 502 | 503 | 504 | ### Bug Fixes 505 | 506 | * Pascal case for fully-lowercase types ([#2](https://github.com/joe/schmo/issues/2)) ([c1e9170](https://github.com/joe/schmo/commit/c1e91708b6e08120bfb61b7927927ed978d63a51)) 507 | 508 | ### [0.1.3](https://github.com/joe/schmo/compare/v0.1.2...v0.1.3) (2020-07-15) 509 | 510 | ### [0.1.2](https://github.com/joe/schmo/compare/v0.1.1...v0.1.2) (2020-06-17) 511 | 512 | 513 | ### Bug Fixes 514 | 515 | * empty lines are indented ([5c4928e](https://github.com/joe/schmo/commit/5c4928ef3d154c90978e14692bfd83b1d2e86e68)) 516 | 517 | ### 0.1.1 (2020-06-17) 518 | -------------------------------------------------------------------------------- /test/__snapshots__/tojson.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`any 1`] = ` 4 | "/** 5 | * @schema MyStruct 6 | */ 7 | export interface MyStruct { 8 | /** 9 | * @schema MyStruct#MyAny 10 | */ 11 | readonly myAny?: any; 12 | } 13 | 14 | /** 15 | * Converts an object of type 'MyStruct' to JSON representation. 16 | */ 17 | /* eslint-disable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 18 | export function toJson_MyStruct(obj: MyStruct | undefined): Record | undefined { 19 | if (obj === undefined) { return undefined; } 20 | const result = { 21 | 'MyAny': obj.myAny, 22 | }; 23 | // filter undefined values 24 | return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {}); 25 | } 26 | /* eslint-enable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 27 | " 28 | `; 29 | 30 | exports[`arrays of complex types 1`] = ` 31 | "/** 32 | * @schema MyStruct 33 | */ 34 | export interface MyStruct { 35 | /** 36 | * @schema MyStruct#Array_Of_complex 37 | */ 38 | readonly arrayOfComplex?: MyType[]; 39 | } 40 | 41 | /** 42 | * Converts an object of type 'MyStruct' to JSON representation. 43 | */ 44 | /* eslint-disable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 45 | export function toJson_MyStruct(obj: MyStruct | undefined): Record | undefined { 46 | if (obj === undefined) { return undefined; } 47 | const result = { 48 | 'Array_Of_complex': obj.arrayOfComplex?.map(y => toJson_MyType(y)), 49 | }; 50 | // filter undefined values 51 | return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {}); 52 | } 53 | /* eslint-enable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 54 | 55 | /** 56 | * @schema MyType 57 | */ 58 | export interface MyType { 59 | /** 60 | * @schema MyType#Foo 61 | */ 62 | readonly foo?: number; 63 | 64 | /** 65 | * @schema MyType#Bar 66 | */ 67 | readonly bar?: string; 68 | } 69 | 70 | /** 71 | * Converts an object of type 'MyType' to JSON representation. 72 | */ 73 | /* eslint-disable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 74 | export function toJson_MyType(obj: MyType | undefined): Record | undefined { 75 | if (obj === undefined) { return undefined; } 76 | const result = { 77 | 'Foo': obj.foo, 78 | 'Bar': obj.bar, 79 | }; 80 | // filter undefined values 81 | return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {}); 82 | } 83 | /* eslint-enable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 84 | " 85 | `; 86 | 87 | exports[`arrays of primitives 1`] = ` 88 | "/** 89 | * @schema MyStruct 90 | */ 91 | export interface MyStruct { 92 | /** 93 | * @schema MyStruct#ArrayProp 94 | */ 95 | readonly arrayProp?: string[]; 96 | } 97 | 98 | /** 99 | * Converts an object of type 'MyStruct' to JSON representation. 100 | */ 101 | /* eslint-disable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 102 | export function toJson_MyStruct(obj: MyStruct | undefined): Record | undefined { 103 | if (obj === undefined) { return undefined; } 104 | const result = { 105 | 'ArrayProp': obj.arrayProp?.map(y => y), 106 | }; 107 | // filter undefined values 108 | return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {}); 109 | } 110 | /* eslint-enable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 111 | " 112 | `; 113 | 114 | exports[`arrays with items 1`] = ` 115 | "/** 116 | * @schema MyStruct 117 | */ 118 | export interface MyStruct { 119 | /** 120 | * @schema MyStruct#ArrayProp 121 | */ 122 | readonly arrayProp?: string[]; 123 | } 124 | 125 | /** 126 | * Converts an object of type 'MyStruct' to JSON representation. 127 | */ 128 | /* eslint-disable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 129 | export function toJson_MyStruct(obj: MyStruct | undefined): Record | undefined { 130 | if (obj === undefined) { return undefined; } 131 | const result = { 132 | 'ArrayProp': obj.arrayProp?.map(y => y), 133 | }; 134 | // filter undefined values 135 | return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {}); 136 | } 137 | /* eslint-enable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 138 | " 139 | `; 140 | 141 | exports[`arrays without items 1`] = ` 142 | "/** 143 | * @schema MyStruct 144 | */ 145 | export interface MyStruct { 146 | /** 147 | * @schema MyStruct#ArrayProp 148 | */ 149 | readonly arrayProp?: any[]; 150 | } 151 | 152 | /** 153 | * Converts an object of type 'MyStruct' to JSON representation. 154 | */ 155 | /* eslint-disable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 156 | export function toJson_MyStruct(obj: MyStruct | undefined): Record | undefined { 157 | if (obj === undefined) { return undefined; } 158 | const result = { 159 | 'ArrayProp': obj.arrayProp?.map(y => y), 160 | }; 161 | // filter undefined values 162 | return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {}); 163 | } 164 | /* eslint-enable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 165 | " 166 | `; 167 | 168 | exports[`complex types 1`] = ` 169 | "/** 170 | * @schema MyStruct 171 | */ 172 | export interface MyStruct { 173 | /** 174 | * @schema MyStruct#ComplexType 175 | */ 176 | readonly complexType?: MyType; 177 | } 178 | 179 | /** 180 | * Converts an object of type 'MyStruct' to JSON representation. 181 | */ 182 | /* eslint-disable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 183 | export function toJson_MyStruct(obj: MyStruct | undefined): Record | undefined { 184 | if (obj === undefined) { return undefined; } 185 | const result = { 186 | 'ComplexType': toJson_MyType(obj.complexType), 187 | }; 188 | // filter undefined values 189 | return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {}); 190 | } 191 | /* eslint-enable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 192 | 193 | /** 194 | * @schema MyType 195 | */ 196 | export interface MyType { 197 | /** 198 | * @schema MyType#Foo 199 | */ 200 | readonly foo?: number; 201 | 202 | /** 203 | * @schema MyType#Bar 204 | */ 205 | readonly bar?: string; 206 | 207 | /** 208 | * @schema MyType#Nested 209 | */ 210 | readonly nested: YourType; 211 | } 212 | 213 | /** 214 | * Converts an object of type 'MyType' to JSON representation. 215 | */ 216 | /* eslint-disable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 217 | export function toJson_MyType(obj: MyType | undefined): Record | undefined { 218 | if (obj === undefined) { return undefined; } 219 | const result = { 220 | 'Foo': obj.foo, 221 | 'Bar': obj.bar, 222 | 'Nested': toJson_YourType(obj.nested), 223 | }; 224 | // filter undefined values 225 | return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {}); 226 | } 227 | /* eslint-enable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 228 | 229 | /** 230 | * @schema YourType 231 | */ 232 | export interface YourType { 233 | /** 234 | * @schema YourType#John 235 | */ 236 | readonly john?: string[]; 237 | } 238 | 239 | /** 240 | * Converts an object of type 'YourType' to JSON representation. 241 | */ 242 | /* eslint-disable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 243 | export function toJson_YourType(obj: YourType | undefined): Record | undefined { 244 | if (obj === undefined) { return undefined; } 245 | const result = { 246 | 'John': obj.john?.map(y => y), 247 | }; 248 | // filter undefined values 249 | return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {}); 250 | } 251 | /* eslint-enable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 252 | " 253 | `; 254 | 255 | exports[`date 1`] = ` 256 | "/** 257 | * @schema MyStruct 258 | */ 259 | export interface MyStruct { 260 | /** 261 | * @schema MyStruct#DateTime 262 | */ 263 | readonly dateTime?: Date; 264 | } 265 | 266 | /** 267 | * Converts an object of type 'MyStruct' to JSON representation. 268 | */ 269 | /* eslint-disable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 270 | export function toJson_MyStruct(obj: MyStruct | undefined): Record | undefined { 271 | if (obj === undefined) { return undefined; } 272 | const result = { 273 | 'DateTime': obj.dateTime?.toISOString(), 274 | }; 275 | // filter undefined values 276 | return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {}); 277 | } 278 | /* eslint-enable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 279 | " 280 | `; 281 | 282 | exports[`enums 1`] = ` 283 | "/** 284 | * @schema MyStruct 285 | */ 286 | export interface MyStruct { 287 | /** 288 | * @schema MyStruct#MyEnum 289 | */ 290 | readonly myEnum?: MyStructMyEnum; 291 | 292 | /** 293 | * @schema MyStruct#YourEnum 294 | */ 295 | readonly yourEnum?: MyStructYourEnum; 296 | } 297 | 298 | /** 299 | * Converts an object of type 'MyStruct' to JSON representation. 300 | */ 301 | /* eslint-disable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 302 | export function toJson_MyStruct(obj: MyStruct | undefined): Record | undefined { 303 | if (obj === undefined) { return undefined; } 304 | const result = { 305 | 'MyEnum': obj.myEnum, 306 | 'YourEnum': obj.yourEnum, 307 | }; 308 | // filter undefined values 309 | return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {}); 310 | } 311 | /* eslint-enable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 312 | 313 | /** 314 | * @schema MyStructMyEnum 315 | */ 316 | export enum MyStructMyEnum { 317 | /** one */ 318 | ONE = \\"one\\", 319 | /** two */ 320 | TWO = \\"two\\", 321 | /** three */ 322 | THREE = \\"three\\", 323 | } 324 | 325 | /** 326 | * @schema MyStructYourEnum 327 | */ 328 | export enum MyStructYourEnum { 329 | /** jo */ 330 | JO = \\"jo\\", 331 | /** shmo */ 332 | SHMO = \\"shmo\\", 333 | } 334 | " 335 | `; 336 | 337 | exports[`maps of complex 1`] = ` 338 | "/** 339 | * @schema MyStruct 340 | */ 341 | export interface MyStruct { 342 | /** 343 | * @schema MyStruct#ComplexMap 344 | */ 345 | readonly complexMap?: { [key: string]: MyType }; 346 | } 347 | 348 | /** 349 | * Converts an object of type 'MyStruct' to JSON representation. 350 | */ 351 | /* eslint-disable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 352 | export function toJson_MyStruct(obj: MyStruct | undefined): Record | undefined { 353 | if (obj === undefined) { return undefined; } 354 | const result = { 355 | 'ComplexMap': ((obj.complexMap) === undefined) ? undefined : (Object.entries(obj.complexMap).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: toJson_MyType(i[1]) }), {})), 356 | }; 357 | // filter undefined values 358 | return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {}); 359 | } 360 | /* eslint-enable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 361 | 362 | /** 363 | * @schema MyType 364 | */ 365 | export interface MyType { 366 | /** 367 | * @schema MyType#Foo 368 | */ 369 | readonly foo?: number; 370 | 371 | /** 372 | * @schema MyType#Bar 373 | */ 374 | readonly bar?: string; 375 | } 376 | 377 | /** 378 | * Converts an object of type 'MyType' to JSON representation. 379 | */ 380 | /* eslint-disable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 381 | export function toJson_MyType(obj: MyType | undefined): Record | undefined { 382 | if (obj === undefined) { return undefined; } 383 | const result = { 384 | 'Foo': obj.foo, 385 | 'Bar': obj.bar, 386 | }; 387 | // filter undefined values 388 | return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {}); 389 | } 390 | /* eslint-enable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 391 | " 392 | `; 393 | 394 | exports[`maps of primitives 1`] = ` 395 | "/** 396 | * @schema MyStruct 397 | */ 398 | export interface MyStruct { 399 | /** 400 | * @schema MyStruct#StringMap 401 | */ 402 | readonly stringMap?: { [key: string]: string }; 403 | 404 | /** 405 | * @schema MyStruct#NumberMap 406 | */ 407 | readonly numberMap?: { [key: string]: number }; 408 | } 409 | 410 | /** 411 | * Converts an object of type 'MyStruct' to JSON representation. 412 | */ 413 | /* eslint-disable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 414 | export function toJson_MyStruct(obj: MyStruct | undefined): Record | undefined { 415 | if (obj === undefined) { return undefined; } 416 | const result = { 417 | 'StringMap': ((obj.stringMap) === undefined) ? undefined : (Object.entries(obj.stringMap).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {})), 418 | 'NumberMap': ((obj.numberMap) === undefined) ? undefined : (Object.entries(obj.numberMap).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {})), 419 | }; 420 | // filter undefined values 421 | return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {}); 422 | } 423 | /* eslint-enable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 424 | " 425 | `; 426 | 427 | exports[`names can get crazy 1`] = ` 428 | "/** 429 | * @schema MyStruct 430 | */ 431 | export interface MyStruct { 432 | /** 433 | * @schema MyStruct#hyphen-name 434 | */ 435 | readonly hyphenName?: string; 436 | 437 | /** 438 | * @schema MyStruct#$nameWithDolar 439 | */ 440 | readonly nameWithDolar?: string; 441 | 442 | /** 443 | * @schema MyStruct#name with spaces 444 | */ 445 | readonly nameWithSpaces?: string; 446 | 447 | /** 448 | * @schema MyStruct#x-extension 449 | */ 450 | readonly xExtension?: string; 451 | } 452 | 453 | /** 454 | * Converts an object of type 'MyStruct' to JSON representation. 455 | */ 456 | /* eslint-disable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 457 | export function toJson_MyStruct(obj: MyStruct | undefined): Record | undefined { 458 | if (obj === undefined) { return undefined; } 459 | const result = { 460 | 'hyphen-name': obj.hyphenName, 461 | '$nameWithDolar': obj.nameWithDolar, 462 | 'name with spaces': obj.nameWithSpaces, 463 | 'x-extension': obj.xExtension, 464 | }; 465 | // filter undefined values 466 | return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {}); 467 | } 468 | /* eslint-enable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 469 | " 470 | `; 471 | 472 | exports[`primitives 1`] = ` 473 | "/** 474 | * @schema MyStruct 475 | */ 476 | export interface MyStruct { 477 | /** 478 | * @schema MyStruct#StringProperty 479 | */ 480 | readonly stringProperty: string; 481 | 482 | /** 483 | * @schema MyStruct#BooleanProperty 484 | */ 485 | readonly booleanProperty?: boolean; 486 | 487 | /** 488 | * @schema MyStruct#NumberProperty 489 | */ 490 | readonly numberProperty?: number; 491 | 492 | /** 493 | * @schema MyStruct#With_UnderScore 494 | */ 495 | readonly withUnderScore?: number; 496 | } 497 | 498 | /** 499 | * Converts an object of type 'MyStruct' to JSON representation. 500 | */ 501 | /* eslint-disable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 502 | export function toJson_MyStruct(obj: MyStruct | undefined): Record | undefined { 503 | if (obj === undefined) { return undefined; } 504 | const result = { 505 | 'StringProperty': obj.stringProperty, 506 | 'BooleanProperty': obj.booleanProperty, 507 | 'NumberProperty': obj.numberProperty, 508 | 'With_UnderScore': obj.withUnderScore, 509 | }; 510 | // filter undefined values 511 | return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {}); 512 | } 513 | /* eslint-enable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 514 | " 515 | `; 516 | 517 | exports[`union types 1`] = ` 518 | "/** 519 | * @schema MyStruct 520 | */ 521 | export interface MyStruct { 522 | /** 523 | * @schema MyStruct#ref_to_complex 524 | */ 525 | readonly refToComplex?: ComplexType; 526 | 527 | /** 528 | * @schema MyStruct#ReusedType 529 | */ 530 | readonly reusedType?: IntOrString; 531 | 532 | /** 533 | * @schema MyStruct#Haver 534 | */ 535 | readonly haver?: MyStructHaver; 536 | } 537 | 538 | /** 539 | * Converts an object of type 'MyStruct' to JSON representation. 540 | */ 541 | /* eslint-disable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 542 | export function toJson_MyStruct(obj: MyStruct | undefined): Record | undefined { 543 | if (obj === undefined) { return undefined; } 544 | const result = { 545 | 'ref_to_complex': toJson_ComplexType(obj.refToComplex), 546 | 'ReusedType': obj.reusedType?.value, 547 | 'Haver': obj.haver?.value, 548 | }; 549 | // filter undefined values 550 | return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {}); 551 | } 552 | /* eslint-enable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 553 | 554 | /** 555 | * @schema ComplexType 556 | */ 557 | export interface ComplexType { 558 | /** 559 | * @schema ComplexType#Ref_to_union 560 | */ 561 | readonly refToUnion?: IntOrString; 562 | } 563 | 564 | /** 565 | * Converts an object of type 'ComplexType' to JSON representation. 566 | */ 567 | /* eslint-disable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 568 | export function toJson_ComplexType(obj: ComplexType | undefined): Record | undefined { 569 | if (obj === undefined) { return undefined; } 570 | const result = { 571 | 'Ref_to_union': obj.refToUnion?.value, 572 | }; 573 | // filter undefined values 574 | return Object.entries(result).reduce((r, i) => (i[1] === undefined) ? r : ({ ...r, [i[0]]: i[1] }), {}); 575 | } 576 | /* eslint-enable max-len, @stylistic/max-len, quote-props, @stylistic/quote-props */ 577 | 578 | /** 579 | * @schema IntOrString 580 | */ 581 | export class IntOrString { 582 | public static fromNumber(value: number): IntOrString { 583 | return new IntOrString(value); 584 | } 585 | public static fromString(value: string): IntOrString { 586 | return new IntOrString(value); 587 | } 588 | private constructor(public readonly value: number | string) { 589 | } 590 | } 591 | 592 | /** 593 | * @schema MyStructHaver 594 | */ 595 | export class MyStructHaver { 596 | public static fromNumber(value: number): MyStructHaver { 597 | return new MyStructHaver(value); 598 | } 599 | public static fromString(value: string): MyStructHaver { 600 | return new MyStructHaver(value); 601 | } 602 | private constructor(public readonly value: number | string) { 603 | } 604 | } 605 | " 606 | `; 607 | -------------------------------------------------------------------------------- /test/type-generator.test.ts: -------------------------------------------------------------------------------- 1 | import { JSONSchema4 } from 'json-schema'; 2 | import { generate } from './util'; 3 | import { TypeGenerator } from '../src'; 4 | import { which } from './which'; 5 | 6 | jest.setTimeout(3 * 60_000); // 3min 7 | 8 | describe('unions', () => { 9 | 10 | which('include primitives', { 11 | oneOf: [ 12 | { type: 'string' }, 13 | { type: 'number' }, 14 | ], 15 | }); 16 | 17 | which('include primitive types through references', { 18 | oneOf: [ 19 | { type: 'string' }, 20 | { $ref: '#/definitions/NumberType' }, 21 | ], 22 | }, { 23 | definitions: { 24 | NumberType: { 25 | type: 'number', 26 | }, 27 | }, 28 | }); 29 | 30 | which('reject non-primitive types through references', { 31 | oneOf: [ 32 | { type: 'string' }, 33 | { $ref: '#/definitions/ObjectType' }, 34 | ], 35 | }, { 36 | definitions: { 37 | ObjectType: { 38 | type: 'object', 39 | properties: { 40 | foo: { type: 'string' }, 41 | }, 42 | }, 43 | }, 44 | }); 45 | 46 | which('constraints are ignored for objects', { 47 | description: 'An ordered list of route rules for HTTP traffic.', 48 | type: 'array', 49 | items: { 50 | type: 'object', 51 | properties: { 52 | fault: { 53 | type: 'object', 54 | description: 'Fault injection policy to apply on HTTP traffic at\nthe client side.', 55 | properties: { 56 | delay: { 57 | oneOf: [ 58 | { 59 | anyOf: [ 60 | { required: ['fixedDelay'] }, 61 | { required: ['exponentialDelay'] }, 62 | ], 63 | }, 64 | { required: ['fixedDelay'] }, 65 | { required: ['exponentialDelay'] }, 66 | ], 67 | properties: { 68 | exponentialDelay: { 69 | type: 'string', 70 | }, 71 | fixedDelay: { 72 | description: 'Add a fixed delay before forwarding the request.', 73 | type: 'string', 74 | }, 75 | percent: { 76 | description: 'Percentage of requests on which the delay\nwill be injected (0-100).', 77 | format: 'int32', 78 | type: 'integer', 79 | }, 80 | percentage: { 81 | description: 'Percentage of requests on which the delay\nwill be injected.', 82 | properties: { 83 | value: { 84 | format: 'double', 85 | type: 'number', 86 | }, 87 | }, 88 | type: 'object', 89 | }, 90 | }, 91 | type: 'object', 92 | }, 93 | }, 94 | }, 95 | }, 96 | }, 97 | }); 98 | 99 | which('have multiple of the same type', { 100 | properties: { 101 | foo: { 102 | oneOf: [ 103 | { type: 'boolean' }, 104 | { type: 'boolean' }, 105 | ], 106 | }, 107 | }, 108 | }); 109 | 110 | which('have enums in unions', { 111 | properties: { 112 | foo: { 113 | oneOf: [ 114 | { 115 | type: 'string', 116 | enum: ['A', 'B', 'C'], 117 | }, 118 | { 119 | type: 'number', 120 | enum: [1, 2, 3], 121 | }, 122 | ], 123 | }, 124 | }, 125 | }); 126 | 127 | which('have multiple enums', { 128 | properties: { 129 | foo: { 130 | oneOf: [ 131 | { 132 | type: 'string', 133 | enum: [ 134 | 'tab', 135 | ], 136 | }, 137 | { 138 | type: 'string', 139 | enum: [ 140 | 'space', 141 | ], 142 | }, 143 | ], 144 | }, 145 | }, 146 | }); 147 | 148 | which.usingTransforms('hoistSingletonUnions')('have only one type', { 149 | properties: { 150 | foo: { 151 | anyOf: [{ type: 'boolean' }], 152 | }, 153 | bar: { 154 | oneOf: [{ type: 'string' }], 155 | }, 156 | baz: { 157 | allOf: [{ type: 'number' }], 158 | }, 159 | }, 160 | }); 161 | 162 | which.usingTransforms('convertNullUnionsToOptional')('are tuples with a null type', { 163 | properties: { 164 | foo: { 165 | anyOf: [{ type: 'null' }, { type: 'boolean' }], 166 | }, 167 | bar: { 168 | oneOf: [{ type: 'null' }, { type: 'boolean' }], 169 | }, 170 | }, 171 | }); 172 | 173 | which.usingTransforms('simplifyElementArrayUnions')('have an array of a type and the same type', { 174 | properties: { 175 | foo: { 176 | anyOf: [ 177 | { type: 'array', items: { type: 'string' } }, 178 | { type: 'string' }, 179 | ], 180 | }, 181 | bar: { 182 | anyOf: [ 183 | { type: 'array', items: { type: 'boolean' } }, 184 | { type: 'boolean' }, 185 | ], 186 | }, 187 | baz: { 188 | allOf: [ 189 | { type: 'array', items: { type: 'number' } }, 190 | { type: 'number' }, 191 | ], 192 | }, 193 | }, 194 | }); 195 | 196 | }); 197 | 198 | 199 | describe('structs', () => { 200 | 201 | which('has primitive types and collections of primitive types', { 202 | type: 'object', 203 | properties: { 204 | stringValue: { type: 'string' }, 205 | boolValue: { type: 'boolean' }, 206 | numberValue: { type: 'number' }, 207 | integerValue: { type: 'integer' }, 208 | arrayOfString: { 209 | type: 'array', 210 | items: { type: 'string' }, 211 | }, 212 | }, 213 | }); 214 | 215 | which('has a field that references another struct (with required fields)', { 216 | type: 'object', 217 | properties: { 218 | other: { 219 | $ref: '#/definitions/Other', 220 | }, 221 | }, 222 | }, { 223 | definitions: { 224 | Other: { 225 | type: 'object', 226 | properties: { 227 | stringValue: { type: 'string' }, 228 | }, 229 | required: ['stringValue'], 230 | }, 231 | }, 232 | }); 233 | 234 | which('supports $defs references', { 235 | type: 'object', 236 | properties: { 237 | other: { 238 | $ref: '#/$defs/Other', 239 | }, 240 | }, 241 | }, { 242 | definitions: { 243 | Other: { 244 | type: 'object', 245 | properties: { 246 | stringValue: { type: 'string' }, 247 | }, 248 | required: ['stringValue'], 249 | }, 250 | }, 251 | }); 252 | 253 | which('references itself', { 254 | type: 'object', 255 | properties: { 256 | entrypoint: { 257 | $ref: '#/definitions/MyType', 258 | }, 259 | }, 260 | }, { 261 | definitions: { 262 | MyType: { 263 | type: 'object', 264 | properties: { 265 | self: { $ref: '#/definitions/MyType' }, 266 | }, 267 | }, 268 | }, 269 | }); 270 | 271 | which('array of structs is considered optional', { 272 | type: 'object', 273 | properties: { 274 | shouldBeRequired: { $ref: '#/definitions/ItemType' }, 275 | mapShouldBeOptional: { 276 | type: 'object', 277 | additionalProperties: { $ref: '#/definitions/ItemType' }, 278 | }, 279 | arrayShouldBeOptional: { 280 | type: 'array', 281 | items: { 282 | $ref: '#/definitions/ItemType', 283 | }, 284 | }, 285 | }, 286 | }, { 287 | definitions: { 288 | ItemType: { 289 | type: 'object', 290 | required: ['requiredField'], 291 | properties: { 292 | requiredField: { type: 'string' }, 293 | }, 294 | }, 295 | }, 296 | }); 297 | 298 | which('includes required fields', { 299 | required: [ 300 | 'minReadySeconds', 301 | 'revisionHistoryLimit', 302 | 'NonCamelCaseRequired', 303 | ], 304 | type: 'object', 305 | properties: { 306 | minReadySeconds: { 307 | description: 'Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Defaults to 0 (pod will be considered available as soon as it is ready)', 308 | format: 'int32', 309 | type: 'integer', 310 | }, 311 | paused: { 312 | description: 'Indicates that the deployment is paused.', 313 | type: 'boolean', 314 | }, 315 | progressDeadlineSeconds: { 316 | description: 'The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that progress will not be estimated during the time a deployment is paused. Defaults to 600s.', 317 | format: 'int32', 318 | type: 'integer', 319 | }, 320 | replicas: { 321 | description: 'Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.', 322 | format: 'int32', 323 | type: 'integer', 324 | }, 325 | revisionHistoryLimit: { 326 | description: 'The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.', 327 | format: 'int32', 328 | type: 'integer', 329 | }, 330 | NonCamelCaseRequired: { 331 | type: 'string', 332 | }, 333 | }, 334 | }); 335 | 336 | which('if we have "properties" and "type" is omitted, it is considered a struct', { 337 | properties: { 338 | foo: { 339 | type: 'string', 340 | }, 341 | }, 342 | }); 343 | 344 | }); 345 | 346 | 347 | describe('documentation', () => { 348 | 349 | which('does not render if not defined', { 350 | type: 'object', 351 | properties: { 352 | field: { 353 | type: 'boolean', 354 | }, 355 | }, 356 | }); 357 | 358 | which('renders based on description', { 359 | type: 'object', 360 | properties: { 361 | field: { 362 | description: 'hello, description', 363 | type: 'string', 364 | }, 365 | }, 366 | }); 367 | 368 | which('"*/" is is escaped', { 369 | type: 'object', 370 | properties: { 371 | field: { 372 | description: 'hello */world', 373 | type: 'string', 374 | }, 375 | }, 376 | }); 377 | 378 | }); 379 | 380 | describe('enums', () => { 381 | 382 | which('renders a typescript enum', { 383 | type: 'object', 384 | required: ['firstEnum'], 385 | properties: { 386 | firstEnum: { 387 | description: 'description of first enum', 388 | type: 'string', 389 | enum: ['value1', 'value2', 'value-of-three', 'valueOfFour'], 390 | }, 391 | child: { 392 | type: 'object', 393 | properties: { 394 | secondEnum: { 395 | description: 'description of second enum', 396 | type: 'string', 397 | enum: ['hey', 'enum values can be crazy', 'yes>>123'], 398 | }, 399 | }, 400 | }, 401 | }, 402 | }); 403 | 404 | which('without type implies "string"', { 405 | properties: { 406 | Color: { enum: ['red', 'green', 'blue'] }, 407 | }, 408 | }); 409 | 410 | which('uses non-symbolic values', { 411 | properties: { 412 | Timeframe: { 413 | description: 'The SLO time window options. Allowed enum values: 7d,30d,90d', 414 | type: 'string', 415 | enum: [ 416 | '7d', 417 | '30d', 418 | '90d', 419 | ], 420 | }, 421 | }, 422 | }); 423 | 424 | which('has number values', { 425 | properties: { 426 | Percentiles: { 427 | type: 'number', 428 | enum: [ 429 | .9, 430 | .95, 431 | .99, 432 | ], 433 | }, 434 | }, 435 | }); 436 | 437 | which('has integer values', { 438 | properties: { 439 | Days: { 440 | type: 'integer', 441 | enum: [ 442 | 1, 443 | 2, 444 | 3, 445 | ], 446 | }, 447 | }, 448 | }); 449 | 450 | which('has allowlisted characters', { 451 | properties: { 452 | Chars: { 453 | type: 'string', 454 | enum: [ 455 | '!=', 456 | '!', 457 | '<=', 458 | '!~', 459 | '\'', 460 | '\\', 461 | 'Foo.Bar!=', 462 | 'Foo.!=Bar', 463 | 'bool:true', 464 | 'Foo-Bar', 465 | 'Foo_Bar', 466 | 'Foo.Bar', 467 | '0.Foo', 468 | 'Foo.0', 469 | '.Foo', 470 | 'Foo.', 471 | '0.9', 472 | '.9', 473 | '9', 474 | '9.', 475 | '9.9', 476 | '99', 477 | '9.90.0.9', 478 | 'Foo<>>=9.0.9{}-Bar', 479 | ], 480 | }, 481 | }, 482 | }); 483 | 484 | which('has repeated values', { 485 | properties: { 486 | Same: { 487 | type: 'string', 488 | enum: [ 489 | 'replace', 490 | 'Replace', 491 | 'keep', 492 | 'Keep', 493 | 'hashmod', 494 | 'HashMod', 495 | 'labelmap', 496 | 'LabelMap', 497 | '24', 498 | '0.99', 499 | ], 500 | }, 501 | }, 502 | }); 503 | }); 504 | 505 | which('primitives', { 506 | type: 'object', 507 | properties: { 508 | stringValue: { type: 'string' }, 509 | booleanValue: { type: 'boolean' }, 510 | dateValue: { type: 'string', format: 'date-time' }, 511 | dateValueImplicitType: { format: 'date-time' }, 512 | anyValue: { type: 'any' }, 513 | nullValue: { type: 'null' }, 514 | numberValue: { type: 'number' }, 515 | integerValue: { type: 'integer' }, 516 | }, 517 | }); 518 | 519 | which('camel casing', { 520 | properties: { 521 | 'CamelCase1': { type: 'string' }, 522 | 'Camel_Case2': { type: 'string' }, 523 | 'camel_case_3': { type: 'string' }, 524 | 'CAMEL_CASE_4': { type: 'string' }, 525 | 'camel-case-5': { type: 'string' }, 526 | }, 527 | }); 528 | 529 | describe('type aliases', () => { 530 | 531 | test('alias to a custom type', () => { 532 | // GIVEN 533 | const gen = new TypeGenerator(); 534 | gen.addDefinition('TypeB', { type: 'object', properties: { refToTypeA: { $ref: '#/definitions/TypeA' } } }); 535 | gen.emitCustomType('NewType', code => code.line('// this is NewType')); 536 | 537 | // WHEN 538 | gen.addAlias('TypeA', 'NewType'); // this is a type alias 539 | gen.emitType('TypeB'); 540 | 541 | // THEN 542 | expect(gen.render()).toMatchSnapshot(); 543 | }); 544 | 545 | test('alias to a definition', () => { 546 | // GIVEN 547 | const gen = new TypeGenerator(); 548 | gen.addDefinition('TypeA', { type: 'object', properties: { ref: { $ref: '#/definitions/TypeB' } } } ); 549 | gen.addDefinition('TypeC', { type: 'object', properties: { field: { type: 'string' } } }); 550 | 551 | // WHEN 552 | gen.addAlias('TypeB', 'TypeC'); 553 | 554 | // THEN 555 | gen.emitType('TypeA'); 556 | expect(gen.render()).toMatchSnapshot(); 557 | }); 558 | }); 559 | 560 | 561 | test('fails when trying to resolve an undefined $ref', () => { 562 | const g = new TypeGenerator(); 563 | expect(() => g.addType('Foo', { $ref: 'unresolvable' })).toThrow(/invalid \$ref {\"\$ref\":\"unresolvable\"}/); 564 | expect(() => g.addType('Foo', { $ref: '#/definitions/unresolvable' })).toThrow(/unable to find a definition for the \$ref \"unresolvable\"/); 565 | }); 566 | 567 | test('if "toJson" is disabled, toJson functions are not generated', async () => { 568 | const schema: JSONSchema4 = { 569 | properties: { 570 | bar: { type: 'string' }, 571 | }, 572 | }; 573 | 574 | const gen = TypeGenerator.forStruct('Foo', schema, { 575 | toJson: false, 576 | }); 577 | 578 | expect(await generate(gen)).toMatchSnapshot(); 579 | }); 580 | 581 | test('if "toJsonInternal" is enabled, toJson functions are marked as @internal', async () => { 582 | const schema: JSONSchema4 = { 583 | properties: { 584 | bar: { type: 'string' }, 585 | nested: { 586 | type: 'object', 587 | properties: { 588 | value: { type: 'number' }, 589 | }, 590 | }, 591 | }, 592 | }; 593 | 594 | const gen = TypeGenerator.forStruct('Foo', schema, { 595 | toJson: true, 596 | toJsonInternal: true, 597 | }); 598 | 599 | expect(await generate(gen)).toMatchSnapshot(); 600 | }); 601 | 602 | describe('type arrays', () => { 603 | which('have null and a single non null type', { 604 | properties: { 605 | bar: { type: ['null', 'boolean'] }, 606 | }, 607 | }); 608 | 609 | which('have a single type', { 610 | properties: { 611 | bar: { type: ['boolean'] }, 612 | }, 613 | }); 614 | }); 615 | 616 | test('additionalProperties when type is defined as array', async () => { 617 | 618 | const schema: JSONSchema4 = { 619 | properties: { 620 | foo: { 621 | type: ['null', 'object'], 622 | additionalProperties: { 623 | type: 'string', 624 | }, 625 | }, 626 | }, 627 | }; 628 | 629 | const gen = TypeGenerator.forStruct('Foo', schema, { 630 | toJson: false, 631 | }); 632 | 633 | expect(await generate(gen)).toMatchSnapshot(); 634 | 635 | }); 636 | 637 | test('properties when type is defined as array', async () => { 638 | 639 | const schema: JSONSchema4 = { 640 | type: ['null', 'object'], 641 | properties: { 642 | bar: { type: 'string' }, 643 | }, 644 | }; 645 | 646 | const gen = TypeGenerator.forStruct('Foo', schema, { 647 | toJson: false, 648 | }); 649 | 650 | expect(await generate(gen)).toMatchSnapshot(); 651 | 652 | }); 653 | 654 | test('enum when type is defined as array', async () => { 655 | 656 | const schema: JSONSchema4 = { 657 | properties: { 658 | foo: { 659 | type: ['null', 'string'], 660 | enum: ['val1', 'val2'], 661 | }, 662 | }, 663 | }; 664 | 665 | const gen = TypeGenerator.forStruct('Foo', schema, { 666 | toJson: false, 667 | }); 668 | 669 | expect(await generate(gen)).toMatchSnapshot(); 670 | 671 | }); 672 | 673 | test('sanitize string enum when one of the values is null', async () => { 674 | 675 | const schema: JSONSchema4 = { 676 | properties: { 677 | foo: { 678 | type: ['null', 'string'], 679 | enum: ['val1', null], 680 | }, 681 | }, 682 | }; 683 | 684 | const gen = TypeGenerator.forStruct('Foo', schema, { 685 | toJson: false, 686 | sanitizeEnums: true, 687 | }); 688 | 689 | expect(await generate(gen)).toMatchSnapshot(); 690 | 691 | }); 692 | 693 | test('sanitize number enum when one of the values is null', async () => { 694 | 695 | const schema: JSONSchema4 = { 696 | properties: { 697 | foo: { 698 | type: ['null', 'number'], 699 | enum: [3, null], 700 | }, 701 | }, 702 | }; 703 | 704 | const gen = TypeGenerator.forStruct('Foo', schema, { 705 | toJson: false, 706 | sanitizeEnums: true, 707 | }); 708 | 709 | expect(await generate(gen)).toMatchSnapshot(); 710 | 711 | }); 712 | 713 | test('custom ref normalization', async () => { 714 | 715 | const foo = 'io.k8s.v1beta1.Foo'; 716 | const bar = 'Bar'; 717 | 718 | const gen = new TypeGenerator({ 719 | renderTypeName: (def: string) => { 720 | return def.split('.').slice(2, 4).join(''); 721 | }, 722 | }); 723 | 724 | gen.addDefinition(foo, { properties: { props: { type: 'number' } } }); 725 | 726 | // two structs, each referencing a different version 727 | gen.addDefinition(bar, { properties: { prop: { $ref: `#/definitions/${foo}` } } }); 728 | gen.emitType(bar); 729 | 730 | const code = await generate(gen); 731 | expect(code).toMatchSnapshot(); 732 | 733 | }); 734 | 735 | test('shared namespace references', async () => { 736 | 737 | const foo1 = 'io.k8s.v1beta1.Foo'; 738 | const foo2 = 'io.k8s.v1.Foo'; 739 | const bar1 = 'Bar1'; 740 | const bar2 = 'Bar2'; 741 | 742 | const gen = new TypeGenerator(); 743 | 744 | // two versions of the same struct 745 | gen.addDefinition(foo1, { properties: { props: { type: 'number' } } }); 746 | gen.addDefinition(foo2, { properties: { props: { type: 'string' } } }); 747 | 748 | // two structs, each referencing a different version 749 | gen.addDefinition(bar1, { properties: { prop: { $ref: `#/definitions/${foo1}` } } }); 750 | gen.addDefinition(bar2, { properties: { prop: { $ref: `#/definitions/${foo2}` } } }); 751 | 752 | gen.emitType(bar1); 753 | gen.emitType(bar2); 754 | 755 | const code = await generate(gen); 756 | 757 | // we expect the code to contain Bar1 and Bar2 that reference foo1 and 758 | // foo2 respectively 759 | expect(code).toMatchSnapshot(); 760 | }); 761 | 762 | 763 | test('dedup properties with different casing', async () => { 764 | 765 | // based on https://github.com/zalando/postgres-operator/blob/7d4da928726b5029f72e9ab83ee9b6ff481e70c7/manifests/postgresql.crd.yaml#L353-L357 766 | const schema: JSONSchema4 = { 767 | properties: { 768 | pod_priority_class_name: { 769 | type: 'string', 770 | }, 771 | podPriorityClassName: { 772 | type: 'string', 773 | }, 774 | }, 775 | }; 776 | 777 | const gen = TypeGenerator.forStruct('Foo', schema, { 778 | toJson: true, 779 | }); 780 | 781 | expect(await generate(gen)).toMatchSnapshot(); 782 | 783 | }); 784 | --------------------------------------------------------------------------------