├── .circleci └── config.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── auto-merge.yml │ └── codeql.yml ├── .gitignore ├── .licrc ├── .prettierrc.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── babel.config.json ├── bin ├── index.js └── index.spec.js ├── jest.config.ts ├── package-lock.json ├── package.json ├── rollup.config.mjs ├── samples ├── codegen.sh └── spec │ ├── v2.json │ └── v3.json ├── src ├── HttpClient.ts ├── Indent.ts ├── client │ └── interfaces │ │ ├── Client.d.ts │ │ ├── Enum.d.ts │ │ ├── Model.d.ts │ │ ├── ModelComposition.d.ts │ │ ├── Operation.d.ts │ │ ├── OperationError.d.ts │ │ ├── OperationParameter.d.ts │ │ ├── OperationParameters.d.ts │ │ ├── OperationResponse.d.ts │ │ ├── Schema.d.ts │ │ ├── Service.d.ts │ │ └── Type.d.ts ├── index.spec.ts ├── index.ts ├── openApi │ ├── v2 │ │ ├── index.ts │ │ ├── interfaces │ │ │ ├── Extensions │ │ │ │ ├── WithEnumExtension.d.ts │ │ │ │ └── WithNullableExtension.d.ts │ │ │ ├── OpenApi.d.ts │ │ │ ├── OpenApiContact.d.ts │ │ │ ├── OpenApiExample.d.ts │ │ │ ├── OpenApiExternalDocs.d.ts │ │ │ ├── OpenApiHeader.d.ts │ │ │ ├── OpenApiInfo.d.ts │ │ │ ├── OpenApiItems.d.ts │ │ │ ├── OpenApiLicense.d.ts │ │ │ ├── OpenApiOperation.d.ts │ │ │ ├── OpenApiParameter.d.ts │ │ │ ├── OpenApiPath.d.ts │ │ │ ├── OpenApiReference.d.ts │ │ │ ├── OpenApiResponse.d.ts │ │ │ ├── OpenApiResponses.d.ts │ │ │ ├── OpenApiSchema.d.ts │ │ │ ├── OpenApiSecurityRequirement.d.ts │ │ │ ├── OpenApiSecurityScheme.d.ts │ │ │ ├── OpenApiTag.d.ts │ │ │ └── OpenApiXml.d.ts │ │ └── parser │ │ │ ├── escapeName.spec.ts │ │ │ ├── escapeName.ts │ │ │ ├── extendEnum.ts │ │ │ ├── getEnum.ts │ │ │ ├── getMappedType.spec.ts │ │ │ ├── getMappedType.ts │ │ │ ├── getModel.ts │ │ │ ├── getModelComposition.ts │ │ │ ├── getModelProperties.ts │ │ │ ├── getModelTemplate.spec.ts │ │ │ ├── getModelTemplate.ts │ │ │ ├── getModels.ts │ │ │ ├── getOperation.ts │ │ │ ├── getOperationErrors.ts │ │ │ ├── getOperationName.spec.ts │ │ │ ├── getOperationName.ts │ │ │ ├── getOperationParameter.ts │ │ │ ├── getOperationParameterDefault.ts │ │ │ ├── getOperationParameterName.spec.ts │ │ │ ├── getOperationParameterName.ts │ │ │ ├── getOperationParameters.ts │ │ │ ├── getOperationResponse.ts │ │ │ ├── getOperationResponseCode.spec.ts │ │ │ ├── getOperationResponseCode.ts │ │ │ ├── getOperationResponseHeader.ts │ │ │ ├── getOperationResponses.ts │ │ │ ├── getOperationResults.ts │ │ │ ├── getRef.spec.ts │ │ │ ├── getRef.ts │ │ │ ├── getRequiredPropertiesFromComposition.ts │ │ │ ├── getServer.spec.ts │ │ │ ├── getServer.ts │ │ │ ├── getServiceName.spec.ts │ │ │ ├── getServiceName.ts │ │ │ ├── getServiceVersion.spec.ts │ │ │ ├── getServiceVersion.ts │ │ │ ├── getServices.spec.ts │ │ │ ├── getServices.ts │ │ │ ├── getType.spec.ts │ │ │ ├── getType.ts │ │ │ ├── sortByRequired.ts │ │ │ ├── stripNamespace.spec.ts │ │ │ └── stripNamespace.ts │ └── v3 │ │ ├── index.ts │ │ ├── interfaces │ │ ├── Extensions │ │ │ └── WithEnumExtension.d.ts │ │ ├── OpenApi.d.ts │ │ ├── OpenApiCallback.d.ts │ │ ├── OpenApiComponents.d.ts │ │ ├── OpenApiContact.d.ts │ │ ├── OpenApiDiscriminator.d.ts │ │ ├── OpenApiEncoding.d.ts │ │ ├── OpenApiExample.d.ts │ │ ├── OpenApiExternalDocs.d.ts │ │ ├── OpenApiHeader.d.ts │ │ ├── OpenApiInfo.d.ts │ │ ├── OpenApiLicense.d.ts │ │ ├── OpenApiLink.d.ts │ │ ├── OpenApiMediaType.d.ts │ │ ├── OpenApiOAuthFlow.d.ts │ │ ├── OpenApiOAuthFlows.d.ts │ │ ├── OpenApiOperation.d.ts │ │ ├── OpenApiParameter.d.ts │ │ ├── OpenApiPath.d.ts │ │ ├── OpenApiPaths.d.ts │ │ ├── OpenApiReference.d.ts │ │ ├── OpenApiRequestBody.d.ts │ │ ├── OpenApiResponse.d.ts │ │ ├── OpenApiResponses.d.ts │ │ ├── OpenApiSchema.d.ts │ │ ├── OpenApiSecurityRequirement.d.ts │ │ ├── OpenApiSecurityScheme.d.ts │ │ ├── OpenApiServer.d.ts │ │ ├── OpenApiServerVariable.d.ts │ │ ├── OpenApiTag.d.ts │ │ └── OpenApiXml.d.ts │ │ └── parser │ │ ├── escapeName.spec.ts │ │ ├── escapeName.ts │ │ ├── extendEnum.ts │ │ ├── getContent.ts │ │ ├── getEnum.ts │ │ ├── getMappedType.spec.ts │ │ ├── getMappedType.ts │ │ ├── getModel.ts │ │ ├── getModelComposition.ts │ │ ├── getModelDefault.ts │ │ ├── getModelProperties.ts │ │ ├── getModelTemplate.spec.ts │ │ ├── getModelTemplate.ts │ │ ├── getModels.ts │ │ ├── getOperation.ts │ │ ├── getOperationErrors.ts │ │ ├── getOperationName.spec.ts │ │ ├── getOperationName.ts │ │ ├── getOperationParameter.ts │ │ ├── getOperationParameterName.spec.ts │ │ ├── getOperationParameterName.ts │ │ ├── getOperationParameters.ts │ │ ├── getOperationRequestBody.ts │ │ ├── getOperationResponse.ts │ │ ├── getOperationResponseCode.spec.ts │ │ ├── getOperationResponseCode.ts │ │ ├── getOperationResponseHeader.ts │ │ ├── getOperationResponses.ts │ │ ├── getOperationResults.ts │ │ ├── getRef.spec.ts │ │ ├── getRef.ts │ │ ├── getRequiredPropertiesFromComposition.ts │ │ ├── getServer.spec.ts │ │ ├── getServer.ts │ │ ├── getServiceName.spec.ts │ │ ├── getServiceName.ts │ │ ├── getServiceVersion.spec.ts │ │ ├── getServiceVersion.ts │ │ ├── getServices.spec.ts │ │ ├── getServices.ts │ │ ├── getType.spec.ts │ │ ├── getType.ts │ │ ├── sortByRequired.ts │ │ ├── stripNamespace.spec.ts │ │ └── stripNamespace.ts ├── templates │ ├── __mocks__ │ │ └── index.ts │ ├── client.hbs │ ├── core │ │ ├── ApiError.hbs │ │ ├── ApiRequestOptions.hbs │ │ ├── ApiResult.hbs │ │ ├── BaseHttpRequest.hbs │ │ ├── CancelablePromise.hbs │ │ ├── HttpRequest.hbs │ │ ├── OpenAPI.hbs │ │ ├── angular │ │ │ ├── getHeaders.hbs │ │ │ ├── getRequestBody.hbs │ │ │ ├── getResponseBody.hbs │ │ │ ├── getResponseHeader.hbs │ │ │ ├── request.hbs │ │ │ └── sendRequest.hbs │ │ ├── axios │ │ │ ├── getHeaders.hbs │ │ │ ├── getRequestBody.hbs │ │ │ ├── getResponseBody.hbs │ │ │ ├── getResponseHeader.hbs │ │ │ ├── request.hbs │ │ │ └── sendRequest.hbs │ │ ├── fetch │ │ │ ├── getHeaders.hbs │ │ │ ├── getRequestBody.hbs │ │ │ ├── getResponseBody.hbs │ │ │ ├── getResponseHeader.hbs │ │ │ ├── request.hbs │ │ │ └── sendRequest.hbs │ │ ├── functions │ │ │ ├── base64.hbs │ │ │ ├── catchErrorCodes.hbs │ │ │ ├── getFormData.hbs │ │ │ ├── getQueryString.hbs │ │ │ ├── getUrl.hbs │ │ │ ├── isBlob.hbs │ │ │ ├── isDefined.hbs │ │ │ ├── isFormData.hbs │ │ │ ├── isString.hbs │ │ │ ├── isStringWithValue.hbs │ │ │ ├── isSuccess.hbs │ │ │ └── resolve.hbs │ │ ├── node │ │ │ ├── getHeaders.hbs │ │ │ ├── getRequestBody.hbs │ │ │ ├── getResponseBody.hbs │ │ │ ├── getResponseHeader.hbs │ │ │ ├── request.hbs │ │ │ └── sendRequest.hbs │ │ ├── request.hbs │ │ └── xhr │ │ │ ├── getHeaders.hbs │ │ │ ├── getRequestBody.hbs │ │ │ ├── getResponseBody.hbs │ │ │ ├── getResponseHeader.hbs │ │ │ ├── request.hbs │ │ │ └── sendRequest.hbs │ ├── exportModel.hbs │ ├── exportSchema.hbs │ ├── exportService.hbs │ ├── index.hbs │ └── partials │ │ ├── base.hbs │ │ ├── exportComposition.hbs │ │ ├── exportEnum.hbs │ │ ├── exportInterface.hbs │ │ ├── exportType.hbs │ │ ├── header.hbs │ │ ├── isNullable.hbs │ │ ├── isReadOnly.hbs │ │ ├── isRequired.hbs │ │ ├── parameters.hbs │ │ ├── result.hbs │ │ ├── schema.hbs │ │ ├── schemaArray.hbs │ │ ├── schemaComposition.hbs │ │ ├── schemaDictionary.hbs │ │ ├── schemaEnum.hbs │ │ ├── schemaGeneric.hbs │ │ ├── schemaInterface.hbs │ │ ├── type.hbs │ │ ├── typeArray.hbs │ │ ├── typeDictionary.hbs │ │ ├── typeEnum.hbs │ │ ├── typeGeneric.hbs │ │ ├── typeInterface.hbs │ │ ├── typeIntersection.hbs │ │ ├── typeReference.hbs │ │ └── typeUnion.hbs ├── typings │ └── hbs.d.ts └── utils │ ├── __mocks__ │ └── fileSystem.ts │ ├── discriminator.ts │ ├── fileSystem.ts │ ├── flatMap.spec.ts │ ├── flatMap.ts │ ├── formatCode.spec.ts │ ├── formatCode.ts │ ├── formatIndentation.ts │ ├── getHttpRequestName.ts │ ├── getModelNames.spec.ts │ ├── getModelNames.ts │ ├── getOpenApiSpec.ts │ ├── getOpenApiVersion.spec.ts │ ├── getOpenApiVersion.ts │ ├── getPattern.spec.ts │ ├── getPattern.ts │ ├── getServiceNames.spec.ts │ ├── getServiceNames.ts │ ├── isDefined.ts │ ├── isEqual.ts │ ├── isString.spec.ts │ ├── isString.ts │ ├── isSubdirectory.spec.ts │ ├── isSubdirectory.ts │ ├── postProcessClient.ts │ ├── postProcessModel.ts │ ├── postProcessModelEnum.ts │ ├── postProcessModelEnums.ts │ ├── postProcessModelImports.ts │ ├── postProcessService.ts │ ├── postProcessServiceImports.ts │ ├── postProcessServiceOperations.ts │ ├── readSpec.ts │ ├── readSpecFromDisk.ts │ ├── readSpecFromHttp.ts │ ├── readSpecFromHttps.ts │ ├── registerHandlebarHelpers.spec.ts │ ├── registerHandlebarHelpers.ts │ ├── registerHandlebarTemplates.spec.ts │ ├── registerHandlebarTemplates.ts │ ├── reservedWords.ts │ ├── sort.spec.ts │ ├── sort.ts │ ├── sortModelsByName.spec.ts │ ├── sortModelsByName.ts │ ├── sortServicesByName.spec.ts │ ├── sortServicesByName.ts │ ├── types.d.ts │ ├── unique.spec.ts │ ├── unique.ts │ ├── writeClient.spec.ts │ ├── writeClient.ts │ ├── writeClientClass.spec.ts │ ├── writeClientClass.ts │ ├── writeClientCore.spec.ts │ ├── writeClientCore.ts │ ├── writeClientIndex.spec.ts │ ├── writeClientIndex.ts │ ├── writeClientModels.spec.ts │ ├── writeClientModels.ts │ ├── writeClientSchemas.spec.ts │ ├── writeClientSchemas.ts │ ├── writeClientServices.spec.ts │ └── writeClientServices.ts ├── test ├── __snapshots__ │ └── index.spec.ts.snap ├── custom │ └── request.ts ├── e2e │ ├── assets │ │ ├── index.html │ │ ├── main-angular-module.ts │ │ ├── main-angular.ts │ │ └── main.ts │ ├── client.angular.spec.ts │ ├── client.axios.spec.ts │ ├── client.babel.spec.ts │ ├── client.fetch.spec.ts │ ├── client.node.spec.ts │ ├── client.xhr.spec.ts │ ├── scripts │ │ ├── browser.ts │ │ ├── buildAngularProject.ts │ │ ├── cleanup.ts │ │ ├── compileWithBabel.ts │ │ ├── compileWithTypescript.ts │ │ ├── copyAsset.ts │ │ ├── createAngularProject.ts │ │ ├── generateClient.ts │ │ └── server.ts │ ├── v2.angular.spec.ts │ ├── v2.axios.spec.ts │ ├── v2.babel.spec.ts │ ├── v2.fetch.spec.ts │ ├── v2.node.spec.ts │ ├── v2.xhr.spec.ts │ ├── v3.angular.spec.ts │ ├── v3.axios.spec.ts │ ├── v3.babel.spec.ts │ ├── v3.fetch.spec.ts │ ├── v3.node.spec.ts │ └── v3.xhr.spec.ts ├── index.js ├── index.spec.ts └── spec │ ├── v2.json │ └── v3.json ├── tsconfig.json └── types └── index.d.ts /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: cimg/node:lts-browsers 6 | resource_class: large 7 | working_directory: ~/repo 8 | steps: 9 | - checkout 10 | - restore_cache: 11 | keys: 12 | - v1-dependencies-{{ checksum "package-lock.json" }} 13 | - v1-dependencies- 14 | - run: 15 | name: Install dependencies 16 | command: npm install 17 | - save_cache: 18 | key: v1-dependencies-{{ checksum "package-lock.json" }} 19 | paths: 20 | - node_modules 21 | - run: 22 | name: Build library 23 | command: npm run release 24 | - run: 25 | name: Run unit tests 26 | command: npm run test 27 | # - run: 28 | # name: Run e2e tests 29 | # command: npm run test:e2e 30 | - run: 31 | name: Submit to Codecov 32 | command: npm run codecov 33 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 4 10 | 11 | [*.hbs] 12 | indent_style = tab 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | samples 3 | test/generated 4 | test/e2e/generated 5 | node_modules 6 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": ["plugin:@typescript-eslint/recommended", "plugin:prettier/recommended", "prettier"], 4 | "env": { 5 | "es6": true, 6 | "node": true, 7 | "jest": true 8 | }, 9 | "plugins": ["simple-import-sort"], 10 | "rules": { 11 | "@typescript-eslint/no-explicit-any": 0, 12 | "@typescript-eslint/no-inferrable-types": 0, 13 | "@typescript-eslint/no-non-null-assertion": 0, 14 | "@typescript-eslint/no-var-requires": 0, 15 | "@typescript-eslint/ban-ts-ignore": 0, 16 | "@typescript-eslint/ban-ts-comment": 0, 17 | "@typescript-eslint/explicit-function-return-type": 0, 18 | "@typescript-eslint/explicit-module-boundary-types": 0, 19 | "sort-imports": "off", 20 | "import/order": "off", 21 | "simple-import-sort/imports": "error", 22 | "simple-import-sort/exports": "error", 23 | "prettier/prettier": ["error"] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [ferdikoomen] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: ferdikoomen 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. Ideally with a screenshot of the result or a link to a small example. 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. Ideally with a small example of the proposed changes. 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | ignore: 8 | - dependency-name: "@types/node-fetch" 9 | - dependency-name: "node-fetch" 10 | - dependency-name: "camelcase" 11 | - dependency-name: "@angular-devkit/build-angular" 12 | - dependency-name: "@angular/animations" 13 | - dependency-name: "@angular/cli" 14 | - dependency-name: "@angular/common" 15 | - dependency-name: "@angular/compiler" 16 | - dependency-name: "@angular/compiler-cli" 17 | - dependency-name: "@angular/core" 18 | - dependency-name: "@angular/forms" 19 | - dependency-name: "@angular/platform-browser" 20 | - dependency-name: "@angular/platform-browser-dynamic" 21 | - dependency-name: "@angular/router" 22 | - dependency-name: "typescript" -------------------------------------------------------------------------------- /.github/workflows/auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: auto-merge 2 | 3 | on: pull_request_target 4 | 5 | permissions: 6 | pull-requests: write 7 | contents: write 8 | 9 | jobs: 10 | dependabot: 11 | runs-on: ubuntu-latest 12 | if: ${{ github.actor == 'dependabot[bot]' }} 13 | steps: 14 | - name: Fetch Dependabot metadata 15 | id: dependabot-metadata 16 | uses: dependabot/fetch-metadata@v1 17 | with: 18 | github-token: ${{ secrets.GITHUB_TOKEN }} 19 | - name: Approve PR 20 | run: gh pr review --approve "$PR_URL" 21 | env: 22 | PR_URL: ${{ github.event.pull_request.html_url }} 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | - name: Merge PR 25 | if: ${{ steps.dependabot-metadata.outputs.update-type != 'version-update:semver-major' }} 26 | run: gh pr merge --auto --squash "$PR_URL" 27 | env: 28 | PR_URL: ${{ github.event.pull_request.html_url }} 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | schedule: 9 | - cron: "44 20 * * 3" 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: [ javascript ] 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v3 28 | 29 | - name: Initialize CodeQL 30 | uses: github/codeql-action/init@v2 31 | with: 32 | languages: ${{ matrix.language }} 33 | queries: +security-and-quality 34 | 35 | - name: Autobuild 36 | uses: github/codeql-action/autobuild@v2 37 | 38 | - name: Perform CodeQL Analysis 39 | uses: github/codeql-action/analyze@v2 40 | with: 41 | category: "/language:${{ matrix.language }}" 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log* 3 | yarn-debug.log* 4 | yarn-error.log* 5 | junit.xml 6 | .DS_Store 7 | .tmp 8 | .idea 9 | .vscode 10 | *.iml 11 | dist 12 | coverage 13 | test/generated 14 | test/e2e/generated 15 | samples/generated 16 | samples/swagger-codegen-cli-v2.jar 17 | samples/swagger-codegen-cli-v3.jar 18 | -------------------------------------------------------------------------------- /.licrc: -------------------------------------------------------------------------------- 1 | [licenses] 2 | accepted = [ 3 | "MIT", 4 | "BSD", 5 | "0BSD", 6 | "BSD-2-Clause", 7 | "BSD-3-Clause", 8 | "Apache-2.0", 9 | "CC-BY-3.0", 10 | "CC-BY-4.0", 11 | "CC0-1.0", 12 | "ISC" 13 | ] 14 | 15 | [dependencies] 16 | ignored = [] 17 | 18 | [behavior] 19 | run_only_on_dependency_modification = true 20 | do_not_block_pr = true -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "trailingComma": "es5", 5 | "arrowParens": "avoid", 6 | "printWidth": 120, 7 | "tabWidth": 4 8 | } 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Webpack Typings for JSON 2 | 3 | Thanks for your interest in contributing to this project. 4 | 5 | ## Pull Request guidelines 6 | 7 | Before working on a Pull Request, create an issue explaining what you want to contribute. 8 | This ensures that your pull request won't go unnoticed, and that you are not contributing 9 | something that is not suitable for the project. 10 | 11 | If you are unfamiliar with GitHub Pull Requests, please read the following documentation: 12 | https://help.github.com/articles/using-pull-requests 13 | 14 | **Your Pull Request must:** 15 | 16 | * Address a single issue or add a single item of functionality. 17 | * Contain a clean history of small, incremental, logically separate commits, with no merge commits. 18 | * Use clear commit messages. 19 | * Be possible to merge automatically. 20 | 21 | ## Submitting a Pull Request 22 | 23 | 1. Make your changes in a new git branch: `git checkout -b my-fix-branch main` 24 | 2. Create your patch or feature 25 | 3. Ensure the builds work by running: `npm run build` 26 | 4. Ensure the tests will pass by running: `npm run test` 27 | 5. Ensure the code is formatted by running: `npm run eslint:fix` 28 | 6. Commit your changes using a descriptive commit message 29 | 30 | After your Pull Request is created, it will automatically be build using Circle CI. 31 | When the build is successful then the Pull Request is ready for review. 32 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:alpine 2 | WORKDIR /usr/src/openapi 3 | COPY . /usr/src/openapi 4 | RUN npm install 5 | RUN npm run release 6 | ENTRYPOINT [ "node", "/usr/src/openapi/bin/index.js" ] 7 | CMD "--help" 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Ferdi Koomen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": "12" 8 | } 9 | } 10 | ], 11 | [ 12 | "@babel/preset-typescript", 13 | { 14 | "onlyRemoveTypeImports": true 15 | } 16 | ] 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /samples/codegen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -rf dist 4 | rm swagger-codegen-cli-v2.jar 5 | rm swagger-codegen-cli-v3.jar 6 | 7 | curl https://repo1.maven.org/maven2/io/swagger/swagger-codegen-cli/2.4.15/swagger-codegen-cli-2.4.15.jar -o swagger-codegen-cli-v2.jar 8 | curl https://repo1.maven.org/maven2/io/swagger/codegen/v3/swagger-codegen-cli/3.0.21/swagger-codegen-cli-3.0.21.jar -o swagger-codegen-cli-v3.jar 9 | 10 | java -jar ./swagger-codegen-cli-v2.jar generate -i spec/v2.json -l typescript-aurelia -o generated/v2/typescript-aurelia/ 11 | java -jar ./swagger-codegen-cli-v2.jar generate -i spec/v2.json -l typescript-angular -o generated/v2/typescript-angular/ 12 | java -jar ./swagger-codegen-cli-v2.jar generate -i spec/v2.json -l typescript-inversify -o generated/v2/typescript-inversify/ 13 | java -jar ./swagger-codegen-cli-v2.jar generate -i spec/v2.json -l typescript-fetch -o generated/v2/typescript-fetch/ 14 | java -jar ./swagger-codegen-cli-v2.jar generate -i spec/v2.json -l typescript-jquery -o generated/v2/typescript-jquery/ 15 | java -jar ./swagger-codegen-cli-v2.jar generate -i spec/v2.json -l typescript-node -o generated/v2/typescript-node/ 16 | 17 | java -jar ./swagger-codegen-cli-v3.jar generate -i spec/v3.json -l typescript-angular -o generated/v3/typescript-angular/ 18 | java -jar ./swagger-codegen-cli-v3.jar generate -i spec/v3.json -l typescript-fetch -o generated/v3/typescript-fetch/ 19 | 20 | node ../bin/index.js --input spec/v2.json --output generated/v2/openapi-typescript-codegen/ 21 | node ../bin/index.js --input spec/v3.json --output generated/v3/openapi-typescript-codegen/ 22 | -------------------------------------------------------------------------------- /src/HttpClient.ts: -------------------------------------------------------------------------------- 1 | export enum HttpClient { 2 | FETCH = 'fetch', 3 | XHR = 'xhr', 4 | NODE = 'node', 5 | AXIOS = 'axios', 6 | ANGULAR = 'angular', 7 | } 8 | -------------------------------------------------------------------------------- /src/Indent.ts: -------------------------------------------------------------------------------- 1 | export enum Indent { 2 | SPACE_4 = '4', 3 | SPACE_2 = '2', 4 | TAB = 'tab', 5 | } 6 | -------------------------------------------------------------------------------- /src/client/interfaces/Client.d.ts: -------------------------------------------------------------------------------- 1 | import type { Model } from './Model'; 2 | import type { Service } from './Service'; 3 | 4 | export interface Client { 5 | version: string; 6 | server: string; 7 | models: Model[]; 8 | services: Service[]; 9 | } 10 | -------------------------------------------------------------------------------- /src/client/interfaces/Enum.d.ts: -------------------------------------------------------------------------------- 1 | export interface Enum { 2 | name: string; 3 | value: string; 4 | type: string; 5 | description: string | null; 6 | } 7 | -------------------------------------------------------------------------------- /src/client/interfaces/Model.d.ts: -------------------------------------------------------------------------------- 1 | import type { Enum } from './Enum'; 2 | import type { Schema } from './Schema'; 3 | 4 | export interface Model extends Schema { 5 | name: string; 6 | export: 'reference' | 'generic' | 'enum' | 'array' | 'dictionary' | 'interface' | 'one-of' | 'any-of' | 'all-of'; 7 | type: string; 8 | base: string; 9 | template: string | null; 10 | link: Model | null; 11 | description: string | null; 12 | deprecated?: boolean; 13 | default?: string; 14 | imports: string[]; 15 | enum: Enum[]; 16 | enums: Model[]; 17 | properties: Model[]; 18 | } 19 | -------------------------------------------------------------------------------- /src/client/interfaces/ModelComposition.d.ts: -------------------------------------------------------------------------------- 1 | import type { Model } from './Model'; 2 | 3 | export interface ModelComposition { 4 | type: 'one-of' | 'any-of' | 'all-of'; 5 | imports: string[]; 6 | enums: Model[]; 7 | properties: Model[]; 8 | } 9 | -------------------------------------------------------------------------------- /src/client/interfaces/Operation.d.ts: -------------------------------------------------------------------------------- 1 | import type { OperationError } from './OperationError'; 2 | import type { OperationParameters } from './OperationParameters'; 3 | import type { OperationResponse } from './OperationResponse'; 4 | 5 | export interface Operation extends OperationParameters { 6 | service: string; 7 | name: string; 8 | summary: string | null; 9 | description: string | null; 10 | deprecated: boolean; 11 | method: string; 12 | path: string; 13 | errors: OperationError[]; 14 | results: OperationResponse[]; 15 | responseHeader: string | null; 16 | } 17 | -------------------------------------------------------------------------------- /src/client/interfaces/OperationError.d.ts: -------------------------------------------------------------------------------- 1 | export interface OperationError { 2 | code: number; 3 | description: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/client/interfaces/OperationParameter.d.ts: -------------------------------------------------------------------------------- 1 | import type { Model } from './Model'; 2 | 3 | export interface OperationParameter extends Model { 4 | in: 'path' | 'query' | 'header' | 'formData' | 'body' | 'cookie'; 5 | prop: string; 6 | mediaType: string | null; 7 | } 8 | -------------------------------------------------------------------------------- /src/client/interfaces/OperationParameters.d.ts: -------------------------------------------------------------------------------- 1 | import type { OperationParameter } from './OperationParameter'; 2 | 3 | export interface OperationParameters { 4 | imports: string[]; 5 | parameters: OperationParameter[]; 6 | parametersPath: OperationParameter[]; 7 | parametersQuery: OperationParameter[]; 8 | parametersForm: OperationParameter[]; 9 | parametersCookie: OperationParameter[]; 10 | parametersHeader: OperationParameter[]; 11 | parametersBody: OperationParameter | null; 12 | } 13 | -------------------------------------------------------------------------------- /src/client/interfaces/OperationResponse.d.ts: -------------------------------------------------------------------------------- 1 | import type { Model } from './Model'; 2 | 3 | export interface OperationResponse extends Model { 4 | in: 'response' | 'header'; 5 | code: number; 6 | } 7 | -------------------------------------------------------------------------------- /src/client/interfaces/Schema.d.ts: -------------------------------------------------------------------------------- 1 | export interface Schema { 2 | isDefinition: boolean; 3 | isReadOnly: boolean; 4 | isRequired: boolean; 5 | isNullable: boolean; 6 | format?: 7 | | 'int32' 8 | | 'int64' 9 | | 'float' 10 | | 'double' 11 | | 'string' 12 | | 'boolean' 13 | | 'byte' 14 | | 'binary' 15 | | 'date' 16 | | 'date-time' 17 | | 'password'; 18 | maximum?: number; 19 | exclusiveMaximum?: boolean; 20 | minimum?: number; 21 | exclusiveMinimum?: boolean; 22 | multipleOf?: number; 23 | maxLength?: number; 24 | minLength?: number; 25 | pattern?: string; 26 | maxItems?: number; 27 | minItems?: number; 28 | uniqueItems?: boolean; 29 | maxProperties?: number; 30 | minProperties?: number; 31 | } 32 | -------------------------------------------------------------------------------- /src/client/interfaces/Service.d.ts: -------------------------------------------------------------------------------- 1 | import type { Operation } from './Operation'; 2 | 3 | export interface Service { 4 | name: string; 5 | operations: Operation[]; 6 | imports: string[]; 7 | } 8 | -------------------------------------------------------------------------------- /src/client/interfaces/Type.d.ts: -------------------------------------------------------------------------------- 1 | export interface Type { 2 | type: string; 3 | base: string; 4 | template: string | null; 5 | imports: string[]; 6 | isNullable: boolean; 7 | } 8 | -------------------------------------------------------------------------------- /src/index.spec.ts: -------------------------------------------------------------------------------- 1 | import OpenAPI from './index'; 2 | 3 | describe('index', () => { 4 | it('parses v2 without issues', async () => { 5 | await OpenAPI.generate({ 6 | input: './test/spec/v2.json', 7 | output: './generated/v2/', 8 | write: false, 9 | }); 10 | }); 11 | 12 | it('parses v3 without issues', async () => { 13 | await OpenAPI.generate({ 14 | input: './test/spec/v3.json', 15 | output: './generated/v3/', 16 | write: false, 17 | }); 18 | }); 19 | 20 | it('downloads and parses v2 without issues', async () => { 21 | await OpenAPI.generate({ 22 | input: 'https://raw.githubusercontent.com/ferdikoomen/openapi-typescript-codegen/main/test/spec/v2.json', 23 | output: './generated/v2-downloaded/', 24 | write: false, 25 | }); 26 | }); 27 | 28 | it('downloads and parses v3 without issues', async () => { 29 | await OpenAPI.generate({ 30 | input: 'https://raw.githubusercontent.com/ferdikoomen/openapi-typescript-codegen/main/test/spec/v3.json', 31 | output: './generated/v3-downloaded/', 32 | write: false, 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/openApi/v2/index.ts: -------------------------------------------------------------------------------- 1 | import type { Client } from '../../client/interfaces/Client'; 2 | import type { OpenApi } from './interfaces/OpenApi'; 3 | import { getModels } from './parser/getModels'; 4 | import { getServer } from './parser/getServer'; 5 | import { getServices } from './parser/getServices'; 6 | import { getServiceVersion } from './parser/getServiceVersion'; 7 | 8 | /** 9 | * Parse the OpenAPI specification to a Client model that contains 10 | * all the models, services and schema's we should output. 11 | * @param openApi The OpenAPI spec that we have loaded from disk. 12 | */ 13 | export const parse = (openApi: OpenApi): Client => { 14 | const version = getServiceVersion(openApi.info.version); 15 | const server = getServer(openApi); 16 | const models = getModels(openApi); 17 | const services = getServices(openApi); 18 | 19 | return { version, server, models, services }; 20 | }; 21 | -------------------------------------------------------------------------------- /src/openApi/v2/interfaces/Extensions/WithEnumExtension.d.ts: -------------------------------------------------------------------------------- 1 | export interface WithEnumExtension { 2 | 'x-enum-varnames'?: string[]; 3 | 'x-enum-descriptions'?: string[]; 4 | } 5 | -------------------------------------------------------------------------------- /src/openApi/v2/interfaces/Extensions/WithNullableExtension.d.ts: -------------------------------------------------------------------------------- 1 | export interface WithNullableExtension { 2 | 'x-nullable'?: boolean; 3 | } 4 | -------------------------------------------------------------------------------- /src/openApi/v2/interfaces/OpenApi.d.ts: -------------------------------------------------------------------------------- 1 | import type { Dictionary } from '../../../utils/types'; 2 | import type { OpenApiExternalDocs } from './OpenApiExternalDocs'; 3 | import type { OpenApiInfo } from './OpenApiInfo'; 4 | import type { OpenApiParameter } from './OpenApiParameter'; 5 | import type { OpenApiPath } from './OpenApiPath'; 6 | import type { OpenApiResponse } from './OpenApiResponse'; 7 | import type { OpenApiSchema } from './OpenApiSchema'; 8 | import type { OpenApiSecurityRequirement } from './OpenApiSecurityRequirement'; 9 | import type { OpenApiSecurityScheme } from './OpenApiSecurityScheme'; 10 | import type { OpenApiTag } from './OpenApiTag'; 11 | 12 | /** 13 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md 14 | */ 15 | export interface OpenApi { 16 | swagger: string; 17 | info: OpenApiInfo; 18 | host?: string; 19 | basePath?: string; 20 | schemes?: string[]; 21 | consumes?: string[]; 22 | produces?: string[]; 23 | paths: Dictionary; 24 | definitions?: Dictionary; 25 | parameters?: Dictionary; 26 | responses?: Dictionary; 27 | securityDefinitions?: Dictionary; 28 | security?: OpenApiSecurityRequirement[]; 29 | tags?: OpenApiTag[]; 30 | externalDocs?: OpenApiExternalDocs; 31 | } 32 | -------------------------------------------------------------------------------- /src/openApi/v2/interfaces/OpenApiContact.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#contactObject 3 | */ 4 | export interface OpenApiContact { 5 | name?: string; 6 | url?: string; 7 | email?: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/openApi/v2/interfaces/OpenApiExample.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#exampleObject 3 | */ 4 | export interface OpenApiExample { 5 | [mimetype: string]: any; 6 | } 7 | -------------------------------------------------------------------------------- /src/openApi/v2/interfaces/OpenApiExternalDocs.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#externalDocumentationObject 3 | */ 4 | export interface OpenApiExternalDocs { 5 | description?: string; 6 | url: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/openApi/v2/interfaces/OpenApiHeader.d.ts: -------------------------------------------------------------------------------- 1 | import type { Dictionary } from '../../../utils/types'; 2 | import type { OpenApiItems } from './OpenApiItems'; 3 | 4 | /** 5 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#headerObject 6 | */ 7 | export interface OpenApiHeader { 8 | description?: string; 9 | type: 'string' | 'number' | 'integer' | 'boolean' | 'array'; 10 | format?: 11 | | 'int32' 12 | | 'int64' 13 | | 'float' 14 | | 'double' 15 | | 'string' 16 | | 'boolean' 17 | | 'byte' 18 | | 'binary' 19 | | 'date' 20 | | 'date-time' 21 | | 'password'; 22 | items?: Dictionary; 23 | collectionFormat?: 'csv' | 'ssv' | 'tsv' | 'pipes'; 24 | default?: any; 25 | maximum?: number; 26 | exclusiveMaximum?: boolean; 27 | minimum?: number; 28 | exclusiveMinimum?: boolean; 29 | maxLength?: number; 30 | minLength?: number; 31 | pattern?: string; 32 | maxItems?: number; 33 | minItems?: number; 34 | uniqueItems?: boolean; 35 | enum?: (string | number)[]; 36 | multipleOf?: number; 37 | } 38 | -------------------------------------------------------------------------------- /src/openApi/v2/interfaces/OpenApiInfo.d.ts: -------------------------------------------------------------------------------- 1 | import type { OpenApiContact } from './OpenApiContact'; 2 | import type { OpenApiLicense } from './OpenApiLicense'; 3 | 4 | /** 5 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#infoObject 6 | */ 7 | export interface OpenApiInfo { 8 | title: string; 9 | description?: string; 10 | termsOfService?: string; 11 | contact?: OpenApiContact; 12 | license?: OpenApiLicense; 13 | version: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/openApi/v2/interfaces/OpenApiItems.d.ts: -------------------------------------------------------------------------------- 1 | import type { WithEnumExtension } from './Extensions/WithEnumExtension'; 2 | 3 | /** 4 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#itemsObject 5 | */ 6 | export interface OpenApiItems extends WithEnumExtension { 7 | type?: string; 8 | format?: 9 | | 'int32' 10 | | 'int64' 11 | | 'float' 12 | | 'double' 13 | | 'string' 14 | | 'boolean' 15 | | 'byte' 16 | | 'binary' 17 | | 'date' 18 | | 'date-time' 19 | | 'password'; 20 | items?: OpenApiItems; 21 | collectionFormat?: 'csv' | 'ssv' | 'tsv' | 'pipes'; 22 | default?: any; 23 | maximum?: number; 24 | exclusiveMaximum?: number; 25 | minimum?: number; 26 | exclusiveMinimum?: number; 27 | maxLength?: number; 28 | minLength?: number; 29 | pattern?: string; 30 | maxItems?: number; 31 | minItems?: number; 32 | uniqueItems?: boolean; 33 | enum?: (string | number)[]; 34 | multipleOf?: number; 35 | } 36 | -------------------------------------------------------------------------------- /src/openApi/v2/interfaces/OpenApiLicense.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#licenseObject 3 | */ 4 | export interface OpenApiLicense { 5 | name: string; 6 | url?: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/openApi/v2/interfaces/OpenApiOperation.d.ts: -------------------------------------------------------------------------------- 1 | import type { OpenApiExternalDocs } from './OpenApiExternalDocs'; 2 | import type { OpenApiParameter } from './OpenApiParameter'; 3 | import type { OpenApiResponses } from './OpenApiResponses'; 4 | import type { OpenApiSecurityRequirement } from './OpenApiSecurityRequirement'; 5 | 6 | /** 7 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#operationObject 8 | */ 9 | export interface OpenApiOperation { 10 | tags?: string[]; 11 | summary?: string; 12 | description?: string; 13 | externalDocs?: OpenApiExternalDocs; 14 | operationId?: string; 15 | consumes?: string[]; 16 | produces?: string[]; 17 | parameters?: OpenApiParameter[]; 18 | responses: OpenApiResponses; 19 | schemes?: ('http' | 'https' | 'ws' | 'wss')[]; 20 | deprecated?: boolean; 21 | security?: OpenApiSecurityRequirement[]; 22 | } 23 | -------------------------------------------------------------------------------- /src/openApi/v2/interfaces/OpenApiParameter.d.ts: -------------------------------------------------------------------------------- 1 | import type { WithEnumExtension } from './Extensions/WithEnumExtension'; 2 | import type { WithNullableExtension } from './Extensions/WithNullableExtension'; 3 | import type { OpenApiItems } from './OpenApiItems'; 4 | import type { OpenApiReference } from './OpenApiReference'; 5 | import type { OpenApiSchema } from './OpenApiSchema'; 6 | 7 | /** 8 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#parameterObject 9 | */ 10 | export interface OpenApiParameter extends OpenApiReference, WithEnumExtension, WithNullableExtension { 11 | name: string; 12 | in: 'path' | 'query' | 'header' | 'formData' | 'body'; 13 | description?: string; 14 | required?: boolean; 15 | schema?: OpenApiSchema; 16 | type?: string; 17 | format?: 18 | | 'int32' 19 | | 'int64' 20 | | 'float' 21 | | 'double' 22 | | 'string' 23 | | 'boolean' 24 | | 'byte' 25 | | 'binary' 26 | | 'date' 27 | | 'date-time' 28 | | 'password'; 29 | allowEmptyValue?: boolean; 30 | items?: OpenApiItems; 31 | collectionFormat?: 'csv' | 'ssv' | 'tsv' | 'pipes' | 'multi'; 32 | default?: any; 33 | maximum?: number; 34 | exclusiveMaximum?: boolean; 35 | minimum?: number; 36 | exclusiveMinimum?: boolean; 37 | maxLength?: number; 38 | minLength?: number; 39 | pattern?: string; 40 | maxItems?: number; 41 | minItems?: number; 42 | uniqueItems?: boolean; 43 | enum?: (string | number)[]; 44 | multipleOf?: number; 45 | } 46 | -------------------------------------------------------------------------------- /src/openApi/v2/interfaces/OpenApiPath.d.ts: -------------------------------------------------------------------------------- 1 | import type { OpenApiOperation } from './OpenApiOperation'; 2 | import type { OpenApiParameter } from './OpenApiParameter'; 3 | import type { OpenApiReference } from './OpenApiReference'; 4 | 5 | /** 6 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#pathItemObject 7 | */ 8 | export interface OpenApiPath extends OpenApiReference { 9 | get?: OpenApiOperation; 10 | put?: OpenApiOperation; 11 | post?: OpenApiOperation; 12 | delete?: OpenApiOperation; 13 | options?: OpenApiOperation; 14 | head?: OpenApiOperation; 15 | patch?: OpenApiOperation; 16 | parameters?: OpenApiParameter[]; 17 | } 18 | -------------------------------------------------------------------------------- /src/openApi/v2/interfaces/OpenApiReference.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#referenceObject 3 | */ 4 | export interface OpenApiReference { 5 | $ref?: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/openApi/v2/interfaces/OpenApiResponse.d.ts: -------------------------------------------------------------------------------- 1 | import type { Dictionary } from '../../../utils/types'; 2 | import type { OpenApiExample } from './OpenApiExample'; 3 | import type { OpenApiHeader } from './OpenApiHeader'; 4 | import type { OpenApiReference } from './OpenApiReference'; 5 | import type { OpenApiSchema } from './OpenApiSchema'; 6 | 7 | /** 8 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#responseObject 9 | */ 10 | export interface OpenApiResponse extends OpenApiReference { 11 | description: string; 12 | schema?: OpenApiSchema & OpenApiReference; 13 | headers?: Dictionary; 14 | examples?: OpenApiExample; 15 | } 16 | -------------------------------------------------------------------------------- /src/openApi/v2/interfaces/OpenApiResponses.d.ts: -------------------------------------------------------------------------------- 1 | import type { OpenApiResponse } from './OpenApiResponse'; 2 | 3 | /** 4 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#responsesObject 5 | */ 6 | export interface OpenApiResponses { 7 | default?: OpenApiResponse; 8 | 9 | [httpcode: string]: OpenApiResponse; 10 | } 11 | -------------------------------------------------------------------------------- /src/openApi/v2/interfaces/OpenApiSchema.d.ts: -------------------------------------------------------------------------------- 1 | import type { Dictionary } from '../../../utils/types'; 2 | import type { WithEnumExtension } from './Extensions/WithEnumExtension'; 3 | import type { WithNullableExtension } from './Extensions/WithNullableExtension'; 4 | import type { OpenApiExternalDocs } from './OpenApiExternalDocs'; 5 | import type { OpenApiReference } from './OpenApiReference'; 6 | import type { OpenApiXml } from './OpenApiXml'; 7 | 8 | /** 9 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#schemaObject 10 | */ 11 | export interface OpenApiSchema extends OpenApiReference, WithEnumExtension, WithNullableExtension { 12 | title?: string; 13 | description?: string; 14 | default?: any; 15 | multipleOf?: number; 16 | maximum?: number; 17 | exclusiveMaximum?: boolean; 18 | minimum?: number; 19 | exclusiveMinimum?: boolean; 20 | maxLength?: number; 21 | minLength?: number; 22 | pattern?: string; 23 | maxItems?: number; 24 | minItems?: number; 25 | uniqueItems?: boolean; 26 | maxProperties?: number; 27 | minProperties?: number; 28 | required?: string[]; 29 | enum?: (string | number)[]; 30 | type?: string; 31 | format?: 32 | | 'int32' 33 | | 'int64' 34 | | 'float' 35 | | 'double' 36 | | 'string' 37 | | 'boolean' 38 | | 'byte' 39 | | 'binary' 40 | | 'date' 41 | | 'date-time' 42 | | 'password'; 43 | items?: OpenApiSchema; 44 | allOf?: OpenApiSchema[]; 45 | properties?: Dictionary; 46 | additionalProperties?: boolean | OpenApiSchema; 47 | discriminator?: string; 48 | readOnly?: boolean; 49 | xml?: OpenApiXml; 50 | externalDocs?: OpenApiExternalDocs; 51 | example?: any; 52 | } 53 | -------------------------------------------------------------------------------- /src/openApi/v2/interfaces/OpenApiSecurityRequirement.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#securityRequirementObject 3 | */ 4 | export interface OpenApiSecurityRequirement { 5 | [key: string]: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/openApi/v2/interfaces/OpenApiSecurityScheme.d.ts: -------------------------------------------------------------------------------- 1 | import type { Dictionary } from '../../../utils/types'; 2 | 3 | /** 4 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#securitySchemeObject 5 | */ 6 | export interface OpenApiSecurityScheme { 7 | type: 'basic' | 'apiKey' | 'oauth2'; 8 | description?: string; 9 | name?: string; 10 | in?: 'query' | 'header'; 11 | flow?: 'implicit' | 'password' | 'application' | 'accessCode'; 12 | authorizationUrl?: string; 13 | tokenUrl?: string; 14 | scopes: Dictionary; 15 | } 16 | -------------------------------------------------------------------------------- /src/openApi/v2/interfaces/OpenApiTag.d.ts: -------------------------------------------------------------------------------- 1 | import type { OpenApiExternalDocs } from './OpenApiExternalDocs'; 2 | 3 | /** 4 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#tagObject 5 | */ 6 | export interface OpenApiTag { 7 | name: string; 8 | description?: string; 9 | externalDocs?: OpenApiExternalDocs; 10 | } 11 | -------------------------------------------------------------------------------- /src/openApi/v2/interfaces/OpenApiXml.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#xmlObject 3 | */ 4 | export interface OpenApiXml { 5 | name?: string; 6 | namespace?: string; 7 | prefix?: string; 8 | attribute?: boolean; 9 | wrapped?: boolean; 10 | } 11 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/escapeName.spec.ts: -------------------------------------------------------------------------------- 1 | import { escapeName } from './escapeName'; 2 | 3 | describe('escapeName', () => { 4 | it('should escape', () => { 5 | expect(escapeName('')).toEqual("''"); 6 | expect(escapeName('fooBar')).toEqual('fooBar'); 7 | expect(escapeName('Foo Bar')).toEqual(`'Foo Bar'`); 8 | expect(escapeName('foo bar')).toEqual(`'foo bar'`); 9 | expect(escapeName('foo-bar')).toEqual(`'foo-bar'`); 10 | expect(escapeName('foo.bar')).toEqual(`'foo.bar'`); 11 | expect(escapeName('foo_bar')).toEqual('foo_bar'); 12 | expect(escapeName('123foo.bar')).toEqual(`'123foo.bar'`); 13 | expect(escapeName('@foo.bar')).toEqual(`'@foo.bar'`); 14 | expect(escapeName('$foo.bar')).toEqual(`'$foo.bar'`); 15 | expect(escapeName('_foo.bar')).toEqual(`'_foo.bar'`); 16 | expect(escapeName('123foobar')).toEqual(`'123foobar'`); 17 | expect(escapeName('@foobar')).toEqual(`'@foobar'`); 18 | expect(escapeName('$foobar')).toEqual('$foobar'); 19 | expect(escapeName('_foobar')).toEqual('_foobar'); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/escapeName.ts: -------------------------------------------------------------------------------- 1 | export const escapeName = (value: string): string => { 2 | if (value || value === '') { 3 | const validName = /^[a-zA-Z_$][\w$]+$/g.test(value); 4 | if (!validName) { 5 | return `'${value}'`; 6 | } 7 | } 8 | return value; 9 | }; 10 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/extendEnum.ts: -------------------------------------------------------------------------------- 1 | import type { Enum } from '../../../client/interfaces/Enum'; 2 | import { isString } from '../../../utils/isString'; 3 | import type { WithEnumExtension } from '../interfaces/Extensions/WithEnumExtension'; 4 | 5 | /** 6 | * Extend the enum with the x-enum properties. This adds the capability 7 | * to use names and descriptions inside the generated enums. 8 | * @param enumerators 9 | * @param definition 10 | */ 11 | export const extendEnum = (enumerators: Enum[], definition: WithEnumExtension): Enum[] => { 12 | const names = definition['x-enum-varnames']?.filter(isString); 13 | const descriptions = definition['x-enum-descriptions']?.filter(isString); 14 | 15 | return enumerators.map((enumerator, index) => ({ 16 | name: names?.[index] || enumerator.name, 17 | description: descriptions?.[index] || enumerator.description, 18 | value: enumerator.value, 19 | type: enumerator.type, 20 | })); 21 | }; 22 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getEnum.ts: -------------------------------------------------------------------------------- 1 | import type { Enum } from '../../../client/interfaces/Enum'; 2 | 3 | export const getEnum = (values?: (string | number)[]): Enum[] => { 4 | if (Array.isArray(values)) { 5 | return values 6 | .filter((value, index, arr) => { 7 | return arr.indexOf(value) === index; 8 | }) 9 | .filter((value: any) => { 10 | return typeof value === 'number' || typeof value === 'string'; 11 | }) 12 | .map(value => { 13 | if (typeof value === 'number') { 14 | return { 15 | name: `'_${value}'`, 16 | value: String(value), 17 | type: 'number', 18 | description: null, 19 | }; 20 | } 21 | return { 22 | name: String(value) 23 | .replace(/\W+/g, '_') 24 | .replace(/^(\d+)/g, '_$1') 25 | .replace(/([a-z])([A-Z]+)/g, '$1_$2') 26 | .toUpperCase(), 27 | value: `'${value.replace(/'/g, "\\'")}'`, 28 | type: 'string', 29 | description: null, 30 | }; 31 | }); 32 | } 33 | return []; 34 | }; 35 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getMappedType.spec.ts: -------------------------------------------------------------------------------- 1 | import { getMappedType } from './getMappedType'; 2 | 3 | describe('getMappedType', () => { 4 | it('should map types to the basics', () => { 5 | expect(getMappedType('file')).toEqual('binary'); 6 | expect(getMappedType('string')).toEqual('string'); 7 | expect(getMappedType('date')).toEqual('string'); 8 | expect(getMappedType('date-time')).toEqual('string'); 9 | expect(getMappedType('float')).toEqual('number'); 10 | expect(getMappedType('double')).toEqual('number'); 11 | expect(getMappedType('short')).toEqual('number'); 12 | expect(getMappedType('int')).toEqual('number'); 13 | expect(getMappedType('boolean')).toEqual('boolean'); 14 | expect(getMappedType('any')).toEqual('any'); 15 | expect(getMappedType('object')).toEqual('any'); 16 | expect(getMappedType('void')).toEqual('void'); 17 | expect(getMappedType('null')).toEqual('null'); 18 | expect(getMappedType('unknown')).toEqual(undefined); 19 | expect(getMappedType('')).toEqual(undefined); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getMappedType.ts: -------------------------------------------------------------------------------- 1 | const TYPE_MAPPINGS = new Map([ 2 | ['file', 'binary'], 3 | ['any', 'any'], 4 | ['object', 'any'], 5 | ['array', 'any[]'], 6 | ['boolean', 'boolean'], 7 | ['byte', 'number'], 8 | ['int', 'number'], 9 | ['integer', 'number'], 10 | ['float', 'number'], 11 | ['double', 'number'], 12 | ['short', 'number'], 13 | ['long', 'number'], 14 | ['number', 'number'], 15 | ['char', 'string'], 16 | ['date', 'string'], 17 | ['date-time', 'string'], 18 | ['password', 'string'], 19 | ['string', 'string'], 20 | ['void', 'void'], 21 | ['null', 'null'], 22 | ]); 23 | 24 | /** 25 | * Get mapped type for given type to any basic Typescript/Javascript type. 26 | */ 27 | export const getMappedType = (type: string, format?: string): string | undefined => { 28 | if (format === 'binary') { 29 | return 'binary'; 30 | } 31 | return TYPE_MAPPINGS.get(type); 32 | }; 33 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getModelTemplate.spec.ts: -------------------------------------------------------------------------------- 1 | import { getModelTemplate } from './getModelTemplate'; 2 | 3 | describe('getModelTemplate', () => { 4 | it('should return generic for template type', () => { 5 | const template = getModelTemplate({ 6 | type: 'Link', 7 | base: 'Link', 8 | template: 'Model', 9 | imports: ['Model'], 10 | isNullable: false, 11 | }); 12 | expect(template).toEqual(''); 13 | }); 14 | 15 | it('should return empty for primary type', () => { 16 | const template = getModelTemplate({ 17 | type: 'string', 18 | base: 'string', 19 | template: null, 20 | imports: [], 21 | isNullable: false, 22 | }); 23 | expect(template).toEqual(''); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getModelTemplate.ts: -------------------------------------------------------------------------------- 1 | import type { Type } from '../../../client/interfaces/Type'; 2 | 3 | /** 4 | * If our model has a template type, then we want to generalize that! 5 | * In that case we should return "" as our template type. 6 | * @param modelClass The parsed model class type. 7 | * @returns The model template type ( or empty). 8 | */ 9 | export const getModelTemplate = (modelClass: Type): string => { 10 | return modelClass.template ? '' : ''; 11 | }; 12 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getModels.ts: -------------------------------------------------------------------------------- 1 | import type { Model } from '../../../client/interfaces/Model'; 2 | import { reservedWords } from '../../../utils/reservedWords'; 3 | import type { OpenApi } from '../interfaces/OpenApi'; 4 | import { getModel } from './getModel'; 5 | import { getType } from './getType'; 6 | 7 | export const getModels = (openApi: OpenApi): Model[] => { 8 | const models: Model[] = []; 9 | for (const definitionName in openApi.definitions) { 10 | if (openApi.definitions.hasOwnProperty(definitionName)) { 11 | const definition = openApi.definitions[definitionName]; 12 | const definitionType = getType(definitionName); 13 | const model = getModel(openApi, definition, true, definitionType.base.replace(reservedWords, '_$1')); 14 | models.push(model); 15 | } 16 | } 17 | return models; 18 | }; 19 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getOperationErrors.ts: -------------------------------------------------------------------------------- 1 | import type { OperationError } from '../../../client/interfaces/OperationError'; 2 | import type { OperationResponse } from '../../../client/interfaces/OperationResponse'; 3 | 4 | /** 5 | * 6 | * @param operationResponses 7 | */ 8 | export const getOperationErrors = (operationResponses: OperationResponse[]): OperationError[] => { 9 | return operationResponses 10 | .filter(operationResponse => { 11 | return operationResponse.code >= 300 && operationResponse.description; 12 | }) 13 | .map(response => ({ 14 | code: response.code, 15 | description: response.description!, 16 | })); 17 | }; 18 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getOperationName.ts: -------------------------------------------------------------------------------- 1 | import camelCase from 'camelcase'; 2 | 3 | /** 4 | * Convert the input value to a correct operation (method) classname. 5 | * This will use the operation ID - if available - and otherwise fallback 6 | * on a generated name from the URL 7 | */ 8 | export const getOperationName = (url: string, method: string, operationId?: string): string => { 9 | if (operationId) { 10 | return camelCase( 11 | operationId 12 | .replace(/^[^a-zA-Z]+/g, '') 13 | .replace(/[^\w\-]+/g, '-') 14 | .trim() 15 | ); 16 | } 17 | 18 | const urlWithoutPlaceholders = url 19 | .replace(/[^/]*?{api-version}.*?\//g, '') 20 | .replace(/{(.*?)}/g, '') 21 | .replace(/\//g, '-'); 22 | 23 | return camelCase(`${method}-${urlWithoutPlaceholders}`); 24 | }; 25 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getOperationParameterDefault.ts: -------------------------------------------------------------------------------- 1 | import type { OperationParameter } from '../../../client/interfaces/OperationParameter'; 2 | import type { OpenApiParameter } from '../interfaces/OpenApiParameter'; 3 | 4 | export const getOperationParameterDefault = ( 5 | parameter: OpenApiParameter, 6 | operationParameter: OperationParameter 7 | ): string | undefined => { 8 | if (parameter.default === undefined) { 9 | return undefined; 10 | } 11 | 12 | if (parameter.default === null) { 13 | return 'null'; 14 | } 15 | 16 | const type = parameter.type || typeof parameter.default; 17 | 18 | switch (type) { 19 | case 'int': 20 | case 'integer': 21 | case 'number': 22 | if (operationParameter.export === 'enum' && operationParameter.enum?.[parameter.default]) { 23 | return operationParameter.enum[parameter.default].value; 24 | } 25 | return parameter.default; 26 | 27 | case 'boolean': 28 | return JSON.stringify(parameter.default); 29 | 30 | case 'string': 31 | return `'${parameter.default}'`; 32 | 33 | case 'object': 34 | try { 35 | return JSON.stringify(parameter.default, null, 4); 36 | } catch (e) { 37 | // Ignore 38 | } 39 | } 40 | 41 | return undefined; 42 | }; 43 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getOperationParameterName.spec.ts: -------------------------------------------------------------------------------- 1 | import { getOperationParameterName } from './getOperationParameterName'; 2 | 3 | describe('getOperationParameterName', () => { 4 | it('should produce correct result', () => { 5 | expect(getOperationParameterName('')).toEqual(''); 6 | expect(getOperationParameterName('foobar')).toEqual('foobar'); 7 | expect(getOperationParameterName('fooBar')).toEqual('fooBar'); 8 | expect(getOperationParameterName('foo_bar')).toEqual('fooBar'); 9 | expect(getOperationParameterName('foo-bar')).toEqual('fooBar'); 10 | expect(getOperationParameterName('foo.bar')).toEqual('fooBar'); 11 | expect(getOperationParameterName('@foo.bar')).toEqual('fooBar'); 12 | expect(getOperationParameterName('$foo.bar')).toEqual('fooBar'); 13 | expect(getOperationParameterName('123.foo.bar')).toEqual('fooBar'); 14 | expect(getOperationParameterName('Foo-Bar')).toEqual('fooBar'); 15 | expect(getOperationParameterName('FOO-BAR')).toEqual('fooBar'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getOperationParameterName.ts: -------------------------------------------------------------------------------- 1 | import camelCase from 'camelcase'; 2 | 3 | import { reservedWords } from '../../../utils/reservedWords'; 4 | 5 | /** 6 | * Replaces any invalid characters from a parameter name. 7 | * For example: 'filter.someProperty' becomes 'filterSomeProperty'. 8 | */ 9 | export const getOperationParameterName = (value: string): string => { 10 | const clean = value 11 | .replace(/^[^a-zA-Z]+/g, '') 12 | .replace(/[^\w\-]+/g, '-') 13 | .trim(); 14 | return camelCase(clean).replace(reservedWords, '_$1'); 15 | }; 16 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getOperationResponseCode.spec.ts: -------------------------------------------------------------------------------- 1 | import { getOperationResponseCode } from './getOperationResponseCode'; 2 | 3 | describe('getOperationResponseCode', () => { 4 | it('should produce correct result', () => { 5 | expect(getOperationResponseCode('')).toEqual(null); 6 | expect(getOperationResponseCode('default')).toEqual(200); 7 | expect(getOperationResponseCode('200')).toEqual(200); 8 | expect(getOperationResponseCode('300')).toEqual(300); 9 | expect(getOperationResponseCode('400')).toEqual(400); 10 | expect(getOperationResponseCode('abc')).toEqual(null); 11 | expect(getOperationResponseCode('-100')).toEqual(100); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getOperationResponseCode.ts: -------------------------------------------------------------------------------- 1 | export const getOperationResponseCode = (value: string | 'default'): number | null => { 2 | // You can specify a "default" response, this is treated as HTTP code 200 3 | if (value === 'default') { 4 | return 200; 5 | } 6 | 7 | // Check if we can parse the code and return of successful. 8 | if (/[0-9]+/g.test(value)) { 9 | const code = parseInt(value); 10 | if (Number.isInteger(code)) { 11 | return Math.abs(code); 12 | } 13 | } 14 | 15 | return null; 16 | }; 17 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getOperationResponseHeader.ts: -------------------------------------------------------------------------------- 1 | import type { OperationResponse } from '../../../client/interfaces/OperationResponse'; 2 | 3 | export const getOperationResponseHeader = (operationResponses: OperationResponse[]): string | null => { 4 | const header = operationResponses.find(operationResponses => { 5 | return operationResponses.in === 'header'; 6 | }); 7 | if (header) { 8 | return header.name; 9 | } 10 | return null; 11 | }; 12 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getOperationResponses.ts: -------------------------------------------------------------------------------- 1 | import type { OperationResponse } from '../../../client/interfaces/OperationResponse'; 2 | import type { OpenApi } from '../interfaces/OpenApi'; 3 | import type { OpenApiResponse } from '../interfaces/OpenApiResponse'; 4 | import type { OpenApiResponses } from '../interfaces/OpenApiResponses'; 5 | import { getOperationResponse } from './getOperationResponse'; 6 | import { getOperationResponseCode } from './getOperationResponseCode'; 7 | import { getRef } from './getRef'; 8 | 9 | export const getOperationResponses = (openApi: OpenApi, responses: OpenApiResponses): OperationResponse[] => { 10 | const operationResponses: OperationResponse[] = []; 11 | 12 | // Iterate over each response code and get the 13 | // status code and response message (if any). 14 | for (const code in responses) { 15 | if (responses.hasOwnProperty(code)) { 16 | const responseOrReference = responses[code]; 17 | const response = getRef(openApi, responseOrReference); 18 | const responseCode = getOperationResponseCode(code); 19 | 20 | if (responseCode) { 21 | const operationResponse = getOperationResponse(openApi, response, responseCode); 22 | operationResponses.push(operationResponse); 23 | } 24 | } 25 | } 26 | 27 | // Sort the responses to 2XX success codes come before 4XX and 5XX error codes. 28 | return operationResponses.sort((a, b): number => { 29 | return a.code < b.code ? -1 : a.code > b.code ? 1 : 0; 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getOperationResults.ts: -------------------------------------------------------------------------------- 1 | import type { Model } from '../../../client/interfaces/Model'; 2 | import type { OperationResponse } from '../../../client/interfaces/OperationResponse'; 3 | 4 | const areEqual = (a: Model, b: Model): boolean => { 5 | const equal = a.type === b.type && a.base === b.base && a.template === b.template; 6 | if (equal && a.link && b.link) { 7 | return areEqual(a.link, b.link); 8 | } 9 | return equal; 10 | }; 11 | 12 | export const getOperationResults = (operationResponses: OperationResponse[]): OperationResponse[] => { 13 | const operationResults: OperationResponse[] = []; 14 | 15 | // Filter out success response codes, but skip "204 No Content" 16 | operationResponses.forEach(operationResponse => { 17 | const { code } = operationResponse; 18 | if (code && code !== 204 && code >= 200 && code < 300) { 19 | operationResults.push(operationResponse); 20 | } 21 | }); 22 | 23 | if (!operationResults.length) { 24 | operationResults.push({ 25 | in: 'response', 26 | name: '', 27 | code: 200, 28 | description: '', 29 | export: 'generic', 30 | type: 'void', 31 | base: 'void', 32 | template: null, 33 | link: null, 34 | isDefinition: false, 35 | isReadOnly: false, 36 | isRequired: false, 37 | isNullable: false, 38 | imports: [], 39 | enum: [], 40 | enums: [], 41 | properties: [], 42 | }); 43 | } 44 | 45 | return operationResults.filter((operationResult, index, arr) => { 46 | return ( 47 | arr.findIndex(item => { 48 | return areEqual(item, operationResult); 49 | }) === index 50 | ); 51 | }); 52 | }; 53 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getRef.spec.ts: -------------------------------------------------------------------------------- 1 | import { getRef } from './getRef'; 2 | 3 | describe('getRef', () => { 4 | it('should produce correct result', () => { 5 | expect( 6 | getRef( 7 | { 8 | swagger: '2.0', 9 | info: { 10 | title: 'dummy', 11 | version: '1.0', 12 | }, 13 | host: 'localhost:8080', 14 | basePath: '/api', 15 | schemes: ['http', 'https'], 16 | paths: {}, 17 | definitions: { 18 | Example: { 19 | description: 'This is an Example model ', 20 | type: 'integer', 21 | }, 22 | }, 23 | }, 24 | { 25 | $ref: '#/definitions/Example', 26 | } 27 | ) 28 | ).toEqual({ 29 | description: 'This is an Example model ', 30 | type: 'integer', 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getRef.ts: -------------------------------------------------------------------------------- 1 | import type { OpenApi } from '../interfaces/OpenApi'; 2 | import type { OpenApiReference } from '../interfaces/OpenApiReference'; 3 | 4 | const ESCAPED_REF_SLASH = /~1/g; 5 | const ESCAPED_REF_TILDE = /~0/g; 6 | 7 | export const getRef = (openApi: OpenApi, item: T & OpenApiReference): T => { 8 | if (item.$ref) { 9 | // Fetch the paths to the definitions, this converts: 10 | // "#/definitions/Form" to ["definitions", "Form"] 11 | const paths = item.$ref 12 | .replace(/^#/g, '') 13 | .split('/') 14 | .filter(item => item); 15 | 16 | // Try to find the reference by walking down the path, 17 | // if we cannot find it, then we throw an error. 18 | let result: any = openApi; 19 | paths.forEach(path => { 20 | const decodedPath = decodeURIComponent( 21 | path.replace(ESCAPED_REF_SLASH, '/').replace(ESCAPED_REF_TILDE, '~') 22 | ); 23 | if (result.hasOwnProperty(decodedPath)) { 24 | result = result[decodedPath]; 25 | } else { 26 | throw new Error(`Could not find reference: "${item.$ref}"`); 27 | } 28 | }); 29 | return result as T; 30 | } 31 | return item as T; 32 | }; 33 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getRequiredPropertiesFromComposition.ts: -------------------------------------------------------------------------------- 1 | import type { Model } from '../../../client/interfaces/Model'; 2 | import type { OpenApi } from '../interfaces/OpenApi'; 3 | import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; 4 | import type { getModel } from './getModel'; 5 | import { getRef } from './getRef'; 6 | 7 | // Fix for circular dependency 8 | export type GetModelFn = typeof getModel; 9 | 10 | export const getRequiredPropertiesFromComposition = ( 11 | openApi: OpenApi, 12 | required: string[], 13 | definitions: OpenApiSchema[], 14 | getModel: GetModelFn 15 | ): Model[] => { 16 | return definitions 17 | .reduce((properties, definition) => { 18 | if (definition.$ref) { 19 | const schema = getRef(openApi, definition); 20 | return [...properties, ...getModel(openApi, schema).properties]; 21 | } 22 | return [...properties, ...getModel(openApi, definition).properties]; 23 | }, [] as Model[]) 24 | .filter(property => { 25 | return !property.isRequired && required.includes(property.name); 26 | }) 27 | .map(property => { 28 | return { 29 | ...property, 30 | isRequired: true, 31 | }; 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getServer.spec.ts: -------------------------------------------------------------------------------- 1 | import { getServer } from './getServer'; 2 | 3 | describe('getServer', () => { 4 | it('should produce correct result', () => { 5 | expect( 6 | getServer({ 7 | swagger: '2.0', 8 | info: { 9 | title: 'dummy', 10 | version: '1.0', 11 | }, 12 | host: 'localhost:8080', 13 | basePath: '/api', 14 | schemes: ['http', 'https'], 15 | paths: {}, 16 | }) 17 | ).toEqual('http://localhost:8080/api'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getServer.ts: -------------------------------------------------------------------------------- 1 | import type { OpenApi } from '../interfaces/OpenApi'; 2 | 3 | /** 4 | * Get the base server url. 5 | * @param openApi 6 | */ 7 | export const getServer = (openApi: OpenApi): string => { 8 | const scheme = openApi.schemes?.[0] || 'http'; 9 | const host = openApi.host; 10 | const basePath = openApi.basePath || ''; 11 | const url = host ? `${scheme}://${host}${basePath}` : basePath; 12 | return url.replace(/\/$/g, ''); 13 | }; 14 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getServiceName.spec.ts: -------------------------------------------------------------------------------- 1 | import { getServiceName } from './getServiceName'; 2 | 3 | describe('getServiceName', () => { 4 | it('should produce correct result', () => { 5 | expect(getServiceName('')).toEqual(''); 6 | expect(getServiceName('FooBar')).toEqual('FooBar'); 7 | expect(getServiceName('Foo Bar')).toEqual('FooBar'); 8 | expect(getServiceName('foo bar')).toEqual('FooBar'); 9 | expect(getServiceName('@fooBar')).toEqual('FooBar'); 10 | expect(getServiceName('$fooBar')).toEqual('FooBar'); 11 | expect(getServiceName('123fooBar')).toEqual('FooBar'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getServiceName.ts: -------------------------------------------------------------------------------- 1 | import camelCase from 'camelcase'; 2 | 3 | /** 4 | * Convert the input value to a correct service name. This converts 5 | * the input string to PascalCase. 6 | */ 7 | export const getServiceName = (value: string): string => { 8 | const clean = value 9 | .replace(/^[^a-zA-Z]+/g, '') 10 | .replace(/[^\w\-]+/g, '-') 11 | .trim(); 12 | return camelCase(clean, { pascalCase: true }); 13 | }; 14 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getServiceVersion.spec.ts: -------------------------------------------------------------------------------- 1 | import { getServiceVersion } from './getServiceVersion'; 2 | 3 | describe('getServiceVersion', () => { 4 | it('should produce correct result', () => { 5 | expect(getServiceVersion('1.0')).toEqual('1.0'); 6 | expect(getServiceVersion('v1.0')).toEqual('1.0'); 7 | expect(getServiceVersion('V1.0')).toEqual('1.0'); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getServiceVersion.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Convert the service version to 'normal' version. 3 | * This basically removes any "v" prefix from the version string. 4 | * @param version 5 | */ 6 | export const getServiceVersion = (version = '1.0'): string => { 7 | return String(version).replace(/^v/gi, ''); 8 | }; 9 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/getServices.spec.ts: -------------------------------------------------------------------------------- 1 | import { getServices } from './getServices'; 2 | 3 | describe('getServices', () => { 4 | it('should create a unnamed service if tags are empty', () => { 5 | const services = getServices({ 6 | swagger: '2.0', 7 | info: { 8 | title: 'x', 9 | version: '1', 10 | }, 11 | paths: { 12 | '/api/trips': { 13 | get: { 14 | tags: [], 15 | responses: { 16 | 200: { 17 | description: 'x', 18 | }, 19 | default: { 20 | description: 'default', 21 | }, 22 | }, 23 | }, 24 | }, 25 | }, 26 | }); 27 | 28 | expect(services).toHaveLength(1); 29 | expect(services[0].name).toEqual('Default'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/sortByRequired.ts: -------------------------------------------------------------------------------- 1 | import type { OperationParameter } from '../../../client/interfaces/OperationParameter'; 2 | 3 | export const sortByRequired = (a: OperationParameter, b: OperationParameter): number => { 4 | const aNeedsValue = a.isRequired && a.default === undefined; 5 | const bNeedsValue = b.isRequired && b.default === undefined; 6 | if (aNeedsValue && !bNeedsValue) return -1; 7 | if (bNeedsValue && !aNeedsValue) return 1; 8 | return 0; 9 | }; 10 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/stripNamespace.spec.ts: -------------------------------------------------------------------------------- 1 | import { stripNamespace } from './stripNamespace'; 2 | 3 | describe('stripNamespace', () => { 4 | it('should strip namespace', () => { 5 | expect(stripNamespace('#/definitions/Item')).toEqual('Item'); 6 | expect(stripNamespace('#/parameters/Item')).toEqual('Item'); 7 | expect(stripNamespace('#/responses/Item')).toEqual('Item'); 8 | expect(stripNamespace('#/securityDefinitions/Item')).toEqual('Item'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/openApi/v2/parser/stripNamespace.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Strip (OpenAPI) namespaces fom values. 3 | * @param value 4 | */ 5 | export const stripNamespace = (value: string): string => { 6 | return value 7 | .trim() 8 | .replace(/^#\/definitions\//, '') 9 | .replace(/^#\/parameters\//, '') 10 | .replace(/^#\/responses\//, '') 11 | .replace(/^#\/securityDefinitions\//, ''); 12 | }; 13 | -------------------------------------------------------------------------------- /src/openApi/v3/index.ts: -------------------------------------------------------------------------------- 1 | import type { Client } from '../../client/interfaces/Client'; 2 | import type { OpenApi } from './interfaces/OpenApi'; 3 | import { getModels } from './parser/getModels'; 4 | import { getServer } from './parser/getServer'; 5 | import { getServices } from './parser/getServices'; 6 | import { getServiceVersion } from './parser/getServiceVersion'; 7 | 8 | /** 9 | * Parse the OpenAPI specification to a Client model that contains 10 | * all the models, services and schema's we should output. 11 | * @param openApi The OpenAPI spec that we have loaded from disk. 12 | */ 13 | export const parse = (openApi: OpenApi): Client => { 14 | const version = getServiceVersion(openApi.info.version); 15 | const server = getServer(openApi); 16 | const models = getModels(openApi); 17 | const services = getServices(openApi); 18 | 19 | return { version, server, models, services }; 20 | }; 21 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/Extensions/WithEnumExtension.d.ts: -------------------------------------------------------------------------------- 1 | export interface WithEnumExtension { 2 | 'x-enum-varnames'?: string[]; 3 | 'x-enum-descriptions'?: string[]; 4 | } 5 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApi.d.ts: -------------------------------------------------------------------------------- 1 | import type { OpenApiComponents } from './OpenApiComponents'; 2 | import type { OpenApiExternalDocs } from './OpenApiExternalDocs'; 3 | import type { OpenApiInfo } from './OpenApiInfo'; 4 | import type { OpenApiPaths } from './OpenApiPaths'; 5 | import type { OpenApiSecurityRequirement } from './OpenApiSecurityRequirement'; 6 | import type { OpenApiServer } from './OpenApiServer'; 7 | import type { OpenApiTag } from './OpenApiTag'; 8 | 9 | /** 10 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md 11 | */ 12 | export interface OpenApi { 13 | openapi: string; 14 | info: OpenApiInfo; 15 | servers?: OpenApiServer[]; 16 | paths: OpenApiPaths; 17 | components?: OpenApiComponents; 18 | security?: OpenApiSecurityRequirement[]; 19 | tags?: OpenApiTag[]; 20 | externalDocs?: OpenApiExternalDocs; 21 | } 22 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiCallback.d.ts: -------------------------------------------------------------------------------- 1 | import type { OpenApiPath } from './OpenApiPath'; 2 | import type { OpenApiReference } from './OpenApiReference'; 3 | 4 | /** 5 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#callbackObject 6 | */ 7 | export interface OpenApiCallback extends OpenApiReference { 8 | [key: string]: OpenApiPath; 9 | } 10 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiComponents.d.ts: -------------------------------------------------------------------------------- 1 | import type { Dictionary } from '../../../utils/types'; 2 | import type { OpenApiCallback } from './OpenApiCallback'; 3 | import type { OpenApiExample } from './OpenApiExample'; 4 | import type { OpenApiHeader } from './OpenApiHeader'; 5 | import type { OpenApiLink } from './OpenApiLink'; 6 | import type { OpenApiParameter } from './OpenApiParameter'; 7 | import type { OpenApiRequestBody } from './OpenApiRequestBody'; 8 | import type { OpenApiResponses } from './OpenApiResponses'; 9 | import type { OpenApiSchema } from './OpenApiSchema'; 10 | import type { OpenApiSecurityScheme } from './OpenApiSecurityScheme'; 11 | 12 | /** 13 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#componentsObject 14 | */ 15 | export interface OpenApiComponents { 16 | schemas?: Dictionary; 17 | responses?: Dictionary; 18 | parameters?: Dictionary; 19 | examples?: Dictionary; 20 | requestBodies?: Dictionary; 21 | headers?: Dictionary; 22 | securitySchemes?: Dictionary; 23 | links?: Dictionary; 24 | callbacks?: Dictionary; 25 | } 26 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiContact.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#contactObject 3 | */ 4 | export interface OpenApiContact { 5 | name?: string; 6 | url?: string; 7 | email?: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiDiscriminator.d.ts: -------------------------------------------------------------------------------- 1 | import type { Dictionary } from '../../../utils/types'; 2 | 3 | /** 4 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#discriminatorObject 5 | */ 6 | export interface OpenApiDiscriminator { 7 | propertyName: string; 8 | mapping?: Dictionary; 9 | } 10 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiEncoding.d.ts: -------------------------------------------------------------------------------- 1 | import type { Dictionary } from '../../../utils/types'; 2 | import type { OpenApiHeader } from './OpenApiHeader'; 3 | 4 | /** 5 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#encodingObject 6 | */ 7 | export interface OpenApiEncoding { 8 | contentType?: string; 9 | headers?: Dictionary; 10 | style?: string; 11 | explode?: boolean; 12 | allowReserved?: boolean; 13 | } 14 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiExample.d.ts: -------------------------------------------------------------------------------- 1 | import type { OpenApiReference } from './OpenApiReference'; 2 | 3 | /** 4 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#exampleObject 5 | */ 6 | export interface OpenApiExample extends OpenApiReference { 7 | summary?: string; 8 | description?: string; 9 | value?: any; 10 | externalValue?: string; 11 | } 12 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiExternalDocs.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#externalDocumentationObject 3 | */ 4 | export interface OpenApiExternalDocs { 5 | description?: string; 6 | url: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiHeader.d.ts: -------------------------------------------------------------------------------- 1 | import type { Dictionary } from '../../../utils/types'; 2 | import type { OpenApiExample } from './OpenApiExample'; 3 | import type { OpenApiReference } from './OpenApiReference'; 4 | import type { OpenApiSchema } from './OpenApiSchema'; 5 | 6 | /** 7 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#headerObject 8 | */ 9 | export interface OpenApiHeader extends OpenApiReference { 10 | description?: string; 11 | required?: boolean; 12 | deprecated?: boolean; 13 | allowEmptyValue?: boolean; 14 | style?: string; 15 | explode?: boolean; 16 | allowReserved?: boolean; 17 | schema?: OpenApiSchema; 18 | example?: any; 19 | examples?: Dictionary; 20 | } 21 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiInfo.d.ts: -------------------------------------------------------------------------------- 1 | import type { OpenApiContact } from './OpenApiContact'; 2 | import type { OpenApiLicense } from './OpenApiLicense'; 3 | 4 | /** 5 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#infoObject 6 | */ 7 | export interface OpenApiInfo { 8 | title: string; 9 | description?: string; 10 | termsOfService?: string; 11 | contact?: OpenApiContact; 12 | license?: OpenApiLicense; 13 | version: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiLicense.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#licenseObject 3 | */ 4 | export interface OpenApiLicense { 5 | name: string; 6 | url?: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiLink.d.ts: -------------------------------------------------------------------------------- 1 | import type { Dictionary } from '../../../utils/types'; 2 | import type { OpenApiReference } from './OpenApiReference'; 3 | import type { OpenApiServer } from './OpenApiServer'; 4 | 5 | /** 6 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#linkObject 7 | */ 8 | export interface OpenApiLink extends OpenApiReference { 9 | operationRef?: string; 10 | operationId?: string; 11 | parameters?: Dictionary; 12 | requestBody?: any; 13 | description?: string; 14 | server?: OpenApiServer; 15 | } 16 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiMediaType.d.ts: -------------------------------------------------------------------------------- 1 | import type { Dictionary } from '../../../utils/types'; 2 | import type { OpenApiEncoding } from './OpenApiEncoding'; 3 | import type { OpenApiExample } from './OpenApiExample'; 4 | import type { OpenApiReference } from './OpenApiReference'; 5 | import type { OpenApiSchema } from './OpenApiSchema'; 6 | 7 | /** 8 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#mediaTypeObject 9 | */ 10 | export interface OpenApiMediaType extends OpenApiReference { 11 | schema?: OpenApiSchema; 12 | example?: any; 13 | examples?: Dictionary; 14 | encoding?: Dictionary; 15 | } 16 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiOAuthFlow.d.ts: -------------------------------------------------------------------------------- 1 | import type { Dictionary } from '../../../utils/types'; 2 | 3 | /** 4 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#oauthFlowObject 5 | */ 6 | export interface OpenApiOAuthFlow { 7 | authorizationUrl: string; 8 | tokenUrl: string; 9 | refreshUrl?: string; 10 | scopes: Dictionary; 11 | } 12 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiOAuthFlows.d.ts: -------------------------------------------------------------------------------- 1 | import type { OpenApiOAuthFlow } from './OpenApiOAuthFlow'; 2 | 3 | /** 4 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#oauthFlowsObject 5 | */ 6 | export interface OpenApiOAuthFlows { 7 | implicit?: OpenApiOAuthFlow; 8 | password?: OpenApiOAuthFlow; 9 | clientCredentials?: OpenApiOAuthFlow; 10 | authorizationCode?: OpenApiOAuthFlow; 11 | } 12 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiOperation.d.ts: -------------------------------------------------------------------------------- 1 | import type { Dictionary } from '../../../utils/types'; 2 | import type { OpenApiCallback } from './OpenApiCallback'; 3 | import type { OpenApiExternalDocs } from './OpenApiExternalDocs'; 4 | import type { OpenApiParameter } from './OpenApiParameter'; 5 | import type { OpenApiRequestBody } from './OpenApiRequestBody'; 6 | import type { OpenApiResponses } from './OpenApiResponses'; 7 | import type { OpenApiSecurityRequirement } from './OpenApiSecurityRequirement'; 8 | import type { OpenApiServer } from './OpenApiServer'; 9 | 10 | /** 11 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#operationObject 12 | */ 13 | export interface OpenApiOperation { 14 | tags?: string[]; 15 | summary?: string; 16 | description?: string; 17 | externalDocs?: OpenApiExternalDocs; 18 | operationId?: string; 19 | parameters?: OpenApiParameter[]; 20 | requestBody?: OpenApiRequestBody; 21 | responses: OpenApiResponses; 22 | callbacks?: Dictionary; 23 | deprecated?: boolean; 24 | security?: OpenApiSecurityRequirement[]; 25 | servers?: OpenApiServer[]; 26 | } 27 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiParameter.d.ts: -------------------------------------------------------------------------------- 1 | import type { Dictionary } from '../../../utils/types'; 2 | import type { OpenApiExample } from './OpenApiExample'; 3 | import type { OpenApiReference } from './OpenApiReference'; 4 | import type { OpenApiSchema } from './OpenApiSchema'; 5 | 6 | /** 7 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#parameterObject 8 | */ 9 | export interface OpenApiParameter extends OpenApiReference { 10 | name: string; 11 | in: 'path' | 'query' | 'header' | 'formData' | 'cookie'; 12 | description?: string; 13 | required?: boolean; 14 | nullable?: boolean; 15 | deprecated?: boolean; 16 | allowEmptyValue?: boolean; 17 | style?: string; 18 | explode?: boolean; 19 | allowReserved?: boolean; 20 | schema?: OpenApiSchema; 21 | example?: any; 22 | examples?: Dictionary; 23 | } 24 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiPath.d.ts: -------------------------------------------------------------------------------- 1 | import type { OpenApiOperation } from './OpenApiOperation'; 2 | import type { OpenApiParameter } from './OpenApiParameter'; 3 | import type { OpenApiServer } from './OpenApiServer'; 4 | 5 | /** 6 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#pathItemObject 7 | */ 8 | export interface OpenApiPath { 9 | summary?: string; 10 | description?: string; 11 | get?: OpenApiOperation; 12 | put?: OpenApiOperation; 13 | post?: OpenApiOperation; 14 | delete?: OpenApiOperation; 15 | options?: OpenApiOperation; 16 | head?: OpenApiOperation; 17 | patch?: OpenApiOperation; 18 | trace?: OpenApiOperation; 19 | servers?: OpenApiServer[]; 20 | parameters?: OpenApiParameter[]; 21 | } 22 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiPaths.d.ts: -------------------------------------------------------------------------------- 1 | import type { OpenApiPath } from './OpenApiPath'; 2 | 3 | /** 4 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#pathsObject 5 | */ 6 | export interface OpenApiPaths { 7 | [path: string]: OpenApiPath; 8 | } 9 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiReference.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#referenceObject 3 | */ 4 | export interface OpenApiReference { 5 | $ref?: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiRequestBody.d.ts: -------------------------------------------------------------------------------- 1 | import type { Dictionary } from '../../../utils/types'; 2 | import type { OpenApiMediaType } from './OpenApiMediaType'; 3 | import type { OpenApiReference } from './OpenApiReference'; 4 | 5 | /** 6 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#requestBodyObject 7 | */ 8 | export interface OpenApiRequestBody extends OpenApiReference { 9 | description?: string; 10 | content: Dictionary; 11 | required?: boolean; 12 | nullable?: boolean; 13 | } 14 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiResponse.d.ts: -------------------------------------------------------------------------------- 1 | import type { Dictionary } from '../../../utils/types'; 2 | import type { OpenApiHeader } from './OpenApiHeader'; 3 | import type { OpenApiLink } from './OpenApiLink'; 4 | import type { OpenApiMediaType } from './OpenApiMediaType'; 5 | import type { OpenApiReference } from './OpenApiReference'; 6 | 7 | /** 8 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#responseObject 9 | */ 10 | export interface OpenApiResponse extends OpenApiReference { 11 | description: string; 12 | headers?: Dictionary; 13 | content?: Dictionary; 14 | links?: Dictionary; 15 | } 16 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiResponses.d.ts: -------------------------------------------------------------------------------- 1 | import type { OpenApiReference } from './OpenApiReference'; 2 | import type { OpenApiResponse } from './OpenApiResponse'; 3 | 4 | /** 5 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#responsesObject 6 | */ 7 | export interface OpenApiResponses extends OpenApiReference { 8 | default: OpenApiResponse; 9 | 10 | [httpcode: string]: OpenApiResponse; 11 | } 12 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiSecurityRequirement.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#securityRequirementObject 3 | */ 4 | export interface OpenApiSecurityRequirement { 5 | [name: string]: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiSecurityScheme.d.ts: -------------------------------------------------------------------------------- 1 | import type { OpenApiOAuthFlows } from './OpenApiOAuthFlows'; 2 | import type { OpenApiReference } from './OpenApiReference'; 3 | 4 | /** 5 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#securitySchemeObject 6 | */ 7 | export interface OpenApiSecurityScheme extends OpenApiReference { 8 | type: 'apiKey' | 'http' | 'oauth2' | 'openIdConnect'; 9 | description?: string; 10 | name?: string; 11 | in?: 'query' | 'header' | 'cookie'; 12 | scheme?: string; 13 | bearerFormat?: string; 14 | flows?: OpenApiOAuthFlows; 15 | openIdConnectUrl?: string; 16 | } 17 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiServer.d.ts: -------------------------------------------------------------------------------- 1 | import type { Dictionary } from '../../../utils/types'; 2 | import type { OpenApiServerVariable } from './OpenApiServerVariable'; 3 | 4 | /** 5 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#serverObject 6 | */ 7 | export interface OpenApiServer { 8 | url: string; 9 | description?: string; 10 | variables?: Dictionary; 11 | } 12 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiServerVariable.d.ts: -------------------------------------------------------------------------------- 1 | import type { WithEnumExtension } from './Extensions/WithEnumExtension'; 2 | 3 | /** 4 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#serverVariableObject 5 | */ 6 | export interface OpenApiServerVariable extends WithEnumExtension { 7 | enum?: (string | number)[]; 8 | default: string; 9 | description?: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiTag.d.ts: -------------------------------------------------------------------------------- 1 | import type { OpenApiExternalDocs } from './OpenApiExternalDocs'; 2 | 3 | /** 4 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#tagObject 5 | */ 6 | export interface OpenApiTag { 7 | name: string; 8 | description?: string; 9 | externalDocs?: OpenApiExternalDocs; 10 | } 11 | -------------------------------------------------------------------------------- /src/openApi/v3/interfaces/OpenApiXml.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#xmlObject 3 | */ 4 | export interface OpenApiXml { 5 | name?: string; 6 | namespace?: string; 7 | prefix?: string; 8 | attribute?: boolean; 9 | wrapped?: boolean; 10 | } 11 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/escapeName.spec.ts: -------------------------------------------------------------------------------- 1 | import { escapeName } from './escapeName'; 2 | 3 | describe('escapeName', () => { 4 | it('should escape', () => { 5 | expect(escapeName('')).toEqual("''"); 6 | expect(escapeName('fooBar')).toEqual('fooBar'); 7 | expect(escapeName('Foo Bar')).toEqual(`'Foo Bar'`); 8 | expect(escapeName('foo bar')).toEqual(`'foo bar'`); 9 | expect(escapeName('foo-bar')).toEqual(`'foo-bar'`); 10 | expect(escapeName('foo.bar')).toEqual(`'foo.bar'`); 11 | expect(escapeName('foo_bar')).toEqual('foo_bar'); 12 | expect(escapeName('123foo.bar')).toEqual(`'123foo.bar'`); 13 | expect(escapeName('@foo.bar')).toEqual(`'@foo.bar'`); 14 | expect(escapeName('$foo.bar')).toEqual(`'$foo.bar'`); 15 | expect(escapeName('_foo.bar')).toEqual(`'_foo.bar'`); 16 | expect(escapeName('123foobar')).toEqual(`'123foobar'`); 17 | expect(escapeName('@foobar')).toEqual(`'@foobar'`); 18 | expect(escapeName('$foobar')).toEqual('$foobar'); 19 | expect(escapeName('_foobar')).toEqual('_foobar'); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/escapeName.ts: -------------------------------------------------------------------------------- 1 | export const escapeName = (value: string): string => { 2 | if (value || value === '') { 3 | const validName = /^[a-zA-Z_$][\w$]+$/g.test(value); 4 | if (!validName) { 5 | return `'${value}'`; 6 | } 7 | } 8 | return value; 9 | }; 10 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/extendEnum.ts: -------------------------------------------------------------------------------- 1 | import type { Enum } from '../../../client/interfaces/Enum'; 2 | import { isString } from '../../../utils/isString'; 3 | import type { WithEnumExtension } from '../interfaces/Extensions/WithEnumExtension'; 4 | 5 | /** 6 | * Extend the enum with the x-enum properties. This adds the capability 7 | * to use names and descriptions inside the generated enums. 8 | * @param enumerators 9 | * @param definition 10 | */ 11 | export const extendEnum = (enumerators: Enum[], definition: WithEnumExtension): Enum[] => { 12 | const names = definition['x-enum-varnames']?.filter(isString); 13 | const descriptions = definition['x-enum-descriptions']?.filter(isString); 14 | 15 | return enumerators.map((enumerator, index) => ({ 16 | name: names?.[index] || enumerator.name, 17 | description: descriptions?.[index] || enumerator.description, 18 | value: enumerator.value, 19 | type: enumerator.type, 20 | })); 21 | }; 22 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getContent.ts: -------------------------------------------------------------------------------- 1 | import { isDefined } from '../../../utils/isDefined'; 2 | import type { Dictionary } from '../../../utils/types'; 3 | import type { OpenApi } from '../interfaces/OpenApi'; 4 | import type { OpenApiMediaType } from '../interfaces/OpenApiMediaType'; 5 | import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; 6 | 7 | export interface Content { 8 | mediaType: string; 9 | schema: OpenApiSchema; 10 | } 11 | 12 | const BASIC_MEDIA_TYPES = [ 13 | 'application/json-patch+json', 14 | 'application/json', 15 | 'application/x-www-form-urlencoded', 16 | 'text/json', 17 | 'text/plain', 18 | 'multipart/form-data', 19 | 'multipart/mixed', 20 | 'multipart/related', 21 | 'multipart/batch', 22 | ]; 23 | 24 | export const getContent = (openApi: OpenApi, content: Dictionary): Content | null => { 25 | const basicMediaTypeWithSchema = Object.keys(content) 26 | .filter(mediaType => { 27 | const cleanMediaType = mediaType.split(';')[0].trim(); 28 | return BASIC_MEDIA_TYPES.includes(cleanMediaType); 29 | }) 30 | .find(mediaType => isDefined(content[mediaType]?.schema)); 31 | if (basicMediaTypeWithSchema) { 32 | return { 33 | mediaType: basicMediaTypeWithSchema, 34 | schema: content[basicMediaTypeWithSchema].schema as OpenApiSchema, 35 | }; 36 | } 37 | 38 | const firstMediaTypeWithSchema = Object.keys(content).find(mediaType => isDefined(content[mediaType]?.schema)); 39 | if (firstMediaTypeWithSchema) { 40 | return { 41 | mediaType: firstMediaTypeWithSchema, 42 | schema: content[firstMediaTypeWithSchema].schema as OpenApiSchema, 43 | }; 44 | } 45 | return null; 46 | }; 47 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getEnum.ts: -------------------------------------------------------------------------------- 1 | import type { Enum } from '../../../client/interfaces/Enum'; 2 | 3 | export const getEnum = (values?: (string | number)[]): Enum[] => { 4 | if (Array.isArray(values)) { 5 | return values 6 | .filter((value, index, arr) => { 7 | return arr.indexOf(value) === index; 8 | }) 9 | .filter((value: any) => { 10 | return typeof value === 'number' || typeof value === 'string'; 11 | }) 12 | .map(value => { 13 | if (typeof value === 'number') { 14 | return { 15 | name: `'_${value}'`, 16 | value: String(value), 17 | type: 'number', 18 | description: null, 19 | }; 20 | } 21 | return { 22 | name: String(value) 23 | .replace(/\W+/g, '_') 24 | .replace(/^(\d+)/g, '_$1') 25 | .replace(/([a-z])([A-Z]+)/g, '$1_$2') 26 | .toUpperCase(), 27 | value: `'${value.replace(/'/g, "\\'")}'`, 28 | type: 'string', 29 | description: null, 30 | }; 31 | }); 32 | } 33 | return []; 34 | }; 35 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getMappedType.spec.ts: -------------------------------------------------------------------------------- 1 | import { getMappedType } from './getMappedType'; 2 | 3 | describe('getMappedType', () => { 4 | it('should map types to the basics', () => { 5 | expect(getMappedType('file')).toEqual('binary'); 6 | expect(getMappedType('string')).toEqual('string'); 7 | expect(getMappedType('date')).toEqual('string'); 8 | expect(getMappedType('date-time')).toEqual('string'); 9 | expect(getMappedType('float')).toEqual('number'); 10 | expect(getMappedType('double')).toEqual('number'); 11 | expect(getMappedType('short')).toEqual('number'); 12 | expect(getMappedType('int')).toEqual('number'); 13 | expect(getMappedType('boolean')).toEqual('boolean'); 14 | expect(getMappedType('any')).toEqual('any'); 15 | expect(getMappedType('object')).toEqual('any'); 16 | expect(getMappedType('void')).toEqual('void'); 17 | expect(getMappedType('null')).toEqual('null'); 18 | expect(getMappedType('unknown')).toEqual(undefined); 19 | expect(getMappedType('')).toEqual(undefined); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getMappedType.ts: -------------------------------------------------------------------------------- 1 | const TYPE_MAPPINGS = new Map([ 2 | ['file', 'binary'], 3 | ['any', 'any'], 4 | ['object', 'any'], 5 | ['array', 'any[]'], 6 | ['boolean', 'boolean'], 7 | ['byte', 'number'], 8 | ['int', 'number'], 9 | ['integer', 'number'], 10 | ['float', 'number'], 11 | ['double', 'number'], 12 | ['short', 'number'], 13 | ['long', 'number'], 14 | ['number', 'number'], 15 | ['char', 'string'], 16 | ['date', 'string'], 17 | ['date-time', 'string'], 18 | ['password', 'string'], 19 | ['string', 'string'], 20 | ['void', 'void'], 21 | ['null', 'null'], 22 | ]); 23 | 24 | /** 25 | * Get mapped type for given type to any basic Typescript/Javascript type. 26 | */ 27 | export const getMappedType = (type: string, format?: string): string | undefined => { 28 | if (format === 'binary') { 29 | return 'binary'; 30 | } 31 | return TYPE_MAPPINGS.get(type); 32 | }; 33 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getModelDefault.ts: -------------------------------------------------------------------------------- 1 | import type { Model } from '../../../client/interfaces/Model'; 2 | import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; 3 | 4 | export const getModelDefault = (definition: OpenApiSchema, model?: Model): string | undefined => { 5 | if (definition.default === undefined) { 6 | return undefined; 7 | } 8 | 9 | if (definition.default === null) { 10 | return 'null'; 11 | } 12 | 13 | const type = definition.type || typeof definition.default; 14 | 15 | switch (type) { 16 | case 'int': 17 | case 'integer': 18 | case 'number': 19 | if (model?.export === 'enum' && model.enum?.[definition.default]) { 20 | return model.enum[definition.default].value; 21 | } 22 | return definition.default; 23 | 24 | case 'boolean': 25 | return JSON.stringify(definition.default); 26 | 27 | case 'string': 28 | return `'${definition.default}'`; 29 | 30 | case 'object': 31 | try { 32 | return JSON.stringify(definition.default, null, 4); 33 | } catch (e) { 34 | // Ignore 35 | } 36 | } 37 | 38 | return undefined; 39 | }; 40 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getModelTemplate.spec.ts: -------------------------------------------------------------------------------- 1 | import { getModelTemplate } from './getModelTemplate'; 2 | 3 | describe('getModelTemplate', () => { 4 | it('should return generic for template type', () => { 5 | const template = getModelTemplate({ 6 | type: 'Link', 7 | base: 'Link', 8 | template: 'Model', 9 | imports: ['Model'], 10 | isNullable: false, 11 | }); 12 | expect(template).toEqual(''); 13 | }); 14 | 15 | it('should return empty for primary type', () => { 16 | const template = getModelTemplate({ 17 | type: 'string', 18 | base: 'string', 19 | template: null, 20 | imports: [], 21 | isNullable: false, 22 | }); 23 | expect(template).toEqual(''); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getModelTemplate.ts: -------------------------------------------------------------------------------- 1 | import type { Type } from '../../../client/interfaces/Type'; 2 | 3 | /** 4 | * If our model has a template type, then we want to generalize that! 5 | * In that case we should return "" as our template type. 6 | * @param modelClass The parsed model class type. 7 | * @returns The model template type ( or empty). 8 | */ 9 | export const getModelTemplate = (modelClass: Type): string => { 10 | return modelClass.template ? '' : ''; 11 | }; 12 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getModels.ts: -------------------------------------------------------------------------------- 1 | import type { Model } from '../../../client/interfaces/Model'; 2 | import { reservedWords } from '../../../utils/reservedWords'; 3 | import type { OpenApi } from '../interfaces/OpenApi'; 4 | import { getModel } from './getModel'; 5 | import { getType } from './getType'; 6 | 7 | export const getModels = (openApi: OpenApi): Model[] => { 8 | const models: Model[] = []; 9 | if (openApi.components) { 10 | for (const definitionName in openApi.components.schemas) { 11 | if (openApi.components.schemas.hasOwnProperty(definitionName)) { 12 | const definition = openApi.components.schemas[definitionName]; 13 | const definitionType = getType(definitionName); 14 | const model = getModel(openApi, definition, true, definitionType.base.replace(reservedWords, '_$1')); 15 | models.push(model); 16 | } 17 | } 18 | for (const definitionName in openApi.components.parameters) { 19 | if (openApi.components.parameters.hasOwnProperty(definitionName)) { 20 | const definition = openApi.components.parameters[definitionName]; 21 | const definitionType = getType(definitionName); 22 | const schema = definition.schema; 23 | if (schema) { 24 | const model = getModel(openApi, schema, true, definitionType.base.replace(reservedWords, '_$1')); 25 | model.description = definition.description || null; 26 | model.deprecated = definition.deprecated; 27 | models.push(model); 28 | } 29 | } 30 | } 31 | } 32 | return models; 33 | }; 34 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getOperationErrors.ts: -------------------------------------------------------------------------------- 1 | import type { OperationError } from '../../../client/interfaces/OperationError'; 2 | import type { OperationResponse } from '../../../client/interfaces/OperationResponse'; 3 | 4 | export const getOperationErrors = (operationResponses: OperationResponse[]): OperationError[] => { 5 | return operationResponses 6 | .filter(operationResponse => { 7 | return operationResponse.code >= 300 && operationResponse.description; 8 | }) 9 | .map(response => ({ 10 | code: response.code, 11 | description: response.description!, 12 | })); 13 | }; 14 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getOperationName.ts: -------------------------------------------------------------------------------- 1 | import camelCase from 'camelcase'; 2 | 3 | /** 4 | * Convert the input value to a correct operation (method) classname. 5 | * This will use the operation ID - if available - and otherwise fallback 6 | * on a generated name from the URL 7 | */ 8 | export const getOperationName = (url: string, method: string, operationId?: string): string => { 9 | if (operationId) { 10 | return camelCase( 11 | operationId 12 | .replace(/^[^a-zA-Z]+/g, '') 13 | .replace(/[^\w\-]+/g, '-') 14 | .trim() 15 | ); 16 | } 17 | 18 | const urlWithoutPlaceholders = url 19 | .replace(/[^/]*?{api-version}.*?\//g, '') 20 | .replace(/{(.*?)}/g, '') 21 | .replace(/\//g, '-'); 22 | 23 | return camelCase(`${method}-${urlWithoutPlaceholders}`); 24 | }; 25 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getOperationParameterName.spec.ts: -------------------------------------------------------------------------------- 1 | import { getOperationParameterName } from './getOperationParameterName'; 2 | 3 | describe('getOperationParameterName', () => { 4 | it('should produce correct result', () => { 5 | expect(getOperationParameterName('')).toEqual(''); 6 | expect(getOperationParameterName('foobar')).toEqual('foobar'); 7 | expect(getOperationParameterName('fooBar')).toEqual('fooBar'); 8 | expect(getOperationParameterName('foo_bar')).toEqual('fooBar'); 9 | expect(getOperationParameterName('foo-bar')).toEqual('fooBar'); 10 | expect(getOperationParameterName('foo.bar')).toEqual('fooBar'); 11 | expect(getOperationParameterName('@foo.bar')).toEqual('fooBar'); 12 | expect(getOperationParameterName('$foo.bar')).toEqual('fooBar'); 13 | expect(getOperationParameterName('123.foo.bar')).toEqual('fooBar'); 14 | expect(getOperationParameterName('Foo-Bar')).toEqual('fooBar'); 15 | expect(getOperationParameterName('FOO-BAR')).toEqual('fooBar'); 16 | expect(getOperationParameterName('foo[bar]')).toEqual('fooBar'); 17 | expect(getOperationParameterName('foo.bar[]')).toEqual('fooBarArray'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getOperationParameterName.ts: -------------------------------------------------------------------------------- 1 | import camelCase from 'camelcase'; 2 | 3 | import { reservedWords } from '../../../utils/reservedWords'; 4 | 5 | /** 6 | * Replaces any invalid characters from a parameter name. 7 | * For example: 'filter.someProperty' becomes 'filterSomeProperty'. 8 | */ 9 | export const getOperationParameterName = (value: string): string => { 10 | const clean = value 11 | .replace(/^[^a-zA-Z]+/g, '') 12 | .replace('[]', 'Array') 13 | .replace(/[^\w\-]+/g, '-') 14 | .trim(); 15 | return camelCase(clean).replace(reservedWords, '_$1'); 16 | }; 17 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getOperationResponseCode.spec.ts: -------------------------------------------------------------------------------- 1 | import { getOperationResponseCode } from './getOperationResponseCode'; 2 | 3 | describe('getOperationResponseCode', () => { 4 | it('should produce correct result', () => { 5 | expect(getOperationResponseCode('')).toEqual(null); 6 | expect(getOperationResponseCode('default')).toEqual(200); 7 | expect(getOperationResponseCode('200')).toEqual(200); 8 | expect(getOperationResponseCode('300')).toEqual(300); 9 | expect(getOperationResponseCode('400')).toEqual(400); 10 | expect(getOperationResponseCode('abc')).toEqual(null); 11 | expect(getOperationResponseCode('-100')).toEqual(100); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getOperationResponseCode.ts: -------------------------------------------------------------------------------- 1 | export const getOperationResponseCode = (value: string | 'default'): number | null => { 2 | // You can specify a "default" response, this is treated as HTTP code 200 3 | if (value === 'default') { 4 | return 200; 5 | } 6 | 7 | // Check if we can parse the code and return of successful. 8 | if (/[0-9]+/g.test(value)) { 9 | const code = parseInt(value); 10 | if (Number.isInteger(code)) { 11 | return Math.abs(code); 12 | } 13 | } 14 | 15 | return null; 16 | }; 17 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getOperationResponseHeader.ts: -------------------------------------------------------------------------------- 1 | import type { OperationResponse } from '../../../client/interfaces/OperationResponse'; 2 | 3 | export const getOperationResponseHeader = (operationResponses: OperationResponse[]): string | null => { 4 | const header = operationResponses.find(operationResponses => { 5 | return operationResponses.in === 'header'; 6 | }); 7 | if (header) { 8 | return header.name; 9 | } 10 | return null; 11 | }; 12 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getOperationResponses.ts: -------------------------------------------------------------------------------- 1 | import type { OperationResponse } from '../../../client/interfaces/OperationResponse'; 2 | import type { OpenApi } from '../interfaces/OpenApi'; 3 | import type { OpenApiResponse } from '../interfaces/OpenApiResponse'; 4 | import type { OpenApiResponses } from '../interfaces/OpenApiResponses'; 5 | import { getOperationResponse } from './getOperationResponse'; 6 | import { getOperationResponseCode } from './getOperationResponseCode'; 7 | import { getRef } from './getRef'; 8 | 9 | export const getOperationResponses = (openApi: OpenApi, responses: OpenApiResponses): OperationResponse[] => { 10 | const operationResponses: OperationResponse[] = []; 11 | 12 | // Iterate over each response code and get the 13 | // status code and response message (if any). 14 | for (const code in responses) { 15 | if (responses.hasOwnProperty(code)) { 16 | const responseOrReference = responses[code]; 17 | const response = getRef(openApi, responseOrReference); 18 | const responseCode = getOperationResponseCode(code); 19 | 20 | if (responseCode) { 21 | const operationResponse = getOperationResponse(openApi, response, responseCode); 22 | operationResponses.push(operationResponse); 23 | } 24 | } 25 | } 26 | 27 | // Sort the responses to 2XX success codes come before 4XX and 5XX error codes. 28 | return operationResponses.sort((a, b): number => { 29 | return a.code < b.code ? -1 : a.code > b.code ? 1 : 0; 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getRef.ts: -------------------------------------------------------------------------------- 1 | import type { OpenApi } from '../interfaces/OpenApi'; 2 | import type { OpenApiReference } from '../interfaces/OpenApiReference'; 3 | 4 | const ESCAPED_REF_SLASH = /~1/g; 5 | const ESCAPED_REF_TILDE = /~0/g; 6 | 7 | export const getRef = (openApi: OpenApi, item: T & OpenApiReference): T => { 8 | if (item.$ref) { 9 | // Fetch the paths to the definitions, this converts: 10 | // "#/components/schemas/Form" to ["components", "schemas", "Form"] 11 | const paths = item.$ref 12 | .replace(/^#/g, '') 13 | .split('/') 14 | .filter(item => item); 15 | 16 | // Try to find the reference by walking down the path, 17 | // if we cannot find it, then we throw an error. 18 | let result: any = openApi; 19 | paths.forEach(path => { 20 | const decodedPath = decodeURIComponent( 21 | path.replace(ESCAPED_REF_SLASH, '/').replace(ESCAPED_REF_TILDE, '~') 22 | ); 23 | if (result.hasOwnProperty(decodedPath)) { 24 | result = result[decodedPath]; 25 | } else { 26 | throw new Error(`Could not find reference: "${item.$ref}"`); 27 | } 28 | }); 29 | return result as T; 30 | } 31 | return item as T; 32 | }; 33 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getRequiredPropertiesFromComposition.ts: -------------------------------------------------------------------------------- 1 | import type { Model } from '../../../client/interfaces/Model'; 2 | import type { OpenApi } from '../interfaces/OpenApi'; 3 | import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; 4 | import type { getModel } from './getModel'; 5 | import { getRef } from './getRef'; 6 | 7 | // Fix for circular dependency 8 | export type GetModelFn = typeof getModel; 9 | 10 | export const getRequiredPropertiesFromComposition = ( 11 | openApi: OpenApi, 12 | required: string[], 13 | definitions: OpenApiSchema[], 14 | getModel: GetModelFn 15 | ): Model[] => { 16 | return definitions 17 | .reduce((properties, definition) => { 18 | if (definition.$ref) { 19 | const schema = getRef(openApi, definition); 20 | return [...properties, ...getModel(openApi, schema).properties]; 21 | } 22 | return [...properties, ...getModel(openApi, definition).properties]; 23 | }, [] as Model[]) 24 | .filter(property => { 25 | return !property.isRequired && required.includes(property.name); 26 | }) 27 | .map(property => { 28 | return { 29 | ...property, 30 | isRequired: true, 31 | }; 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getServer.spec.ts: -------------------------------------------------------------------------------- 1 | import { getServer } from './getServer'; 2 | 3 | describe('getServer', () => { 4 | it('should produce correct result', () => { 5 | expect( 6 | getServer({ 7 | openapi: '3.0', 8 | info: { 9 | title: 'dummy', 10 | version: '1.0', 11 | }, 12 | paths: {}, 13 | servers: [ 14 | { 15 | url: 'https://localhost:8080/api', 16 | }, 17 | ], 18 | }) 19 | ).toEqual('https://localhost:8080/api'); 20 | }); 21 | 22 | it('should produce correct result with variables', () => { 23 | expect( 24 | getServer({ 25 | openapi: '3.0', 26 | info: { 27 | title: 'dummy', 28 | version: '1.0', 29 | }, 30 | paths: {}, 31 | servers: [ 32 | { 33 | url: '{scheme}://localhost:{port}/api', 34 | variables: { 35 | scheme: { 36 | default: 'https', 37 | }, 38 | port: { 39 | default: '8080', 40 | }, 41 | }, 42 | }, 43 | ], 44 | }) 45 | ).toEqual('https://localhost:8080/api'); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getServer.ts: -------------------------------------------------------------------------------- 1 | import type { OpenApi } from '../interfaces/OpenApi'; 2 | 3 | export const getServer = (openApi: OpenApi): string => { 4 | const server = openApi.servers?.[0]; 5 | const variables = server?.variables || {}; 6 | let url = server?.url || ''; 7 | for (const variable in variables) { 8 | if (variables.hasOwnProperty(variable)) { 9 | url = url.replace(`{${variable}}`, variables[variable].default); 10 | } 11 | } 12 | return url.replace(/\/$/g, ''); 13 | }; 14 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getServiceName.spec.ts: -------------------------------------------------------------------------------- 1 | import { getServiceName } from './getServiceName'; 2 | 3 | describe('getServiceName', () => { 4 | it('should produce correct result', () => { 5 | expect(getServiceName('')).toEqual(''); 6 | expect(getServiceName('FooBar')).toEqual('FooBar'); 7 | expect(getServiceName('Foo Bar')).toEqual('FooBar'); 8 | expect(getServiceName('foo bar')).toEqual('FooBar'); 9 | expect(getServiceName('@fooBar')).toEqual('FooBar'); 10 | expect(getServiceName('$fooBar')).toEqual('FooBar'); 11 | expect(getServiceName('123fooBar')).toEqual('FooBar'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getServiceName.ts: -------------------------------------------------------------------------------- 1 | import camelCase from 'camelcase'; 2 | 3 | /** 4 | * Convert the input value to a correct service name. This converts 5 | * the input string to PascalCase. 6 | */ 7 | export const getServiceName = (value: string): string => { 8 | const clean = value 9 | .replace(/^[^a-zA-Z]+/g, '') 10 | .replace(/[^\w\-]+/g, '-') 11 | .trim(); 12 | return camelCase(clean, { pascalCase: true }); 13 | }; 14 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getServiceVersion.spec.ts: -------------------------------------------------------------------------------- 1 | import { getServiceVersion } from './getServiceVersion'; 2 | 3 | describe('getServiceVersion', () => { 4 | it('should produce correct result', () => { 5 | expect(getServiceVersion('1.0')).toEqual('1.0'); 6 | expect(getServiceVersion('v1.0')).toEqual('1.0'); 7 | expect(getServiceVersion('V1.0')).toEqual('1.0'); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getServiceVersion.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Convert the service version to 'normal' version. 3 | * This basically removes any "v" prefix from the version string. 4 | * @param version 5 | */ 6 | export const getServiceVersion = (version = '1.0'): string => { 7 | return String(version).replace(/^v/gi, ''); 8 | }; 9 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/getServices.spec.ts: -------------------------------------------------------------------------------- 1 | import { getServices } from './getServices'; 2 | 3 | describe('getServices', () => { 4 | it('should create a unnamed service if tags are empty', () => { 5 | const services = getServices({ 6 | openapi: '3.0.0', 7 | info: { 8 | title: 'x', 9 | version: '1', 10 | }, 11 | paths: { 12 | '/api/trips': { 13 | get: { 14 | tags: [], 15 | responses: { 16 | 200: { 17 | description: 'x', 18 | }, 19 | default: { 20 | description: 'default', 21 | }, 22 | }, 23 | }, 24 | }, 25 | }, 26 | }); 27 | 28 | expect(services).toHaveLength(1); 29 | expect(services[0].name).toEqual('Default'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/sortByRequired.ts: -------------------------------------------------------------------------------- 1 | import type { OperationParameter } from '../../../client/interfaces/OperationParameter'; 2 | 3 | export const sortByRequired = (a: OperationParameter, b: OperationParameter): number => { 4 | const aNeedsValue = a.isRequired && a.default === undefined; 5 | const bNeedsValue = b.isRequired && b.default === undefined; 6 | if (aNeedsValue && !bNeedsValue) return -1; 7 | if (bNeedsValue && !aNeedsValue) return 1; 8 | return 0; 9 | }; 10 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/stripNamespace.spec.ts: -------------------------------------------------------------------------------- 1 | import { stripNamespace } from './stripNamespace'; 2 | 3 | describe('stripNamespace', () => { 4 | it('should strip namespace', () => { 5 | expect(stripNamespace('#/components/schemas/Item')).toEqual('Item'); 6 | expect(stripNamespace('#/components/responses/Item')).toEqual('Item'); 7 | expect(stripNamespace('#/components/parameters/Item')).toEqual('Item'); 8 | expect(stripNamespace('#/components/examples/Item')).toEqual('Item'); 9 | expect(stripNamespace('#/components/requestBodies/Item')).toEqual('Item'); 10 | expect(stripNamespace('#/components/headers/Item')).toEqual('Item'); 11 | expect(stripNamespace('#/components/securitySchemes/Item')).toEqual('Item'); 12 | expect(stripNamespace('#/components/links/Item')).toEqual('Item'); 13 | expect(stripNamespace('#/components/callbacks/Item')).toEqual('Item'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/openApi/v3/parser/stripNamespace.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Strip (OpenAPI) namespaces fom values. 3 | * @param value 4 | */ 5 | export const stripNamespace = (value: string): string => { 6 | return value 7 | .trim() 8 | .replace(/^#\/components\/schemas\//, '') 9 | .replace(/^#\/components\/responses\//, '') 10 | .replace(/^#\/components\/parameters\//, '') 11 | .replace(/^#\/components\/examples\//, '') 12 | .replace(/^#\/components\/requestBodies\//, '') 13 | .replace(/^#\/components\/headers\//, '') 14 | .replace(/^#\/components\/securitySchemes\//, '') 15 | .replace(/^#\/components\/links\//, '') 16 | .replace(/^#\/components\/callbacks\//, ''); 17 | }; 18 | -------------------------------------------------------------------------------- /src/templates/__mocks__/index.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | compiler: [8, '>= 4.3.0'], 3 | useData: true, 4 | main: () => { 5 | return ''; 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /src/templates/core/ApiError.hbs: -------------------------------------------------------------------------------- 1 | {{>header}} 2 | 3 | import type { ApiRequestOptions } from './ApiRequestOptions'; 4 | import type { ApiResult } from './ApiResult'; 5 | 6 | export class ApiError extends Error { 7 | public readonly url: string; 8 | public readonly status: number; 9 | public readonly statusText: string; 10 | public readonly body: any; 11 | public readonly request: ApiRequestOptions; 12 | 13 | constructor(request: ApiRequestOptions, response: ApiResult, message: string) { 14 | super(message); 15 | 16 | this.name = 'ApiError'; 17 | this.url = response.url; 18 | this.status = response.status; 19 | this.statusText = response.statusText; 20 | this.body = response.body; 21 | this.request = request; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/templates/core/ApiRequestOptions.hbs: -------------------------------------------------------------------------------- 1 | {{>header}} 2 | 3 | export type ApiRequestOptions = { 4 | readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH'; 5 | readonly url: string; 6 | readonly path?: Record; 7 | readonly cookies?: Record; 8 | readonly headers?: Record; 9 | readonly query?: Record; 10 | readonly formData?: Record; 11 | readonly body?: any; 12 | readonly mediaType?: string; 13 | readonly responseHeader?: string; 14 | readonly errors?: Record; 15 | }; 16 | -------------------------------------------------------------------------------- /src/templates/core/ApiResult.hbs: -------------------------------------------------------------------------------- 1 | {{>header}} 2 | 3 | export type ApiResult = { 4 | readonly url: string; 5 | readonly ok: boolean; 6 | readonly status: number; 7 | readonly statusText: string; 8 | readonly body: any; 9 | }; 10 | -------------------------------------------------------------------------------- /src/templates/core/BaseHttpRequest.hbs: -------------------------------------------------------------------------------- 1 | {{>header}} 2 | 3 | {{#equals @root.httpClient 'angular'}} 4 | import type { HttpClient } from '@angular/common/http'; 5 | import type { Observable } from 'rxjs'; 6 | 7 | import type { ApiRequestOptions } from './ApiRequestOptions'; 8 | import type { OpenAPIConfig } from './OpenAPI'; 9 | {{else}} 10 | import type { ApiRequestOptions } from './ApiRequestOptions'; 11 | import type { CancelablePromise } from './CancelablePromise'; 12 | import type { OpenAPIConfig } from './OpenAPI'; 13 | {{/equals}} 14 | 15 | export abstract class BaseHttpRequest { 16 | 17 | {{#equals @root.httpClient 'angular'}} 18 | constructor( 19 | public readonly config: OpenAPIConfig, 20 | public readonly http: HttpClient, 21 | ) {} 22 | {{else}} 23 | constructor(public readonly config: OpenAPIConfig) {} 24 | {{/equals}} 25 | 26 | {{#equals @root.httpClient 'angular'}} 27 | public abstract request(options: ApiRequestOptions): Observable; 28 | {{else}} 29 | public abstract request(options: ApiRequestOptions): CancelablePromise; 30 | {{/equals}} 31 | } 32 | -------------------------------------------------------------------------------- /src/templates/core/OpenAPI.hbs: -------------------------------------------------------------------------------- 1 | {{>header}} 2 | 3 | import type { ApiRequestOptions } from './ApiRequestOptions'; 4 | 5 | type Resolver = (options: ApiRequestOptions) => Promise; 6 | type Headers = Record; 7 | 8 | export type OpenAPIConfig = { 9 | BASE: string; 10 | VERSION: string; 11 | WITH_CREDENTIALS: boolean; 12 | CREDENTIALS: 'include' | 'omit' | 'same-origin'; 13 | TOKEN?: string | Resolver | undefined; 14 | USERNAME?: string | Resolver | undefined; 15 | PASSWORD?: string | Resolver | undefined; 16 | HEADERS?: Headers | Resolver | undefined; 17 | ENCODE_PATH?: ((path: string) => string) | undefined; 18 | }; 19 | 20 | export const OpenAPI: OpenAPIConfig = { 21 | BASE: '{{{server}}}', 22 | VERSION: '{{{version}}}', 23 | WITH_CREDENTIALS: false, 24 | CREDENTIALS: 'include', 25 | TOKEN: undefined, 26 | USERNAME: undefined, 27 | PASSWORD: undefined, 28 | HEADERS: undefined, 29 | ENCODE_PATH: undefined, 30 | }; 31 | -------------------------------------------------------------------------------- /src/templates/core/angular/getHeaders.hbs: -------------------------------------------------------------------------------- 1 | export const getHeaders = (config: OpenAPIConfig, options: ApiRequestOptions): Observable => { 2 | return forkJoin({ 3 | token: resolve(options, config.TOKEN), 4 | username: resolve(options, config.USERNAME), 5 | password: resolve(options, config.PASSWORD), 6 | additionalHeaders: resolve(options, config.HEADERS), 7 | }).pipe( 8 | map(({ token, username, password, additionalHeaders }) => { 9 | const headers = Object.entries({ 10 | Accept: 'application/json', 11 | ...additionalHeaders, 12 | ...options.headers, 13 | }) 14 | .filter(([_, value]) => isDefined(value)) 15 | .reduce((headers, [key, value]) => ({ 16 | ...headers, 17 | [key]: String(value), 18 | }), {} as Record); 19 | 20 | if (isStringWithValue(token)) { 21 | headers['Authorization'] = `Bearer ${token}`; 22 | } 23 | 24 | if (isStringWithValue(username) && isStringWithValue(password)) { 25 | const credentials = base64(`${username}:${password}`); 26 | headers['Authorization'] = `Basic ${credentials}`; 27 | } 28 | 29 | if (options.body !== undefined) { 30 | if (options.mediaType) { 31 | headers['Content-Type'] = options.mediaType; 32 | } else if (isBlob(options.body)) { 33 | headers['Content-Type'] = options.body.type || 'application/octet-stream'; 34 | } else if (isString(options.body)) { 35 | headers['Content-Type'] = 'text/plain'; 36 | } else if (!isFormData(options.body)) { 37 | headers['Content-Type'] = 'application/json'; 38 | } 39 | } 40 | 41 | return new HttpHeaders(headers); 42 | }), 43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /src/templates/core/angular/getRequestBody.hbs: -------------------------------------------------------------------------------- 1 | export const getRequestBody = (options: ApiRequestOptions): any => { 2 | if (options.body) { 3 | if (options.mediaType?.includes('/json')) { 4 | return JSON.stringify(options.body) 5 | } else if (isString(options.body) || isBlob(options.body) || isFormData(options.body)) { 6 | return options.body; 7 | } else { 8 | return JSON.stringify(options.body); 9 | } 10 | } 11 | return undefined; 12 | }; 13 | -------------------------------------------------------------------------------- /src/templates/core/angular/getResponseBody.hbs: -------------------------------------------------------------------------------- 1 | export const getResponseBody = (response: HttpResponse): T | undefined => { 2 | if (response.status !== 204 && response.body !== null) { 3 | return response.body; 4 | } 5 | return undefined; 6 | }; 7 | -------------------------------------------------------------------------------- /src/templates/core/angular/getResponseHeader.hbs: -------------------------------------------------------------------------------- 1 | export const getResponseHeader = (response: HttpResponse, responseHeader?: string): string | undefined => { 2 | if (responseHeader) { 3 | const value = response.headers.get(responseHeader); 4 | if (isString(value)) { 5 | return value; 6 | } 7 | } 8 | return undefined; 9 | }; 10 | -------------------------------------------------------------------------------- /src/templates/core/angular/sendRequest.hbs: -------------------------------------------------------------------------------- 1 | export const sendRequest = ( 2 | config: OpenAPIConfig, 3 | options: ApiRequestOptions, 4 | http: HttpClient, 5 | url: string, 6 | body: any, 7 | formData: FormData | undefined, 8 | headers: HttpHeaders 9 | ): Observable> => { 10 | return http.request(options.method, url, { 11 | headers, 12 | body: body ?? formData, 13 | withCredentials: config.WITH_CREDENTIALS, 14 | observe: 'response', 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /src/templates/core/axios/getHeaders.hbs: -------------------------------------------------------------------------------- 1 | export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions, formData?: FormData): Promise> => { 2 | const [token, username, password, additionalHeaders] = await Promise.all([ 3 | resolve(options, config.TOKEN), 4 | resolve(options, config.USERNAME), 5 | resolve(options, config.PASSWORD), 6 | resolve(options, config.HEADERS), 7 | ]); 8 | 9 | const formHeaders = typeof formData?.getHeaders === 'function' && formData?.getHeaders() || {} 10 | 11 | const headers = Object.entries({ 12 | Accept: 'application/json', 13 | ...additionalHeaders, 14 | ...options.headers, 15 | ...formHeaders, 16 | }) 17 | .filter(([_, value]) => isDefined(value)) 18 | .reduce((headers, [key, value]) => ({ 19 | ...headers, 20 | [key]: String(value), 21 | }), {} as Record); 22 | 23 | if (isStringWithValue(token)) { 24 | headers['Authorization'] = `Bearer ${token}`; 25 | } 26 | 27 | if (isStringWithValue(username) && isStringWithValue(password)) { 28 | const credentials = base64(`${username}:${password}`); 29 | headers['Authorization'] = `Basic ${credentials}`; 30 | } 31 | 32 | if (options.body !== undefined) { 33 | if (options.mediaType) { 34 | headers['Content-Type'] = options.mediaType; 35 | } else if (isBlob(options.body)) { 36 | headers['Content-Type'] = options.body.type || 'application/octet-stream'; 37 | } else if (isString(options.body)) { 38 | headers['Content-Type'] = 'text/plain'; 39 | } else if (!isFormData(options.body)) { 40 | headers['Content-Type'] = 'application/json'; 41 | } 42 | } 43 | 44 | return headers; 45 | }; 46 | -------------------------------------------------------------------------------- /src/templates/core/axios/getRequestBody.hbs: -------------------------------------------------------------------------------- 1 | export const getRequestBody = (options: ApiRequestOptions): any => { 2 | if (options.body) { 3 | return options.body; 4 | } 5 | return undefined; 6 | }; 7 | -------------------------------------------------------------------------------- /src/templates/core/axios/getResponseBody.hbs: -------------------------------------------------------------------------------- 1 | export const getResponseBody = (response: AxiosResponse): any => { 2 | if (response.status !== 204) { 3 | return response.data; 4 | } 5 | return undefined; 6 | }; 7 | -------------------------------------------------------------------------------- /src/templates/core/axios/getResponseHeader.hbs: -------------------------------------------------------------------------------- 1 | export const getResponseHeader = (response: AxiosResponse, responseHeader?: string): string | undefined => { 2 | if (responseHeader) { 3 | const content = response.headers[responseHeader]; 4 | if (isString(content)) { 5 | return content; 6 | } 7 | } 8 | return undefined; 9 | }; 10 | -------------------------------------------------------------------------------- /src/templates/core/axios/sendRequest.hbs: -------------------------------------------------------------------------------- 1 | export const sendRequest = async ( 2 | config: OpenAPIConfig, 3 | options: ApiRequestOptions, 4 | url: string, 5 | body: any, 6 | formData: FormData | undefined, 7 | headers: Record, 8 | onCancel: OnCancel, 9 | axiosClient: AxiosInstance 10 | ): Promise> => { 11 | const source = axios.CancelToken.source(); 12 | 13 | const requestConfig: AxiosRequestConfig = { 14 | url, 15 | headers, 16 | data: body ?? formData, 17 | method: options.method, 18 | withCredentials: config.WITH_CREDENTIALS, 19 | withXSRFToken: config.CREDENTIALS === 'include' ? config.WITH_CREDENTIALS : false, 20 | cancelToken: source.token, 21 | }; 22 | 23 | onCancel(() => source.cancel('The user aborted a request.')); 24 | 25 | try { 26 | return await axiosClient.request(requestConfig); 27 | } catch (error) { 28 | const axiosError = error as AxiosError; 29 | if (axiosError.response) { 30 | return axiosError.response; 31 | } 32 | throw error; 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/templates/core/fetch/getHeaders.hbs: -------------------------------------------------------------------------------- 1 | export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions): Promise => { 2 | const [token, username, password, additionalHeaders] = await Promise.all([ 3 | resolve(options, config.TOKEN), 4 | resolve(options, config.USERNAME), 5 | resolve(options, config.PASSWORD), 6 | resolve(options, config.HEADERS), 7 | ]); 8 | 9 | const headers = Object.entries({ 10 | Accept: 'application/json', 11 | ...additionalHeaders, 12 | ...options.headers, 13 | }) 14 | .filter(([_, value]) => isDefined(value)) 15 | .reduce((headers, [key, value]) => ({ 16 | ...headers, 17 | [key]: String(value), 18 | }), {} as Record); 19 | 20 | if (isStringWithValue(token)) { 21 | headers['Authorization'] = `Bearer ${token}`; 22 | } 23 | 24 | if (isStringWithValue(username) && isStringWithValue(password)) { 25 | const credentials = base64(`${username}:${password}`); 26 | headers['Authorization'] = `Basic ${credentials}`; 27 | } 28 | 29 | if (options.body !== undefined) { 30 | if (options.mediaType) { 31 | headers['Content-Type'] = options.mediaType; 32 | } else if (isBlob(options.body)) { 33 | headers['Content-Type'] = options.body.type || 'application/octet-stream'; 34 | } else if (isString(options.body)) { 35 | headers['Content-Type'] = 'text/plain'; 36 | } else if (!isFormData(options.body)) { 37 | headers['Content-Type'] = 'application/json'; 38 | } 39 | } 40 | 41 | return new Headers(headers); 42 | }; 43 | -------------------------------------------------------------------------------- /src/templates/core/fetch/getRequestBody.hbs: -------------------------------------------------------------------------------- 1 | export const getRequestBody = (options: ApiRequestOptions): any => { 2 | if (options.body !== undefined) { 3 | if (options.mediaType?.includes('/json')) { 4 | return JSON.stringify(options.body) 5 | } else if (isString(options.body) || isBlob(options.body) || isFormData(options.body)) { 6 | return options.body; 7 | } else { 8 | return JSON.stringify(options.body); 9 | } 10 | } 11 | return undefined; 12 | }; 13 | -------------------------------------------------------------------------------- /src/templates/core/fetch/getResponseBody.hbs: -------------------------------------------------------------------------------- 1 | export const getResponseBody = async (response: Response): Promise => { 2 | if (response.status !== 204) { 3 | try { 4 | const contentType = response.headers.get('Content-Type'); 5 | if (contentType) { 6 | const jsonTypes = ['application/json', 'application/problem+json'] 7 | const isJSON = jsonTypes.some(type => contentType.toLowerCase().startsWith(type)); 8 | if (isJSON) { 9 | return await response.json(); 10 | } else { 11 | return await response.text(); 12 | } 13 | } 14 | } catch (error) { 15 | console.error(error); 16 | } 17 | } 18 | return undefined; 19 | }; 20 | -------------------------------------------------------------------------------- /src/templates/core/fetch/getResponseHeader.hbs: -------------------------------------------------------------------------------- 1 | export const getResponseHeader = (response: Response, responseHeader?: string): string | undefined => { 2 | if (responseHeader) { 3 | const content = response.headers.get(responseHeader); 4 | if (isString(content)) { 5 | return content; 6 | } 7 | } 8 | return undefined; 9 | }; 10 | -------------------------------------------------------------------------------- /src/templates/core/fetch/sendRequest.hbs: -------------------------------------------------------------------------------- 1 | export const sendRequest = async ( 2 | config: OpenAPIConfig, 3 | options: ApiRequestOptions, 4 | url: string, 5 | body: any, 6 | formData: FormData | undefined, 7 | headers: Headers, 8 | onCancel: OnCancel 9 | ): Promise => { 10 | const controller = new AbortController(); 11 | 12 | const request: RequestInit = { 13 | headers, 14 | body: body ?? formData, 15 | method: options.method, 16 | signal: controller.signal, 17 | }; 18 | 19 | if (config.WITH_CREDENTIALS) { 20 | request.credentials = config.CREDENTIALS; 21 | } 22 | 23 | onCancel(() => controller.abort()); 24 | 25 | return await fetch(url, request); 26 | }; 27 | -------------------------------------------------------------------------------- /src/templates/core/functions/base64.hbs: -------------------------------------------------------------------------------- 1 | export const base64 = (str: string): string => { 2 | try { 3 | return btoa(str); 4 | } catch (err) { 5 | // @ts-ignore 6 | return Buffer.from(str).toString('base64'); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /src/templates/core/functions/catchErrorCodes.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => { 4 | const errors: Record = { 5 | 400: 'Bad Request', 6 | 401: 'Unauthorized', 7 | 403: 'Forbidden', 8 | 404: 'Not Found', 9 | 500: 'Internal Server Error', 10 | 502: 'Bad Gateway', 11 | 503: 'Service Unavailable', 12 | ...options.errors, 13 | } 14 | 15 | const error = errors[result.status]; 16 | if (error) { 17 | throw new ApiError(options, result, error); 18 | } 19 | 20 | if (!result.ok) { 21 | const errorStatus = result.status ?? 'unknown'; 22 | const errorStatusText = result.statusText ?? 'unknown'; 23 | const errorBody = (() => { 24 | try { 25 | return JSON.stringify(result.body, null, 2); 26 | } catch (e) { 27 | return undefined; 28 | } 29 | })(); 30 | 31 | throw new ApiError(options, result, 32 | `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}` 33 | ); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /src/templates/core/functions/getFormData.hbs: -------------------------------------------------------------------------------- 1 | export const getFormData = (options: ApiRequestOptions): FormData | undefined => { 2 | if (options.formData) { 3 | const formData = new FormData(); 4 | 5 | const process = (key: string, value: any) => { 6 | if (isString(value) || isBlob(value)) { 7 | formData.append(key, value); 8 | } else { 9 | formData.append(key, JSON.stringify(value)); 10 | } 11 | }; 12 | 13 | Object.entries(options.formData) 14 | .filter(([_, value]) => isDefined(value)) 15 | .forEach(([key, value]) => { 16 | if (Array.isArray(value)) { 17 | value.forEach(v => process(key, v)); 18 | } else { 19 | process(key, value); 20 | } 21 | }); 22 | 23 | return formData; 24 | } 25 | return undefined; 26 | }; 27 | -------------------------------------------------------------------------------- /src/templates/core/functions/getQueryString.hbs: -------------------------------------------------------------------------------- 1 | export const getQueryString = (params: Record): string => { 2 | const qs: string[] = []; 3 | 4 | const append = (key: string, value: any) => { 5 | qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); 6 | }; 7 | 8 | const process = (key: string, value: any) => { 9 | if (isDefined(value)) { 10 | if (Array.isArray(value)) { 11 | value.forEach(v => { 12 | process(key, v); 13 | }); 14 | } else if (typeof value === 'object') { 15 | Object.entries(value).forEach(([k, v]) => { 16 | process(`${key}[${k}]`, v); 17 | }); 18 | } else { 19 | append(key, value); 20 | } 21 | } 22 | }; 23 | 24 | Object.entries(params).forEach(([key, value]) => { 25 | process(key, value); 26 | }); 27 | 28 | if (qs.length > 0) { 29 | return `?${qs.join('&')}`; 30 | } 31 | 32 | return ''; 33 | }; 34 | -------------------------------------------------------------------------------- /src/templates/core/functions/getUrl.hbs: -------------------------------------------------------------------------------- 1 | const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { 2 | const encoder = config.ENCODE_PATH || encodeURI; 3 | 4 | const path = options.url 5 | .replace('{api-version}', config.VERSION) 6 | .replace(/{(.*?)}/g, (substring: string, group: string) => { 7 | if (options.path?.hasOwnProperty(group)) { 8 | return encoder(String(options.path[group])); 9 | } 10 | return substring; 11 | }); 12 | 13 | const url = `${config.BASE}${path}`; 14 | if (options.query) { 15 | return `${url}${getQueryString(options.query)}`; 16 | } 17 | return url; 18 | }; 19 | -------------------------------------------------------------------------------- /src/templates/core/functions/isBlob.hbs: -------------------------------------------------------------------------------- 1 | export const isBlob = (value: any): value is Blob => { 2 | return ( 3 | typeof value === 'object' && 4 | typeof value.type === 'string' && 5 | typeof value.stream === 'function' && 6 | typeof value.arrayBuffer === 'function' && 7 | typeof value.constructor === 'function' && 8 | typeof value.constructor.name === 'string' && 9 | /^(Blob|File)$/.test(value.constructor.name) && 10 | /^(Blob|File)$/.test(value[Symbol.toStringTag]) 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /src/templates/core/functions/isDefined.hbs: -------------------------------------------------------------------------------- 1 | export const isDefined = (value: T | null | undefined): value is Exclude => { 2 | return value !== undefined && value !== null; 3 | }; 4 | -------------------------------------------------------------------------------- /src/templates/core/functions/isFormData.hbs: -------------------------------------------------------------------------------- 1 | export const isFormData = (value: any): value is FormData => { 2 | return value instanceof FormData; 3 | }; 4 | -------------------------------------------------------------------------------- /src/templates/core/functions/isString.hbs: -------------------------------------------------------------------------------- 1 | export const isString = (value: any): value is string => { 2 | return typeof value === 'string'; 3 | }; 4 | -------------------------------------------------------------------------------- /src/templates/core/functions/isStringWithValue.hbs: -------------------------------------------------------------------------------- 1 | export const isStringWithValue = (value: any): value is string => { 2 | return isString(value) && value !== ''; 3 | }; 4 | -------------------------------------------------------------------------------- /src/templates/core/functions/isSuccess.hbs: -------------------------------------------------------------------------------- 1 | export const isSuccess = (status: number): boolean => { 2 | return status >= 200 && status < 300; 3 | }; 4 | -------------------------------------------------------------------------------- /src/templates/core/functions/resolve.hbs: -------------------------------------------------------------------------------- 1 | type Resolver = (options: ApiRequestOptions) => Promise; 2 | 3 | export const resolve = async (options: ApiRequestOptions, resolver?: T | Resolver): Promise => { 4 | if (typeof resolver === 'function') { 5 | return (resolver as Resolver)(options); 6 | } 7 | return resolver; 8 | }; 9 | -------------------------------------------------------------------------------- /src/templates/core/node/getHeaders.hbs: -------------------------------------------------------------------------------- 1 | export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions): Promise => { 2 | const [token, username, password, additionalHeaders] = await Promise.all([ 3 | resolve(options, config.TOKEN), 4 | resolve(options, config.USERNAME), 5 | resolve(options, config.PASSWORD), 6 | resolve(options, config.HEADERS), 7 | ]); 8 | 9 | const headers = Object.entries({ 10 | Accept: 'application/json', 11 | ...additionalHeaders, 12 | ...options.headers, 13 | }) 14 | .filter(([_, value]) => isDefined(value)) 15 | .reduce((headers, [key, value]) => ({ 16 | ...headers, 17 | [key]: String(value), 18 | }), {} as Record); 19 | 20 | if (isStringWithValue(token)) { 21 | headers['Authorization'] = `Bearer ${token}`; 22 | } 23 | 24 | if (isStringWithValue(username) && isStringWithValue(password)) { 25 | const credentials = base64(`${username}:${password}`); 26 | headers['Authorization'] = `Basic ${credentials}`; 27 | } 28 | 29 | if (options.body !== undefined) { 30 | if (options.mediaType) { 31 | headers['Content-Type'] = options.mediaType; 32 | } else if (isBlob(options.body)) { 33 | headers['Content-Type'] = 'application/octet-stream'; 34 | } else if (isString(options.body)) { 35 | headers['Content-Type'] = 'text/plain'; 36 | } else if (!isFormData(options.body)) { 37 | headers['Content-Type'] = 'application/json'; 38 | } 39 | } 40 | 41 | return new Headers(headers); 42 | }; 43 | -------------------------------------------------------------------------------- /src/templates/core/node/getRequestBody.hbs: -------------------------------------------------------------------------------- 1 | export const getRequestBody = (options: ApiRequestOptions): any => { 2 | if (options.body !== undefined) { 3 | if (options.mediaType?.includes('/json')) { 4 | return JSON.stringify(options.body) 5 | } else if (isString(options.body) || isBlob(options.body) || isFormData(options.body)) { 6 | return options.body as any; 7 | } else { 8 | return JSON.stringify(options.body); 9 | } 10 | } 11 | return undefined; 12 | }; 13 | -------------------------------------------------------------------------------- /src/templates/core/node/getResponseBody.hbs: -------------------------------------------------------------------------------- 1 | export const getResponseBody = async (response: Response): Promise => { 2 | if (response.status !== 204) { 3 | try { 4 | const contentType = response.headers.get('Content-Type'); 5 | if (contentType) { 6 | const jsonTypes = ['application/json', 'application/problem+json'] 7 | const isJSON = jsonTypes.some(type => contentType.toLowerCase().startsWith(type)); 8 | if (isJSON) { 9 | return await response.json(); 10 | } else { 11 | return await response.text(); 12 | } 13 | } 14 | } catch (error) { 15 | console.error(error); 16 | } 17 | } 18 | return undefined; 19 | }; 20 | -------------------------------------------------------------------------------- /src/templates/core/node/getResponseHeader.hbs: -------------------------------------------------------------------------------- 1 | export const getResponseHeader = (response: Response, responseHeader?: string): string | undefined => { 2 | if (responseHeader) { 3 | const content = response.headers.get(responseHeader); 4 | if (isString(content)) { 5 | return content; 6 | } 7 | } 8 | return undefined; 9 | }; 10 | -------------------------------------------------------------------------------- /src/templates/core/node/sendRequest.hbs: -------------------------------------------------------------------------------- 1 | export const sendRequest = async ( 2 | options: ApiRequestOptions, 3 | url: string, 4 | body: any, 5 | formData: FormData | undefined, 6 | headers: Headers, 7 | onCancel: OnCancel 8 | ): Promise => { 9 | const controller = new AbortController(); 10 | 11 | const request: RequestInit = { 12 | headers, 13 | method: options.method, 14 | body: body ?? formData, 15 | signal: controller.signal as AbortSignal, 16 | }; 17 | 18 | onCancel(() => controller.abort()); 19 | 20 | return await fetch(url, request); 21 | }; 22 | -------------------------------------------------------------------------------- /src/templates/core/request.hbs: -------------------------------------------------------------------------------- 1 | {{~#equals @root.httpClient 'fetch'}}{{>fetch/request}}{{/equals~}} 2 | {{~#equals @root.httpClient 'xhr'}}{{>xhr/request}}{{/equals~}} 3 | {{~#equals @root.httpClient 'axios'}}{{>axios/request}}{{/equals~}} 4 | {{~#equals @root.httpClient 'angular'}}{{>angular/request}}{{/equals~}} 5 | {{~#equals @root.httpClient 'node'}}{{>node/request}}{{/equals~}} 6 | -------------------------------------------------------------------------------- /src/templates/core/xhr/getHeaders.hbs: -------------------------------------------------------------------------------- 1 | export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions): Promise => { 2 | const [token, username, password, additionalHeaders] = await Promise.all([ 3 | resolve(options, config.TOKEN), 4 | resolve(options, config.USERNAME), 5 | resolve(options, config.PASSWORD), 6 | resolve(options, config.HEADERS), 7 | ]); 8 | 9 | const headers = Object.entries({ 10 | Accept: 'application/json', 11 | ...additionalHeaders, 12 | ...options.headers, 13 | }) 14 | .filter(([_, value]) => isDefined(value)) 15 | .reduce((headers, [key, value]) => ({ 16 | ...headers, 17 | [key]: String(value), 18 | }), {} as Record); 19 | 20 | if (isStringWithValue(token)) { 21 | headers['Authorization'] = `Bearer ${token}`; 22 | } 23 | 24 | if (isStringWithValue(username) && isStringWithValue(password)) { 25 | const credentials = base64(`${username}:${password}`); 26 | headers['Authorization'] = `Basic ${credentials}`; 27 | } 28 | 29 | if (options.body !== undefined) { 30 | if (options.mediaType) { 31 | headers['Content-Type'] = options.mediaType; 32 | } else if (isBlob(options.body)) { 33 | headers['Content-Type'] = options.body.type || 'application/octet-stream'; 34 | } else if (isString(options.body)) { 35 | headers['Content-Type'] = 'text/plain'; 36 | } else if (!isFormData(options.body)) { 37 | headers['Content-Type'] = 'application/json'; 38 | } 39 | } 40 | 41 | return new Headers(headers); 42 | }; 43 | -------------------------------------------------------------------------------- /src/templates/core/xhr/getRequestBody.hbs: -------------------------------------------------------------------------------- 1 | export const getRequestBody = (options: ApiRequestOptions): any => { 2 | if (options.body !== undefined) { 3 | if (options.mediaType?.includes('/json')) { 4 | return JSON.stringify(options.body) 5 | } else if (isString(options.body) || isBlob(options.body) || isFormData(options.body)) { 6 | return options.body; 7 | } else { 8 | return JSON.stringify(options.body); 9 | } 10 | } 11 | return undefined; 12 | }; 13 | -------------------------------------------------------------------------------- /src/templates/core/xhr/getResponseBody.hbs: -------------------------------------------------------------------------------- 1 | export const getResponseBody = (xhr: XMLHttpRequest): any => { 2 | if (xhr.status !== 204) { 3 | try { 4 | const contentType = xhr.getResponseHeader('Content-Type'); 5 | if (contentType) { 6 | const jsonTypes = ['application/json', 'application/problem+json'] 7 | const isJSON = jsonTypes.some(type => contentType.toLowerCase().startsWith(type)); 8 | if (isJSON) { 9 | return JSON.parse(xhr.responseText); 10 | } else { 11 | return xhr.responseText; 12 | } 13 | } 14 | } catch (error) { 15 | console.error(error); 16 | } 17 | } 18 | return undefined; 19 | }; 20 | -------------------------------------------------------------------------------- /src/templates/core/xhr/getResponseHeader.hbs: -------------------------------------------------------------------------------- 1 | export const getResponseHeader = (xhr: XMLHttpRequest, responseHeader?: string): string | undefined => { 2 | if (responseHeader) { 3 | const content = xhr.getResponseHeader(responseHeader); 4 | if (isString(content)) { 5 | return content; 6 | } 7 | } 8 | return undefined; 9 | }; 10 | -------------------------------------------------------------------------------- /src/templates/core/xhr/sendRequest.hbs: -------------------------------------------------------------------------------- 1 | export const sendRequest = async ( 2 | config: OpenAPIConfig, 3 | options: ApiRequestOptions, 4 | url: string, 5 | body: any, 6 | formData: FormData | undefined, 7 | headers: Headers, 8 | onCancel: OnCancel 9 | ): Promise => { 10 | const xhr = new XMLHttpRequest(); 11 | xhr.open(options.method, url, true); 12 | xhr.withCredentials = config.WITH_CREDENTIALS; 13 | 14 | headers.forEach((value, key) => { 15 | xhr.setRequestHeader(key, value); 16 | }); 17 | 18 | return new Promise((resolve, reject) => { 19 | xhr.onload = () => resolve(xhr); 20 | xhr.onabort = () => reject(new Error('Request aborted')); 21 | xhr.onerror = () => reject(new Error('Network error')); 22 | xhr.send(body ?? formData); 23 | 24 | onCancel(() => xhr.abort()); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /src/templates/exportModel.hbs: -------------------------------------------------------------------------------- 1 | {{>header}} 2 | 3 | {{#if imports}} 4 | 5 | {{#each imports}} 6 | import type { {{{this}}} } from './{{{this}}}'; 7 | {{/each}} 8 | {{/if}} 9 | 10 | {{#equals export 'interface'}} 11 | {{>exportInterface}} 12 | {{else equals export 'one-of'}} 13 | {{>exportComposition}} 14 | {{else equals export 'any-of'}} 15 | {{>exportComposition}} 16 | {{else equals export 'all-of'}} 17 | {{>exportComposition}} 18 | {{else equals export 'enum'}} 19 | {{#if @root.useUnionTypes}} 20 | {{>exportType}} 21 | {{else}} 22 | {{>exportEnum}} 23 | {{/if}} 24 | {{else}} 25 | {{>exportType}} 26 | {{/equals}} 27 | -------------------------------------------------------------------------------- /src/templates/exportSchema.hbs: -------------------------------------------------------------------------------- 1 | {{>header}} 2 | 3 | export const ${{{name}}} = {{>schema}} as const; 4 | -------------------------------------------------------------------------------- /src/templates/index.hbs: -------------------------------------------------------------------------------- 1 | {{>header}} 2 | 3 | {{#if @root.exportClient}} 4 | export { {{{clientName}}} } from './{{{clientName}}}'; 5 | 6 | {{/if}} 7 | {{#if @root.exportCore}} 8 | export { ApiError } from './core/ApiError'; 9 | {{#if @root.exportClient}} 10 | export { BaseHttpRequest } from './core/BaseHttpRequest'; 11 | {{/if}} 12 | export { CancelablePromise, CancelError } from './core/CancelablePromise'; 13 | export { OpenAPI } from './core/OpenAPI'; 14 | export type { OpenAPIConfig } from './core/OpenAPI'; 15 | {{/if}} 16 | {{#if @root.exportModels}} 17 | {{#if models}} 18 | 19 | {{#each models}} 20 | {{#if @root.useUnionTypes}} 21 | export type { {{{name}}}{{#if @root.postfixModels}} as {{{name}}}{{{@root.postfixModels}}}{{/if}} } from './models/{{{name}}}'; 22 | {{else if enum}} 23 | export { {{{name}}}{{#if @root.postfixModels}} as {{{name}}}{{{@root.postfixModels}}}{{/if}} } from './models/{{{name}}}'; 24 | {{else if enums}} 25 | export { {{{name}}}{{#if @root.postfixModels}} as {{{name}}}{{{@root.postfixModels}}}{{/if}} } from './models/{{{name}}}'; 26 | {{else}} 27 | export type { {{{name}}}{{#if @root.postfixModels}} as {{{name}}}{{{@root.postfixModels}}}{{/if}} } from './models/{{{name}}}'; 28 | {{/if}} 29 | {{/each}} 30 | {{/if}} 31 | {{/if}} 32 | {{#if @root.exportSchemas}} 33 | {{#if models}} 34 | 35 | {{#each models}} 36 | export { ${{{name}}} } from './schemas/${{{name}}}'; 37 | {{/each}} 38 | {{/if}} 39 | {{/if}} 40 | {{#if @root.exportServices}} 41 | {{#if services}} 42 | 43 | {{#each services}} 44 | export { {{{name}}}{{{@root.postfixServices}}} } from './services/{{{name}}}{{{@root.postfixServices}}}'; 45 | {{/each}} 46 | {{/if}} 47 | {{/if}} 48 | -------------------------------------------------------------------------------- /src/templates/partials/base.hbs: -------------------------------------------------------------------------------- 1 | {{~#equals base 'binary'~}} 2 | {{~#equals @root.httpClient 'fetch'}}Blob{{/equals~}} 3 | {{~#equals @root.httpClient 'xhr'}}Blob{{/equals~}} 4 | {{~#equals @root.httpClient 'axios'}}Blob{{/equals~}} 5 | {{~#equals @root.httpClient 'angular'}}Blob{{/equals~}} 6 | {{~#equals @root.httpClient 'node'}}Blob{{/equals~}} 7 | {{~else~}} 8 | {{{base}}} 9 | {{~/equals~}} 10 | -------------------------------------------------------------------------------- /src/templates/partials/exportComposition.hbs: -------------------------------------------------------------------------------- 1 | {{#ifdef description deprecated}} 2 | /** 3 | {{#if description}} 4 | * {{{escapeComment description}}} 5 | {{/if}} 6 | {{#if deprecated}} 7 | * @deprecated 8 | {{/if}} 9 | */ 10 | {{/ifdef}} 11 | export type {{{name}}} = {{>type parent=name}}; 12 | {{#if enums}} 13 | {{#unless @root.useUnionTypes}} 14 | 15 | export namespace {{{name}}} { 16 | 17 | {{#each enums}} 18 | {{#ifdef description deprecated}} 19 | /** 20 | {{#if description}} 21 | * {{{escapeComment description}}} 22 | {{/if}} 23 | {{#if deprecated}} 24 | * @deprecated 25 | {{/if}} 26 | */ 27 | {{/ifdef}} 28 | export enum {{{name}}} { 29 | {{#each enum}} 30 | {{{name}}} = {{{value}}}, 31 | {{/each}} 32 | } 33 | 34 | {{/each}} 35 | 36 | } 37 | {{/unless}} 38 | {{/if}} 39 | -------------------------------------------------------------------------------- /src/templates/partials/exportEnum.hbs: -------------------------------------------------------------------------------- 1 | {{#ifdef description deprecated}} 2 | /** 3 | {{#if description}} 4 | * {{{escapeComment description}}} 5 | {{/if}} 6 | {{#if deprecated}} 7 | * @deprecated 8 | {{/if}} 9 | */ 10 | {{/ifdef}} 11 | export enum {{{name}}} { 12 | {{#each enum}} 13 | {{#if description}} 14 | /** 15 | * {{{escapeComment description}}} 16 | */ 17 | {{/if}} 18 | {{#containsSpaces name}} 19 | '{{{name}}}' = {{{value}}}, 20 | {{else}} 21 | {{{name}}} = {{{value}}}, 22 | {{/containsSpaces}} 23 | {{/each}} 24 | } 25 | -------------------------------------------------------------------------------- /src/templates/partials/exportInterface.hbs: -------------------------------------------------------------------------------- 1 | {{#ifdef description deprecated}} 2 | /** 3 | {{#if description}} 4 | * {{{escapeComment description}}} 5 | {{/if}} 6 | {{#if deprecated}} 7 | * @deprecated 8 | {{/if}} 9 | */ 10 | {{/ifdef}} 11 | export type {{{name}}} = { 12 | {{#each properties}} 13 | {{#ifdef description deprecated}} 14 | /** 15 | {{#if description}} 16 | * {{{escapeComment description}}} 17 | {{/if}} 18 | {{#if deprecated}} 19 | * @deprecated 20 | {{/if}} 21 | */ 22 | {{/ifdef}} 23 | {{>isReadOnly}}{{{name}}}{{>isRequired}}: {{>type parent=../name}}; 24 | {{/each}} 25 | }; 26 | {{#if enums}} 27 | {{#unless @root.useUnionTypes}} 28 | 29 | export namespace {{{name}}} { 30 | 31 | {{#each enums}} 32 | {{#if description}} 33 | /** 34 | * {{{escapeComment description}}} 35 | */ 36 | {{/if}} 37 | export enum {{{name}}} { 38 | {{#each enum}} 39 | {{{name}}} = {{{value}}}, 40 | {{/each}} 41 | } 42 | 43 | {{/each}} 44 | 45 | } 46 | {{/unless}} 47 | {{/if}} 48 | -------------------------------------------------------------------------------- /src/templates/partials/exportType.hbs: -------------------------------------------------------------------------------- 1 | {{#ifdef description deprecated}} 2 | /** 3 | {{#if description}} 4 | * {{{escapeComment description}}} 5 | {{/if}} 6 | {{#if deprecated}} 7 | * @deprecated 8 | {{/if}} 9 | */ 10 | {{/ifdef}} 11 | export type {{{name}}} = {{>type}}; 12 | -------------------------------------------------------------------------------- /src/templates/partials/header.hbs: -------------------------------------------------------------------------------- 1 | /* generated using openapi-typescript-codegen -- do not edit */ 2 | /* istanbul ignore file */ 3 | /* tslint:disable */ 4 | /* eslint-disable */ 5 | -------------------------------------------------------------------------------- /src/templates/partials/isNullable.hbs: -------------------------------------------------------------------------------- 1 | {{#if isNullable}} | null{{/if}} 2 | -------------------------------------------------------------------------------- /src/templates/partials/isReadOnly.hbs: -------------------------------------------------------------------------------- 1 | {{#if isReadOnly}}readonly {{/if}} 2 | -------------------------------------------------------------------------------- /src/templates/partials/isRequired.hbs: -------------------------------------------------------------------------------- 1 | {{~#if @root.useOptions~}} 2 | {{#unless isRequired}}?{{else if default}}?{{/unless}} 3 | {{~else~}} 4 | {{#unless isRequired}}{{#unless default}}?{{/unless}}{{/unless}} 5 | {{~/if~}} 6 | -------------------------------------------------------------------------------- /src/templates/partials/parameters.hbs: -------------------------------------------------------------------------------- 1 | {{#if parameters}} 2 | {{#if @root.useOptions~}} 3 | { 4 | {{#each parameters}} 5 | {{{name}}}{{#if default}} = {{{default}}}{{/if}}, 6 | {{/each}} 7 | }: { 8 | {{#each parameters}} 9 | {{#ifdef description deprecated}} 10 | /** 11 | {{#if description}} 12 | * {{{escapeComment description}}} 13 | {{/if}} 14 | {{#if deprecated}} 15 | * @deprecated 16 | {{/if}} 17 | */ 18 | {{/ifdef}} 19 | {{{name}}}{{>isRequired}}: {{>type}}, 20 | {{/each}} 21 | } 22 | {{~else}} 23 | 24 | {{#each parameters}} 25 | {{{name}}}{{>isRequired}}: {{>type}}{{#if default}} = {{{default}}}{{/if}}, 26 | {{/each}} 27 | {{/if}} 28 | {{/if}} 29 | -------------------------------------------------------------------------------- /src/templates/partials/result.hbs: -------------------------------------------------------------------------------- 1 | {{~#if results~}} 2 | {{#each results}}{{>type}}{{#unless @last}} | {{/unless}}{{/each}} 3 | {{~else~}} 4 | void 5 | {{~/if~}} 6 | -------------------------------------------------------------------------------- /src/templates/partials/schema.hbs: -------------------------------------------------------------------------------- 1 | {{#equals export 'interface'}} 2 | {{>schemaInterface}} 3 | {{else equals export 'enum'}} 4 | {{>schemaEnum}} 5 | {{else equals export 'array'}} 6 | {{>schemaArray}} 7 | {{else equals export 'dictionary'}} 8 | {{>schemaDictionary}} 9 | {{else equals export 'any-of'}} 10 | {{>schemaComposition}} 11 | {{else equals export 'all-of'}} 12 | {{>schemaComposition}} 13 | {{else equals export 'one-of'}} 14 | {{>schemaComposition}} 15 | {{else}} 16 | {{>schemaGeneric}} 17 | {{/equals}} 18 | -------------------------------------------------------------------------------- /src/templates/partials/schemaArray.hbs: -------------------------------------------------------------------------------- 1 | { 2 | type: 'array', 3 | {{#if link}} 4 | contains: {{>schema link}}, 5 | {{else}} 6 | contains: { 7 | type: '{{{base}}}', 8 | }, 9 | {{/if}} 10 | {{#if isReadOnly}} 11 | isReadOnly: {{{isReadOnly}}}, 12 | {{/if}} 13 | {{#if isRequired}} 14 | isRequired: {{{isRequired}}}, 15 | {{/if}} 16 | {{#if isNullable}} 17 | isNullable: {{{isNullable}}}, 18 | {{/if}} 19 | } 20 | -------------------------------------------------------------------------------- /src/templates/partials/schemaComposition.hbs: -------------------------------------------------------------------------------- 1 | { 2 | type: '{{export}}', 3 | {{#if description}} 4 | description: `{{{escapeDescription description}}}`, 5 | {{/if}} 6 | contains: [{{#each properties}}{{>schema}}{{#unless @last}}, {{/unless}}{{/each}}], 7 | {{#if isReadOnly}} 8 | isReadOnly: {{{isReadOnly}}}, 9 | {{/if}} 10 | {{#if isRequired}} 11 | isRequired: {{{isRequired}}}, 12 | {{/if}} 13 | {{#if isNullable}} 14 | isNullable: {{{isNullable}}}, 15 | {{/if}} 16 | } 17 | -------------------------------------------------------------------------------- /src/templates/partials/schemaDictionary.hbs: -------------------------------------------------------------------------------- 1 | { 2 | type: 'dictionary', 3 | {{#if link}} 4 | contains: {{>schema link}}, 5 | {{else}} 6 | contains: { 7 | type: '{{{base}}}', 8 | }, 9 | {{/if}} 10 | {{#if isReadOnly}} 11 | isReadOnly: {{{isReadOnly}}}, 12 | {{/if}} 13 | {{#if isRequired}} 14 | isRequired: {{{isRequired}}}, 15 | {{/if}} 16 | {{#if isNullable}} 17 | isNullable: {{{isNullable}}}, 18 | {{/if}} 19 | } 20 | -------------------------------------------------------------------------------- /src/templates/partials/schemaEnum.hbs: -------------------------------------------------------------------------------- 1 | { 2 | type: 'Enum', 3 | {{#if isReadOnly}} 4 | isReadOnly: {{{isReadOnly}}}, 5 | {{/if}} 6 | {{#if isRequired}} 7 | isRequired: {{{isRequired}}}, 8 | {{/if}} 9 | {{#if isNullable}} 10 | isNullable: {{{isNullable}}}, 11 | {{/if}} 12 | } 13 | -------------------------------------------------------------------------------- /src/templates/partials/schemaGeneric.hbs: -------------------------------------------------------------------------------- 1 | { 2 | {{#if type}} 3 | type: '{{{type}}}', 4 | {{/if}} 5 | {{#if description}} 6 | description: `{{{escapeDescription description}}}`, 7 | {{/if}} 8 | {{#if isReadOnly}} 9 | isReadOnly: {{{isReadOnly}}}, 10 | {{/if}} 11 | {{#if isRequired}} 12 | isRequired: {{{isRequired}}}, 13 | {{/if}} 14 | {{#if isNullable}} 15 | isNullable: {{{isNullable}}}, 16 | {{/if}} 17 | {{#if format}} 18 | format: '{{{format}}}', 19 | {{/if}} 20 | {{#if maximum}} 21 | maximum: {{{maximum}}}, 22 | {{/if}} 23 | {{#if exclusiveMaximum}} 24 | exclusiveMaximum: {{{exclusiveMaximum}}}, 25 | {{/if}} 26 | {{#if minimum}} 27 | minimum: {{{minimum}}}, 28 | {{/if}} 29 | {{#if exclusiveMinimum}} 30 | exclusiveMinimum: {{{exclusiveMinimum}}}, 31 | {{/if}} 32 | {{#if multipleOf}} 33 | multipleOf: {{{multipleOf}}}, 34 | {{/if}} 35 | {{#if maxLength}} 36 | maxLength: {{{maxLength}}}, 37 | {{/if}} 38 | {{#if minLength}} 39 | minLength: {{{minLength}}}, 40 | {{/if}} 41 | {{#if pattern}} 42 | pattern: '{{{pattern}}}', 43 | {{/if}} 44 | {{#if maxItems}} 45 | maxItems: {{{maxItems}}}, 46 | {{/if}} 47 | {{#if minItems}} 48 | minItems: {{{minItems}}}, 49 | {{/if}} 50 | {{#if uniqueItems}} 51 | uniqueItems: {{{uniqueItems}}}, 52 | {{/if}} 53 | {{#if maxProperties}} 54 | maxProperties: {{{maxProperties}}}, 55 | {{/if}} 56 | {{#if minProperties}} 57 | minProperties: {{{minProperties}}}, 58 | {{/if}} 59 | } 60 | -------------------------------------------------------------------------------- /src/templates/partials/schemaInterface.hbs: -------------------------------------------------------------------------------- 1 | { 2 | {{#if description}} 3 | description: `{{{escapeDescription description}}}`, 4 | {{/if}} 5 | properties: { 6 | {{#if properties}} 7 | {{#each properties}} 8 | {{{name}}}: {{>schema}}, 9 | {{/each}} 10 | {{/if}} 11 | }, 12 | {{#if isReadOnly}} 13 | isReadOnly: {{{isReadOnly}}}, 14 | {{/if}} 15 | {{#if isRequired}} 16 | isRequired: {{{isRequired}}}, 17 | {{/if}} 18 | {{#if isNullable}} 19 | isNullable: {{{isNullable}}}, 20 | {{/if}} 21 | } 22 | -------------------------------------------------------------------------------- /src/templates/partials/type.hbs: -------------------------------------------------------------------------------- 1 | {{#equals export 'interface'}} 2 | {{>typeInterface}} 3 | {{else equals export 'reference'}} 4 | {{>typeReference}} 5 | {{else equals export 'enum'}} 6 | {{>typeEnum}} 7 | {{else equals export 'array'}} 8 | {{>typeArray}} 9 | {{else equals export 'dictionary'}} 10 | {{>typeDictionary}} 11 | {{else equals export 'one-of'}} 12 | {{>typeUnion}} 13 | {{else equals export 'any-of'}} 14 | {{>typeUnion}} 15 | {{else equals export 'all-of'}} 16 | {{>typeIntersection}} 17 | {{else}} 18 | {{>typeGeneric}} 19 | {{/equals}} 20 | -------------------------------------------------------------------------------- /src/templates/partials/typeArray.hbs: -------------------------------------------------------------------------------- 1 | {{~#if link~}} 2 | Array<{{>type link}}>{{>isNullable}} 3 | {{~else~}} 4 | Array<{{>base}}>{{>isNullable}} 5 | {{~/if~}} 6 | -------------------------------------------------------------------------------- /src/templates/partials/typeDictionary.hbs: -------------------------------------------------------------------------------- 1 | {{~#if link~}} 2 | Recordtype link}}>{{>isNullable}} 3 | {{~else~}} 4 | Recordbase}}>{{>isNullable}} 5 | {{~/if~}} 6 | -------------------------------------------------------------------------------- /src/templates/partials/typeEnum.hbs: -------------------------------------------------------------------------------- 1 | {{#enumerator enum parent name}}{{this}}{{/enumerator}}{{>isNullable}} 2 | 3 | -------------------------------------------------------------------------------- /src/templates/partials/typeGeneric.hbs: -------------------------------------------------------------------------------- 1 | {{>base}}{{>isNullable}} 2 | -------------------------------------------------------------------------------- /src/templates/partials/typeInterface.hbs: -------------------------------------------------------------------------------- 1 | {{~#if properties~}} 2 | { 3 | {{#each properties}} 4 | {{#ifdef description deprecated}} 5 | /** 6 | {{#if description}} 7 | * {{{escapeComment description}}} 8 | {{/if}} 9 | {{#if deprecated}} 10 | * @deprecated 11 | {{/if}} 12 | */ 13 | {{/ifdef}} 14 | {{#if ../parent}} 15 | {{>isReadOnly}}{{{name}}}{{>isRequired}}: {{>type parent=../parent}}; 16 | {{else}} 17 | {{>isReadOnly}}{{{name}}}{{>isRequired}}: {{>type}}; 18 | {{/if}} 19 | {{/each}} 20 | }{{>isNullable}} 21 | {{~else~}} 22 | any 23 | {{~/if~}} 24 | -------------------------------------------------------------------------------- /src/templates/partials/typeIntersection.hbs: -------------------------------------------------------------------------------- 1 | {{#intersection properties parent}}{{this}}{{/intersection}}{{>isNullable}} 2 | -------------------------------------------------------------------------------- /src/templates/partials/typeReference.hbs: -------------------------------------------------------------------------------- 1 | {{>base}}{{>isNullable}} 2 | -------------------------------------------------------------------------------- /src/templates/partials/typeUnion.hbs: -------------------------------------------------------------------------------- 1 | {{#union properties parent}}{{this}}{{/union}}{{>isNullable}} 2 | -------------------------------------------------------------------------------- /src/typings/hbs.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * We precompile the handlebar templates during the build process, 3 | * however in the source code we want to reference these templates 4 | * by importing the hbs files directly. Of course this is not allowed 5 | * by Typescript, so we need to provide some declaration for these 6 | * types. 7 | * @see: build.js for more information 8 | */ 9 | declare module '*.hbs' { 10 | const template: { 11 | compiler: [number, string]; 12 | useData: true; 13 | main: () => void; 14 | }; 15 | export default template; 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/__mocks__/fileSystem.ts: -------------------------------------------------------------------------------- 1 | export const readFile = jest.fn(); 2 | export const writeFile = jest.fn(); 3 | export const copyFile = jest.fn(); 4 | export const exists = jest.fn(); 5 | export const rmdir = jest.fn(); 6 | export const mkdir = jest.fn(); 7 | -------------------------------------------------------------------------------- /src/utils/discriminator.ts: -------------------------------------------------------------------------------- 1 | import type { Model } from '../client/interfaces/Model'; 2 | import type { OpenApi } from '../openApi/v3/interfaces/OpenApi'; 3 | import type { OpenApiDiscriminator } from '../openApi/v3/interfaces/OpenApiDiscriminator'; 4 | import { stripNamespace } from '../openApi/v3/parser/stripNamespace'; 5 | import type { Dictionary } from './types'; 6 | 7 | const inverseDictionary = (map: Dictionary): Dictionary => { 8 | const m2: Dictionary = {}; 9 | for (const key in map) { 10 | m2[map[key]] = key; 11 | } 12 | return m2; 13 | }; 14 | 15 | export const findOneOfParentDiscriminator = (openApi: OpenApi, parent?: Model): OpenApiDiscriminator | undefined => { 16 | if (openApi.components && parent) { 17 | for (const definitionName in openApi.components.schemas) { 18 | if (openApi.components.schemas.hasOwnProperty(definitionName)) { 19 | const schema = openApi.components.schemas[definitionName]; 20 | if ( 21 | schema.discriminator && 22 | schema.oneOf?.length && 23 | schema.oneOf.some(definition => definition.$ref && stripNamespace(definition.$ref) == parent.name) 24 | ) { 25 | return schema.discriminator; 26 | } 27 | } 28 | } 29 | } 30 | return undefined; 31 | }; 32 | 33 | export const mapPropertyValue = (discriminator: OpenApiDiscriminator, parent: Model): string => { 34 | if (discriminator.mapping) { 35 | const mapping = inverseDictionary(discriminator.mapping); 36 | const key = Object.keys(mapping).find(item => stripNamespace(item) == parent.name); 37 | if (key && mapping[key]) { 38 | return mapping[key]; 39 | } 40 | } 41 | return parent.name; 42 | }; 43 | -------------------------------------------------------------------------------- /src/utils/fileSystem.ts: -------------------------------------------------------------------------------- 1 | import { 2 | copyFile as __copyFile, 3 | mkdirp as __mkdirp, 4 | pathExists as __pathExists, 5 | readFile as __readFile, 6 | remove as __remove, 7 | writeFile as __writeFile, 8 | } from 'fs-extra'; 9 | 10 | // Export calls (needed for mocking) 11 | export const readFile = __readFile; 12 | export const writeFile = __writeFile; 13 | export const copyFile = __copyFile; 14 | export const exists = __pathExists; 15 | export const mkdir = __mkdirp; 16 | export const rmdir = __remove; 17 | -------------------------------------------------------------------------------- /src/utils/flatMap.spec.ts: -------------------------------------------------------------------------------- 1 | import { flatMap } from './flatMap'; 2 | 3 | describe('flatMap', () => { 4 | it('should produce correct result', () => { 5 | expect(flatMap([1, 2, 3], i => [i])).toEqual([1, 2, 3]); 6 | expect(flatMap([1, 2, 3], i => [i + 1])).toEqual([2, 3, 4]); 7 | expect(flatMap([1, 2, 3], () => [1])).toEqual([1, 1, 1]); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /src/utils/flatMap.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Calls a defined callback on each element of an array. 3 | * Then, flattens the result into a new array. 4 | */ 5 | export const flatMap = (array: T[], callback: (value: T, index: number, array: T[]) => U[]): U[] => { 6 | const result: U[] = []; 7 | array.map(callback).forEach(arr => { 8 | result.push(...arr); 9 | }); 10 | return result; 11 | }; 12 | -------------------------------------------------------------------------------- /src/utils/formatCode.spec.ts: -------------------------------------------------------------------------------- 1 | import { EOL } from 'os'; 2 | 3 | import { formatCode } from './formatCode'; 4 | 5 | const input1 = `{ foo: true }`; 6 | 7 | const output1 = `{ foo: true }`; 8 | 9 | const input2 = `{ foo: true, bar: 123 }`; 10 | 11 | const output2 = `{ foo: true, bar: 123 }`; 12 | 13 | const input3 = `{ 14 | foo: true, 15 | bar: 123 16 | }`; 17 | 18 | const output3 = `{${EOL}\tfoo: true,${EOL}\tbar: 123${EOL}}`; 19 | 20 | const input4 = `{ 21 | \t\t\t\tfoo: true, 22 | \t\t\t\tbar: 123 23 | }`; 24 | 25 | const output4 = `{${EOL}\tfoo: true,${EOL}\tbar: 123${EOL}}`; 26 | 27 | describe('format', () => { 28 | it('should produce correct result', () => { 29 | expect(formatCode(``)).toEqual(''); 30 | expect(formatCode(`{}`)).toEqual('{}'); 31 | expect(formatCode(input1)).toEqual(output1); 32 | expect(formatCode(input2)).toEqual(output2); 33 | expect(formatCode(input3)).toEqual(output3); 34 | expect(formatCode(input4)).toEqual(output4); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/utils/formatCode.ts: -------------------------------------------------------------------------------- 1 | import { EOL } from 'os'; 2 | 3 | export const formatCode = (s: string): string => { 4 | let indent: number = 0; 5 | let lines = s.split(/[\r\n]+/); 6 | lines = lines.map(line => { 7 | line = line.trim().replace(/^\*/g, ' *'); 8 | let i = indent; 9 | if (line.endsWith('(') || line.endsWith('{') || line.endsWith('[')) { 10 | indent++; 11 | } 12 | if ((line.startsWith(')') || line.startsWith('}') || line.startsWith(']')) && i) { 13 | indent--; 14 | i--; 15 | } 16 | const result = `${'\t'.repeat(i)}${line}`; 17 | if (result.trim() === '') { 18 | return ''; 19 | } 20 | return result; 21 | }); 22 | return lines.join(EOL); 23 | }; 24 | -------------------------------------------------------------------------------- /src/utils/formatIndentation.ts: -------------------------------------------------------------------------------- 1 | import { EOL } from 'os'; 2 | 3 | import { Indent } from '../Indent'; 4 | 5 | export const formatIndentation = (s: string, indent: Indent): string => { 6 | let lines = s.split(EOL); 7 | lines = lines.map(line => { 8 | switch (indent) { 9 | case Indent.SPACE_4: 10 | return line.replace(/\t/g, ' '); 11 | case Indent.SPACE_2: 12 | return line.replace(/\t/g, ' '); 13 | case Indent.TAB: 14 | return line; // Default output is tab formatted 15 | } 16 | }); 17 | // Make sure we have a blank line at the end 18 | const content = lines.join(EOL); 19 | return `${content}${EOL}`; 20 | }; 21 | -------------------------------------------------------------------------------- /src/utils/getHttpRequestName.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '../HttpClient'; 2 | 3 | /** 4 | * Generate the HttpRequest filename based on the selected client 5 | * @param httpClient The selected httpClient (fetch, xhr, node or axios) 6 | */ 7 | export const getHttpRequestName = (httpClient: HttpClient): string => { 8 | switch (httpClient) { 9 | case HttpClient.FETCH: 10 | return 'FetchHttpRequest'; 11 | case HttpClient.XHR: 12 | return 'XHRHttpRequest'; 13 | case HttpClient.NODE: 14 | return 'NodeHttpRequest'; 15 | case HttpClient.AXIOS: 16 | return 'AxiosHttpRequest'; 17 | case HttpClient.ANGULAR: 18 | return 'AngularHttpRequest'; 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/utils/getModelNames.ts: -------------------------------------------------------------------------------- 1 | import type { Model } from '../client/interfaces/Model'; 2 | import { sort } from './sort'; 3 | 4 | export const getModelNames = (models: Model[]): string[] => { 5 | return models.map(model => model.name).sort(sort); 6 | }; 7 | -------------------------------------------------------------------------------- /src/utils/getOpenApiSpec.ts: -------------------------------------------------------------------------------- 1 | import RefParser from '@apidevtools/json-schema-ref-parser'; 2 | import { resolve } from 'path'; 3 | 4 | import { exists } from './fileSystem'; 5 | 6 | /** 7 | * Load and parse te open api spec. If the file extension is ".yml" or ".yaml" 8 | * we will try to parse the file as a YAML spec, otherwise we will fall back 9 | * on parsing the file as JSON. 10 | * @param location: Path or url 11 | */ 12 | export const getOpenApiSpec = async (location: string): Promise => { 13 | const absolutePathOrUrl = (await exists(location)) ? resolve(location) : location; 14 | return await RefParser.bundle(absolutePathOrUrl, absolutePathOrUrl, {}); 15 | }; 16 | -------------------------------------------------------------------------------- /src/utils/getOpenApiVersion.spec.ts: -------------------------------------------------------------------------------- 1 | import { getOpenApiVersion } from './getOpenApiVersion'; 2 | 3 | describe('getOpenApiVersion', () => { 4 | it('should read the correct version', () => { 5 | expect(getOpenApiVersion({ openapi: '2' })).toEqual(2); 6 | expect(getOpenApiVersion({ openapi: '3' })).toEqual(3); 7 | expect(getOpenApiVersion({ openapi: '2.0' })).toEqual(2); 8 | expect(getOpenApiVersion({ openapi: '3.0' })).toEqual(3); 9 | 10 | expect(getOpenApiVersion({ swagger: '2' })).toEqual(2); 11 | expect(getOpenApiVersion({ swagger: '3' })).toEqual(3); 12 | expect(getOpenApiVersion({ swagger: '2.0' })).toEqual(2); 13 | expect(getOpenApiVersion({ swagger: '3.0' })).toEqual(3); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/utils/getOpenApiVersion.ts: -------------------------------------------------------------------------------- 1 | export enum OpenApiVersion { 2 | V2 = 2, 3 | V3 = 3, 4 | } 5 | 6 | /** 7 | * Get the Open API specification version (V2 or V3). This generator only supports 8 | * version 2 and 3 of the specification, so we will alert the user if we encounter 9 | * an incompatible type. Or if the type is missing... 10 | * @param openApi The loaded spec (can be any object) 11 | */ 12 | export const getOpenApiVersion = (openApi: any): OpenApiVersion => { 13 | const info: any = openApi.swagger || openApi.openapi; 14 | if (typeof info === 'string') { 15 | const c = info.charAt(0); 16 | const v = Number.parseInt(c); 17 | if (v === OpenApiVersion.V2 || v === OpenApiVersion.V3) { 18 | return v as OpenApiVersion; 19 | } 20 | } 21 | throw new Error(`Unsupported Open API version: "${String(info)}"`); 22 | }; 23 | -------------------------------------------------------------------------------- /src/utils/getPattern.spec.ts: -------------------------------------------------------------------------------- 1 | import { getPattern } from './getPattern'; 2 | 3 | describe('getPattern', () => { 4 | it('should produce correct result', () => { 5 | expect(getPattern()).toEqual(undefined); 6 | expect(getPattern('')).toEqual(''); 7 | expect(getPattern('^[a-zA-Z]')).toEqual('^[a-zA-Z]'); 8 | expect(getPattern('^\\w+$')).toEqual('^\\\\w+$'); 9 | expect(getPattern('^\\d{3}-\\d{2}-\\d{4}$')).toEqual('^\\\\d{3}-\\\\d{2}-\\\\d{4}$'); 10 | expect(getPattern('\\')).toEqual('\\\\'); 11 | expect(getPattern('\\/')).toEqual('\\\\/'); 12 | expect(getPattern('\\/\\/')).toEqual('\\\\/\\\\/'); 13 | // eslint-disable-next-line prettier/prettier 14 | expect(getPattern("'")).toEqual("\\'"); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/utils/getPattern.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The spec generates a pattern like this '^\d{3}-\d{2}-\d{4}$' 3 | * However, to use it in HTML or inside new RegExp() we need to 4 | * escape the pattern to become: '^\\d{3}-\\d{2}-\\d{4}$' in order 5 | * to make it a valid regexp string. 6 | * 7 | * Also, escape single quote characters, because the output uses single quotes for strings 8 | * 9 | * @param pattern 10 | */ 11 | export const getPattern = (pattern?: string): string | undefined => { 12 | // eslint-disable-next-line prettier/prettier 13 | return pattern?.replace(/\\/g, '\\\\').replace(/'/g, "\\'"); 14 | }; 15 | -------------------------------------------------------------------------------- /src/utils/getServiceNames.spec.ts: -------------------------------------------------------------------------------- 1 | import type { Service } from '../client/interfaces/Service'; 2 | import { getServiceNames } from './getServiceNames'; 3 | 4 | describe('getServiceNames', () => { 5 | it('should return sorted list', () => { 6 | const john: Service = { 7 | name: 'John', 8 | operations: [], 9 | imports: [], 10 | }; 11 | const jane: Service = { 12 | name: 'Jane', 13 | operations: [], 14 | imports: [], 15 | }; 16 | const doe: Service = { 17 | name: 'Doe', 18 | operations: [], 19 | imports: [], 20 | }; 21 | 22 | const services = [john, jane, doe]; 23 | 24 | expect(getServiceNames([])).toEqual([]); 25 | expect(getServiceNames(services)).toEqual(['Doe', 'Jane', 'John']); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/utils/getServiceNames.ts: -------------------------------------------------------------------------------- 1 | import type { Service } from '../client/interfaces/Service'; 2 | import { sort } from './sort'; 3 | 4 | export const getServiceNames = (services: Service[]): string[] => { 5 | return services.map(service => service.name).sort(sort); 6 | }; 7 | -------------------------------------------------------------------------------- /src/utils/isDefined.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Check if a value is defined 3 | * @param value 4 | */ 5 | export const isDefined = (value: T | undefined | null | ''): value is Exclude => { 6 | return value !== undefined && value !== null && value !== ''; 7 | }; 8 | -------------------------------------------------------------------------------- /src/utils/isEqual.ts: -------------------------------------------------------------------------------- 1 | export const isEqual = (a: any, b: any): boolean => { 2 | if (a === b) { 3 | return true; 4 | } 5 | 6 | if (a && b && typeof a === 'object' && typeof b === 'object') { 7 | if (Array.isArray(a) && Array.isArray(b)) { 8 | if (a.length !== b.length) { 9 | return false; 10 | } 11 | for (let i = 0; i < a.length; i++) { 12 | if (!isEqual(a[i], b[i])) { 13 | return false; 14 | } 15 | } 16 | return true; 17 | } 18 | 19 | const keysA = Object.keys(a); 20 | const keysB = Object.keys(b); 21 | if (keysA.length !== keysB.length) { 22 | return false; 23 | } 24 | 25 | for (let i = 0; i < keysA.length; i++) { 26 | const key = keysA[i]; 27 | if (!Object.prototype.hasOwnProperty.call(b, key)) { 28 | return false; 29 | } 30 | if (!isEqual(a[key], b[key])) { 31 | return false; 32 | } 33 | } 34 | return true; 35 | } 36 | 37 | return a !== a && b !== b; 38 | }; 39 | -------------------------------------------------------------------------------- /src/utils/isString.spec.ts: -------------------------------------------------------------------------------- 1 | import { isString } from './isString'; 2 | 3 | describe('isString', () => { 4 | it('should produce correct result', () => { 5 | expect(isString('foo')).toBeTruthy(); 6 | expect(isString('123')).toBeTruthy(); 7 | expect(isString('-1')).toBeTruthy(); 8 | expect(isString('')).toBeTruthy(); 9 | expect(isString(null)).toBeFalsy(); 10 | expect(isString(undefined)).toBeFalsy(); 11 | expect(isString({})).toBeFalsy(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/utils/isString.ts: -------------------------------------------------------------------------------- 1 | export const isString = (val: any): val is string => { 2 | return typeof val === 'string'; 3 | }; 4 | -------------------------------------------------------------------------------- /src/utils/isSubdirectory.spec.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | 3 | import { isSubDirectory } from './isSubdirectory'; 4 | 5 | describe('isSubDirectory', () => { 6 | it('should return correct result', () => { 7 | expect(isSubDirectory(resolve('/'), resolve('/'))).toBeFalsy(); 8 | expect(isSubDirectory(resolve('.'), resolve('.'))).toBeFalsy(); 9 | expect(isSubDirectory(resolve('./project'), resolve('./project'))).toBeFalsy(); 10 | expect(isSubDirectory(resolve('./project'), resolve('../'))).toBeFalsy(); 11 | expect(isSubDirectory(resolve('./project'), resolve('../../'))).toBeFalsy(); 12 | expect(isSubDirectory(resolve('./'), resolve('./output'))).toBeTruthy(); 13 | expect(isSubDirectory(resolve('./'), resolve('../output'))).toBeTruthy(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/utils/isSubdirectory.ts: -------------------------------------------------------------------------------- 1 | import { relative } from 'path'; 2 | 3 | export const isSubDirectory = (parent: string, child: string) => { 4 | return relative(child, parent).startsWith('..'); 5 | }; 6 | -------------------------------------------------------------------------------- /src/utils/postProcessClient.ts: -------------------------------------------------------------------------------- 1 | import type { Client } from '../client/interfaces/Client'; 2 | import { postProcessModel } from './postProcessModel'; 3 | import { postProcessService } from './postProcessService'; 4 | 5 | /** 6 | * Post process client 7 | * @param client Client object with all the models, services, etc. 8 | */ 9 | export const postProcessClient = (client: Client): Client => { 10 | return { 11 | ...client, 12 | models: client.models.map(model => postProcessModel(model)), 13 | services: client.services.map(service => postProcessService(service)), 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /src/utils/postProcessModel.ts: -------------------------------------------------------------------------------- 1 | import type { Model } from '../client/interfaces/Model'; 2 | import { postProcessModelEnum } from './postProcessModelEnum'; 3 | import { postProcessModelEnums } from './postProcessModelEnums'; 4 | import { postProcessModelImports } from './postProcessModelImports'; 5 | 6 | /** 7 | * Post processes the model. 8 | * This will clean up any double imports or enum values. 9 | * @param model 10 | */ 11 | export const postProcessModel = (model: Model): Model => { 12 | return { 13 | ...model, 14 | imports: postProcessModelImports(model), 15 | enums: postProcessModelEnums(model), 16 | enum: postProcessModelEnum(model), 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /src/utils/postProcessModelEnum.ts: -------------------------------------------------------------------------------- 1 | import type { Enum } from '../client/interfaces/Enum'; 2 | import type { Model } from '../client/interfaces/Model'; 3 | 4 | /** 5 | * Set unique enum values for the model 6 | * @param model 7 | */ 8 | export const postProcessModelEnum = (model: Model): Enum[] => { 9 | return model.enum.filter((property, index, arr) => { 10 | return arr.findIndex(item => item.name === property.name) === index; 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /src/utils/postProcessModelEnums.ts: -------------------------------------------------------------------------------- 1 | import type { Model } from '../client/interfaces/Model'; 2 | 3 | /** 4 | * Set unique enum values for the model 5 | * @param model The model that is post-processed 6 | */ 7 | export const postProcessModelEnums = (model: Model): Model[] => { 8 | return model.enums.filter((property, index, arr) => { 9 | return arr.findIndex(item => item.name === property.name) === index; 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /src/utils/postProcessModelImports.ts: -------------------------------------------------------------------------------- 1 | import type { Model } from '../client/interfaces/Model'; 2 | import { sort } from './sort'; 3 | import { unique } from './unique'; 4 | 5 | /** 6 | * Set unique imports, sorted by name 7 | * @param model The model that is post-processed 8 | */ 9 | export const postProcessModelImports = (model: Model): string[] => { 10 | return model.imports 11 | .filter(unique) 12 | .sort(sort) 13 | .filter(name => model.name !== name); 14 | }; 15 | -------------------------------------------------------------------------------- /src/utils/postProcessService.ts: -------------------------------------------------------------------------------- 1 | import type { Service } from '../client/interfaces/Service'; 2 | import { postProcessServiceImports } from './postProcessServiceImports'; 3 | import { postProcessServiceOperations } from './postProcessServiceOperations'; 4 | 5 | export const postProcessService = (service: Service): Service => { 6 | const clone = { ...service }; 7 | clone.operations = postProcessServiceOperations(clone); 8 | clone.operations.forEach(operation => { 9 | clone.imports.push(...operation.imports); 10 | }); 11 | clone.imports = postProcessServiceImports(clone); 12 | return clone; 13 | }; 14 | -------------------------------------------------------------------------------- /src/utils/postProcessServiceImports.ts: -------------------------------------------------------------------------------- 1 | import type { Service } from '../client/interfaces/Service'; 2 | import { sort } from './sort'; 3 | import { unique } from './unique'; 4 | 5 | /** 6 | * Set unique imports, sorted by name 7 | * @param service 8 | */ 9 | export const postProcessServiceImports = (service: Service): string[] => { 10 | return service.imports.filter(unique).sort(sort); 11 | }; 12 | -------------------------------------------------------------------------------- /src/utils/postProcessServiceOperations.ts: -------------------------------------------------------------------------------- 1 | import type { Operation } from '../client/interfaces/Operation'; 2 | import type { Service } from '../client/interfaces/Service'; 3 | import { flatMap } from './flatMap'; 4 | 5 | export const postProcessServiceOperations = (service: Service): Operation[] => { 6 | const names = new Map(); 7 | 8 | return service.operations.map(operation => { 9 | const clone = { ...operation }; 10 | 11 | // Parse the service parameters and results, very similar to how we parse 12 | // properties of models. These methods will extend the type if needed. 13 | clone.imports.push(...flatMap(clone.parameters, parameter => parameter.imports)); 14 | clone.imports.push(...flatMap(clone.results, result => result.imports)); 15 | 16 | // Check if the operation name is unique, if not then prefix this with a number 17 | const name = clone.name; 18 | const index = names.get(name) || 0; 19 | if (index > 0) { 20 | clone.name = `${name}${index}`; 21 | } 22 | names.set(name, index + 1); 23 | 24 | return clone; 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /src/utils/readSpec.ts: -------------------------------------------------------------------------------- 1 | import { readSpecFromDisk } from './readSpecFromDisk'; 2 | import { readSpecFromHttp } from './readSpecFromHttp'; 3 | import { readSpecFromHttps } from './readSpecFromHttps'; 4 | 5 | export const readSpec = async (input: string): Promise => { 6 | if (input.startsWith('https://')) { 7 | return await readSpecFromHttps(input); 8 | } 9 | if (input.startsWith('http://')) { 10 | return await readSpecFromHttp(input); 11 | } 12 | return await readSpecFromDisk(input); 13 | }; 14 | -------------------------------------------------------------------------------- /src/utils/readSpecFromDisk.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | 3 | import { exists, readFile } from './fileSystem'; 4 | 5 | /** 6 | * Check if given file exists and try to read the content as string. 7 | * @param input 8 | */ 9 | export const readSpecFromDisk = async (input: string): Promise => { 10 | const filePath = resolve(process.cwd(), input); 11 | const fileExists = await exists(filePath); 12 | if (fileExists) { 13 | try { 14 | const content = await readFile(filePath, 'utf8'); 15 | return content.toString(); 16 | } catch (e) { 17 | throw new Error(`Could not read OpenApi spec: "${filePath}"`); 18 | } 19 | } 20 | throw new Error(`Could not find OpenApi spec: "${filePath}"`); 21 | }; 22 | -------------------------------------------------------------------------------- /src/utils/readSpecFromHttp.ts: -------------------------------------------------------------------------------- 1 | import { get } from 'http'; 2 | 3 | /** 4 | * Download the spec file from a HTTP resource 5 | * @param url 6 | */ 7 | export const readSpecFromHttp = async (url: string): Promise => { 8 | return new Promise((resolve, reject) => { 9 | get(url, response => { 10 | let body = ''; 11 | response.on('data', chunk => { 12 | body += chunk; 13 | }); 14 | response.on('end', () => { 15 | resolve(body); 16 | }); 17 | response.on('error', () => { 18 | reject(`Could not read OpenApi spec: "${url}"`); 19 | }); 20 | }); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /src/utils/readSpecFromHttps.ts: -------------------------------------------------------------------------------- 1 | import { get } from 'https'; 2 | 3 | /** 4 | * Download the spec file from a HTTPS resource 5 | * @param url 6 | */ 7 | export const readSpecFromHttps = async (url: string): Promise => { 8 | return new Promise((resolve, reject) => { 9 | get(url, response => { 10 | let body = ''; 11 | response.on('data', chunk => { 12 | body += chunk; 13 | }); 14 | response.on('end', () => { 15 | resolve(body); 16 | }); 17 | response.on('error', () => { 18 | reject(`Could not read OpenApi spec: "${url}"`); 19 | }); 20 | }); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /src/utils/registerHandlebarHelpers.spec.ts: -------------------------------------------------------------------------------- 1 | import Handlebars from 'handlebars/runtime'; 2 | 3 | import { HttpClient } from '../HttpClient'; 4 | import { registerHandlebarHelpers } from './registerHandlebarHelpers'; 5 | 6 | describe('registerHandlebarHelpers', () => { 7 | it('should register the helpers', () => { 8 | registerHandlebarHelpers({ 9 | httpClient: HttpClient.FETCH, 10 | useOptions: false, 11 | useUnionTypes: false, 12 | }); 13 | const helpers = Object.keys(Handlebars.helpers); 14 | expect(helpers).toContain('ifdef'); 15 | expect(helpers).toContain('equals'); 16 | expect(helpers).toContain('notEquals'); 17 | expect(helpers).toContain('containsSpaces'); 18 | expect(helpers).toContain('union'); 19 | expect(helpers).toContain('intersection'); 20 | expect(helpers).toContain('enumerator'); 21 | expect(helpers).toContain('escapeComment'); 22 | expect(helpers).toContain('escapeDescription'); 23 | expect(helpers).toContain('camelCase'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/utils/registerHandlebarTemplates.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '../HttpClient'; 2 | import { registerHandlebarTemplates } from './registerHandlebarTemplates'; 3 | 4 | describe('registerHandlebarTemplates', () => { 5 | it('should return correct templates', () => { 6 | const templates = registerHandlebarTemplates({ 7 | httpClient: HttpClient.FETCH, 8 | useOptions: false, 9 | useUnionTypes: false, 10 | }); 11 | expect(templates.index).toBeDefined(); 12 | expect(templates.exports.model).toBeDefined(); 13 | expect(templates.exports.schema).toBeDefined(); 14 | expect(templates.exports.service).toBeDefined(); 15 | expect(templates.core.settings).toBeDefined(); 16 | expect(templates.core.apiError).toBeDefined(); 17 | expect(templates.core.apiRequestOptions).toBeDefined(); 18 | expect(templates.core.apiResult).toBeDefined(); 19 | expect(templates.core.request).toBeDefined(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/utils/reservedWords.ts: -------------------------------------------------------------------------------- 1 | export const reservedWords = 2 | /^(arguments|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|eval|export|extends|false|finally|for|function|if|implements|import|in|instanceof|interface|let|new|null|package|private|protected|public|return|static|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)$/g; 3 | -------------------------------------------------------------------------------- /src/utils/sort.spec.ts: -------------------------------------------------------------------------------- 1 | import { sort } from './sort'; 2 | 3 | describe('sort', () => { 4 | it('should return correct index', () => { 5 | expect(sort('a', 'b')).toEqual(-1); 6 | expect(sort('b', 'a')).toEqual(1); 7 | expect(sort('a', 'a')).toEqual(0); 8 | expect(sort('', '')).toEqual(0); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/utils/sort.ts: -------------------------------------------------------------------------------- 1 | export const sort = (a: string, b: string): number => { 2 | const nameA = a.toLowerCase(); 3 | const nameB = b.toLowerCase(); 4 | return nameA.localeCompare(nameB, 'en'); 5 | }; 6 | -------------------------------------------------------------------------------- /src/utils/sortModelsByName.ts: -------------------------------------------------------------------------------- 1 | import type { Model } from '../client/interfaces/Model'; 2 | 3 | export const sortModelsByName = (models: Model[]): Model[] => { 4 | return models.sort((a, b) => { 5 | const nameA = a.name.toLowerCase(); 6 | const nameB = b.name.toLowerCase(); 7 | return nameA.localeCompare(nameB, 'en'); 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /src/utils/sortServicesByName.spec.ts: -------------------------------------------------------------------------------- 1 | import type { Service } from '../client/interfaces/Service'; 2 | import { sortServicesByName } from './sortServicesByName'; 3 | 4 | describe('sortServicesByName', () => { 5 | it('should return sorted list', () => { 6 | const john: Service = { 7 | name: 'John', 8 | operations: [], 9 | imports: [], 10 | }; 11 | const jane: Service = { 12 | name: 'Jane', 13 | operations: [], 14 | imports: [], 15 | }; 16 | const doe: Service = { 17 | name: 'Doe', 18 | operations: [], 19 | imports: [], 20 | }; 21 | 22 | const services: Service[] = [john, jane, doe]; 23 | 24 | expect(sortServicesByName([])).toEqual([]); 25 | expect(sortServicesByName(services)).toEqual([doe, jane, john]); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/utils/sortServicesByName.ts: -------------------------------------------------------------------------------- 1 | import type { Service } from '../client/interfaces/Service'; 2 | 3 | export const sortServicesByName = (services: Service[]): Service[] => { 4 | return services.sort((a, b) => { 5 | const nameA = a.name.toLowerCase(); 6 | const nameB = b.name.toLowerCase(); 7 | return nameA.localeCompare(nameB, 'en'); 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /src/utils/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface Dictionary { 2 | [key: string]: T; 3 | } 4 | -------------------------------------------------------------------------------- /src/utils/unique.spec.ts: -------------------------------------------------------------------------------- 1 | import { unique } from './unique'; 2 | 3 | describe('unique', () => { 4 | it('should return correct index', () => { 5 | expect(unique('a', 0, ['a', 'b', 'c'])).toBeTruthy(); 6 | expect(unique('a', 1, ['a', 'b', 'c'])).toBeFalsy(); 7 | expect(unique('a', 2, ['a', 'b', 'c'])).toBeFalsy(); 8 | expect(unique('a', 0, ['a', 'b', 'c'])).toBeTruthy(); 9 | expect(unique('a', 1, ['z', 'a', 'b'])).toBeTruthy(); 10 | expect(unique('a', 2, ['y', 'z', 'a'])).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/utils/unique.ts: -------------------------------------------------------------------------------- 1 | export const unique = (val: T, index: number, arr: T[]): boolean => { 2 | return arr.indexOf(val) === index; 3 | }; 4 | -------------------------------------------------------------------------------- /src/utils/writeClientClass.spec.ts: -------------------------------------------------------------------------------- 1 | import type { Client } from '../client/interfaces/Client'; 2 | import { HttpClient } from '../HttpClient'; 3 | import { Indent } from '../Indent'; 4 | import { writeFile } from './fileSystem'; 5 | import type { Templates } from './registerHandlebarTemplates'; 6 | import { writeClientClass } from './writeClientClass'; 7 | 8 | jest.mock('./fileSystem'); 9 | 10 | describe('writeClientClass', () => { 11 | it('should write to filesystem', async () => { 12 | const client: Client = { 13 | server: 'http://localhost:8080', 14 | version: 'v1', 15 | models: [], 16 | services: [], 17 | }; 18 | 19 | const templates: Templates = { 20 | index: () => 'index', 21 | client: () => 'client', 22 | exports: { 23 | model: () => 'model', 24 | schema: () => 'schema', 25 | service: () => 'service', 26 | }, 27 | core: { 28 | settings: () => 'settings', 29 | apiError: () => 'apiError', 30 | apiRequestOptions: () => 'apiRequestOptions', 31 | apiResult: () => 'apiResult', 32 | cancelablePromise: () => 'cancelablePromise', 33 | request: () => 'request', 34 | baseHttpRequest: () => 'baseHttpRequest', 35 | httpRequest: () => 'httpRequest', 36 | }, 37 | }; 38 | 39 | await writeClientClass(client, templates, './dist', HttpClient.FETCH, 'AppClient', Indent.SPACE_4, ''); 40 | 41 | expect(writeFile).toBeCalled(); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/utils/writeClientIndex.spec.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | 3 | import type { Client } from '../client/interfaces/Client'; 4 | import { writeFile } from './fileSystem'; 5 | import type { Templates } from './registerHandlebarTemplates'; 6 | import { writeClientIndex } from './writeClientIndex'; 7 | 8 | jest.mock('./fileSystem'); 9 | 10 | describe('writeClientIndex', () => { 11 | it('should write to filesystem', async () => { 12 | const client: Client = { 13 | server: 'http://localhost:8080', 14 | version: '1.0', 15 | models: [], 16 | services: [], 17 | }; 18 | 19 | const templates: Templates = { 20 | index: () => 'index', 21 | client: () => 'client', 22 | exports: { 23 | model: () => 'model', 24 | schema: () => 'schema', 25 | service: () => 'service', 26 | }, 27 | core: { 28 | settings: () => 'settings', 29 | apiError: () => 'apiError', 30 | apiRequestOptions: () => 'apiRequestOptions', 31 | apiResult: () => 'apiResult', 32 | cancelablePromise: () => 'cancelablePromise', 33 | request: () => 'request', 34 | baseHttpRequest: () => 'baseHttpRequest', 35 | httpRequest: () => 'httpRequest', 36 | }, 37 | }; 38 | 39 | await writeClientIndex(client, templates, '/', true, true, true, true, true, 'Service', ''); 40 | 41 | expect(writeFile).toBeCalledWith(resolve('/', '/index.ts'), 'index'); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/utils/writeClientModels.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | 3 | import type { Model } from '../client/interfaces/Model'; 4 | import type { HttpClient } from '../HttpClient'; 5 | import type { Indent } from '../Indent'; 6 | import { writeFile } from './fileSystem'; 7 | import { formatCode as f } from './formatCode'; 8 | import { formatIndentation as i } from './formatIndentation'; 9 | import type { Templates } from './registerHandlebarTemplates'; 10 | 11 | /** 12 | * Generate Models using the Handlebar template and write to disk. 13 | * @param models Array of Models to write 14 | * @param templates The loaded handlebar templates 15 | * @param outputPath Directory to write the generated files to 16 | * @param httpClient The selected httpClient (fetch, xhr, node or axios) 17 | * @param useUnionTypes Use union types instead of enums 18 | * @param indent Indentation options (4, 2 or tab) 19 | */ 20 | export const writeClientModels = async ( 21 | models: Model[], 22 | templates: Templates, 23 | outputPath: string, 24 | httpClient: HttpClient, 25 | useUnionTypes: boolean, 26 | indent: Indent 27 | ): Promise => { 28 | for (const model of models) { 29 | const file = resolve(outputPath, `${model.name}.ts`); 30 | const templateResult = templates.exports.model({ 31 | ...model, 32 | httpClient, 33 | useUnionTypes, 34 | }); 35 | await writeFile(file, i(f(templateResult), indent)); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /src/utils/writeClientSchemas.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | 3 | import type { Model } from '../client/interfaces/Model'; 4 | import type { HttpClient } from '../HttpClient'; 5 | import type { Indent } from '../Indent'; 6 | import { writeFile } from './fileSystem'; 7 | import { formatCode as f } from './formatCode'; 8 | import { formatIndentation as i } from './formatIndentation'; 9 | import type { Templates } from './registerHandlebarTemplates'; 10 | 11 | /** 12 | * Generate Schemas using the Handlebar template and write to disk. 13 | * @param models Array of Models to write 14 | * @param templates The loaded handlebar templates 15 | * @param outputPath Directory to write the generated files to 16 | * @param httpClient The selected httpClient (fetch, xhr, node or axios) 17 | * @param useUnionTypes Use union types instead of enums 18 | * @param indent Indentation options (4, 2 or tab) 19 | */ 20 | export const writeClientSchemas = async ( 21 | models: Model[], 22 | templates: Templates, 23 | outputPath: string, 24 | httpClient: HttpClient, 25 | useUnionTypes: boolean, 26 | indent: Indent 27 | ): Promise => { 28 | for (const model of models) { 29 | const file = resolve(outputPath, `$${model.name}.ts`); 30 | const templateResult = templates.exports.schema({ 31 | ...model, 32 | httpClient, 33 | useUnionTypes, 34 | }); 35 | await writeFile(file, i(f(templateResult), indent)); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /src/utils/writeClientServices.spec.ts: -------------------------------------------------------------------------------- 1 | import { EOL } from 'os'; 2 | import { resolve } from 'path'; 3 | 4 | import type { Service } from '../client/interfaces/Service'; 5 | import { HttpClient } from '../HttpClient'; 6 | import { Indent } from '../Indent'; 7 | import { writeFile } from './fileSystem'; 8 | import type { Templates } from './registerHandlebarTemplates'; 9 | import { writeClientServices } from './writeClientServices'; 10 | 11 | jest.mock('./fileSystem'); 12 | 13 | describe('writeClientServices', () => { 14 | it('should write to filesystem', async () => { 15 | const services: Service[] = [ 16 | { 17 | name: 'User', 18 | operations: [], 19 | imports: [], 20 | }, 21 | ]; 22 | 23 | const templates: Templates = { 24 | index: () => 'index', 25 | client: () => 'client', 26 | exports: { 27 | model: () => 'model', 28 | schema: () => 'schema', 29 | service: () => 'service', 30 | }, 31 | core: { 32 | settings: () => 'settings', 33 | apiError: () => 'apiError', 34 | apiRequestOptions: () => 'apiRequestOptions', 35 | apiResult: () => 'apiResult', 36 | cancelablePromise: () => 'cancelablePromise', 37 | request: () => 'request', 38 | baseHttpRequest: () => 'baseHttpRequest', 39 | httpRequest: () => 'httpRequest', 40 | }, 41 | }; 42 | 43 | await writeClientServices(services, templates, '/', HttpClient.FETCH, false, false, Indent.SPACE_4, 'Service'); 44 | 45 | expect(writeFile).toBeCalledWith(resolve('/', '/UserService.ts'), `service${EOL}`); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/custom/request.ts: -------------------------------------------------------------------------------- 1 | import type { ApiRequestOptions } from './ApiRequestOptions'; 2 | import { CancelablePromise } from './CancelablePromise'; 3 | import type { OpenAPIConfig } from './OpenAPI'; 4 | 5 | export const request = (config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise => { 6 | return new CancelablePromise((resolve, reject, onCancel) => { 7 | const url = `${config.BASE}${options.path}`.replace('{api-version}', config.VERSION); 8 | 9 | try { 10 | // Do your request... 11 | const timeout = setTimeout(() => { 12 | resolve({ 13 | url, 14 | ok: true, 15 | status: 200, 16 | statusText: 'dummy', 17 | body: { 18 | ...options, 19 | }, 20 | }); 21 | }, 500); 22 | 23 | // Cancel your request... 24 | onCancel(() => { 25 | clearTimeout(timeout); 26 | }); 27 | } catch (e) { 28 | reject(e); 29 | } 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /test/e2e/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /test/e2e/assets/main.ts: -------------------------------------------------------------------------------- 1 | import * as api from './index'; 2 | 3 | (window as any).api = api; 4 | -------------------------------------------------------------------------------- /test/e2e/scripts/browser.ts: -------------------------------------------------------------------------------- 1 | import puppeteer, { Browser, EvaluateFn, Page } from 'puppeteer'; 2 | 3 | let _browser: Browser; 4 | let _page: Page; 5 | 6 | const start = async () => { 7 | // This starts a new puppeteer browser (Chrome) 8 | // and load the localhost page, this page will load the 9 | // javascript modules (see server.js for more info) 10 | _browser = await puppeteer.launch({ 11 | headless: 'new', 12 | args: ['--no-sandbox', '--disable-setuid-sandbox'], 13 | }); 14 | _page = await _browser.newPage(); 15 | // _page.on('console', msg => console.log(msg.text())); 16 | await _page.goto(`http://localhost:3000/`, { 17 | waitUntil: 'networkidle0', 18 | }); 19 | }; 20 | 21 | const stop = async () => { 22 | await _page.close(); 23 | await _browser.close(); 24 | }; 25 | 26 | const evaluate = async (fn: EvaluateFn) => { 27 | return await _page.evaluate(fn); 28 | }; 29 | 30 | // eslint-disable-next-line @typescript-eslint/ban-types 31 | const exposeFunction = async (name: string, fn: Function) => { 32 | return await _page.exposeFunction(name, fn); 33 | }; 34 | 35 | export default { 36 | start, 37 | stop, 38 | evaluate, 39 | exposeFunction, 40 | }; 41 | -------------------------------------------------------------------------------- /test/e2e/scripts/buildAngularProject.ts: -------------------------------------------------------------------------------- 1 | import { sync } from 'cross-spawn'; 2 | import { resolve as resolvePath } from 'path'; 3 | 4 | export const buildAngularProject = (dir: string, name: string, output: string) => { 5 | const cwd = `./test/e2e/generated/${dir}/${name}/`; 6 | sync( 7 | 'ng', 8 | [ 9 | 'build', 10 | '--output-path', 11 | output, 12 | '--optimization', 13 | 'false', 14 | '--configuration', 15 | 'development', 16 | '--source-map', 17 | 'false', 18 | ], 19 | { 20 | cwd: resolvePath(cwd), 21 | stdio: 'inherit', 22 | } 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /test/e2e/scripts/cleanup.ts: -------------------------------------------------------------------------------- 1 | import { rmSync } from 'fs'; 2 | 3 | export const cleanup = (dir: string) => { 4 | rmSync(`./test/e2e/generated/${dir}/`, { 5 | force: true, 6 | recursive: true, 7 | }); 8 | }; 9 | -------------------------------------------------------------------------------- /test/e2e/scripts/compileWithBabel.ts: -------------------------------------------------------------------------------- 1 | import { transformSync } from '@babel/core'; 2 | import { readFileSync, writeFileSync } from 'fs'; 3 | import { sync } from 'glob'; 4 | 5 | export const compileWithBabel = (dir: string) => { 6 | sync(`./test/e2e/generated/${dir}/**/*.ts`).forEach(file => { 7 | try { 8 | const content = readFileSync(file, 'utf8').toString(); 9 | const result = transformSync(content, { 10 | filename: file, 11 | presets: [ 12 | [ 13 | '@babel/preset-env', 14 | { 15 | modules: false, 16 | targets: { 17 | node: true, 18 | }, 19 | }, 20 | ], 21 | [ 22 | '@babel/preset-typescript', 23 | { 24 | onlyRemoveTypeImports: true, 25 | }, 26 | ], 27 | ], 28 | }); 29 | if (result?.code) { 30 | const out = file.replace(/\.ts$/, '.js'); 31 | writeFileSync(out, result.code); 32 | } 33 | } catch (error) { 34 | console.error(error); 35 | } 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /test/e2e/scripts/copyAsset.ts: -------------------------------------------------------------------------------- 1 | import { copyFileSync } from 'fs'; 2 | 3 | export const copyAsset = (fileNameIn: string, fileNameOut: string) => { 4 | copyFileSync(`./test/e2e/assets/${fileNameIn}`, `./test/e2e/generated/${fileNameOut}`); 5 | }; 6 | -------------------------------------------------------------------------------- /test/e2e/scripts/createAngularProject.ts: -------------------------------------------------------------------------------- 1 | import { sync } from 'cross-spawn'; 2 | import { mkdirSync, rmSync } from 'fs'; 3 | import { resolve as resolvePath } from 'path'; 4 | 5 | export const createAngularProject = (dir: string, name: string) => { 6 | const cwd = `./test/e2e/generated/${dir}/`; 7 | mkdirSync(cwd, { 8 | recursive: true, 9 | }); 10 | 11 | sync('ng', ['analytics', 'off', '--global'], { 12 | cwd: resolvePath(cwd), 13 | stdio: 'inherit', 14 | }); 15 | 16 | sync( 17 | 'ng', 18 | [ 19 | 'new', 20 | name, 21 | '--minimal', 22 | 'true', 23 | '--style', 24 | 'css', 25 | '--inline-style', 26 | 'true', 27 | '--inline-template', 28 | 'true', 29 | '--routing', 30 | 'false', 31 | '--ssr', 32 | 'false', 33 | '--skip-tests', 34 | 'true', 35 | '--skip-install', 36 | 'true', 37 | '--skip-git', 38 | 'true', 39 | '--commit', 40 | 'false', 41 | '--force', 42 | ], 43 | { 44 | cwd: resolvePath(cwd), 45 | stdio: 'inherit', 46 | } 47 | ); 48 | rmSync(`${cwd}/${name}/src/app/`, { 49 | recursive: true, 50 | }); 51 | }; 52 | -------------------------------------------------------------------------------- /test/e2e/scripts/generateClient.ts: -------------------------------------------------------------------------------- 1 | import { generate as __generate } from '../../../'; 2 | 3 | export const generateClient = async ( 4 | dir: string, 5 | version: string, 6 | client: 'fetch' | 'xhr' | 'node' | 'axios' | 'angular', 7 | useOptions: boolean = false, 8 | useUnionTypes: boolean = false, 9 | clientName?: string 10 | ) => { 11 | await __generate({ 12 | input: `./test/spec/${version}.json`, 13 | output: `./test/e2e/generated/${dir}/`, 14 | httpClient: client, 15 | useOptions, 16 | useUnionTypes, 17 | clientName, 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /test/e2e/v2.axios.spec.ts: -------------------------------------------------------------------------------- 1 | import { cleanup } from './scripts/cleanup'; 2 | import { compileWithTypescript } from './scripts/compileWithTypescript'; 3 | import { generateClient } from './scripts/generateClient'; 4 | import server from './scripts/server'; 5 | 6 | describe('v2.axios', () => { 7 | beforeAll(async () => { 8 | cleanup('v2/axios'); 9 | await generateClient('v2/axios', 'v2', 'axios'); 10 | compileWithTypescript('v2/axios'); 11 | await server.start('v2/axios'); 12 | }, 30000); 13 | 14 | afterAll(async () => { 15 | await server.stop(); 16 | }); 17 | 18 | it('requests token', async () => { 19 | const { OpenAPI, SimpleService } = require('./generated/v2/axios/index.js'); 20 | const tokenRequest = jest.fn().mockResolvedValue('MY_TOKEN'); 21 | OpenAPI.TOKEN = tokenRequest; 22 | const result = await SimpleService.getCallWithoutParametersAndResponse(); 23 | expect(tokenRequest.mock.calls.length).toBe(1); 24 | expect(result.headers.authorization).toBe('Bearer MY_TOKEN'); 25 | }); 26 | 27 | it('supports complex params', async () => { 28 | const { ComplexService } = require('./generated/v2/axios/index.js'); 29 | const result = await ComplexService.complexTypes({ 30 | first: { 31 | second: { 32 | third: 'Hello World!', 33 | }, 34 | }, 35 | }); 36 | expect(result).toBeDefined(); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | import { sync } from 'glob'; 3 | 4 | import { generate, HttpClient } from '../'; 5 | 6 | describe('v2', () => { 7 | it('should generate', async () => { 8 | await generate({ 9 | input: './test/spec/v2.json', 10 | output: './test/generated/v2/', 11 | httpClient: HttpClient.FETCH, 12 | useOptions: false, 13 | useUnionTypes: false, 14 | exportCore: true, 15 | exportSchemas: true, 16 | exportModels: true, 17 | exportServices: true, 18 | }); 19 | 20 | sync('./test/generated/v2/**/*.ts').forEach(file => { 21 | const content = readFileSync(file, 'utf8').toString(); 22 | expect(content).toMatchSnapshot(file); 23 | }); 24 | }); 25 | }); 26 | 27 | describe('v3', () => { 28 | it('should generate', async () => { 29 | await generate({ 30 | input: './test/spec/v3.json', 31 | output: './test/generated/v3/', 32 | httpClient: HttpClient.FETCH, 33 | useOptions: false, 34 | useUnionTypes: false, 35 | exportCore: true, 36 | exportSchemas: true, 37 | exportModels: true, 38 | exportServices: true, 39 | }); 40 | 41 | sync('./test/generated/v3/**/*.ts').forEach(file => { 42 | const content = readFileSync(file, 'utf8').toString(); 43 | expect(content).toMatchSnapshot(file); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "target": "es2019", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "lib": ["es2019", "dom"], 8 | "types": ["jest", "node"], 9 | "declaration": false, 10 | "declarationMap": false, 11 | "sourceMap": false, 12 | "noImplicitReturns": true, 13 | "noImplicitThis": true, 14 | "noImplicitAny": true, 15 | "strict": true, 16 | "skipLibCheck": true, 17 | "allowSyntheticDefaultImports": true, 18 | "experimentalDecorators": true 19 | }, 20 | 21 | "files": [ 22 | "./src/typings/hbs.d.ts" 23 | ], 24 | 25 | "include": [ 26 | "./src/**/*.ts" 27 | ], 28 | 29 | "exclude": [ 30 | "node_modules", 31 | "**/__mocks__" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | export declare enum HttpClient { 2 | FETCH = 'fetch', 3 | XHR = 'xhr', 4 | NODE = 'node', 5 | AXIOS = 'axios', 6 | ANGULAR = 'angular', 7 | } 8 | 9 | export declare enum Indent { 10 | SPACE_4 = '4', 11 | SPACE_2 = '2', 12 | TAB = 'tab', 13 | } 14 | 15 | export type Options = { 16 | input: string | Record; 17 | output: string; 18 | httpClient?: HttpClient | 'fetch' | 'xhr' | 'node' | 'axios' | 'angular'; 19 | clientName?: string; 20 | useOptions?: boolean; 21 | useUnionTypes?: boolean; 22 | exportCore?: boolean; 23 | exportServices?: boolean; 24 | exportModels?: boolean; 25 | exportSchemas?: boolean; 26 | indent?: Indent | '4' | '2' | 'tab'; 27 | postfixServices?: string; 28 | postfixModels?: string; 29 | request?: string; 30 | write?: boolean; 31 | }; 32 | 33 | export declare function generate(options: Options): Promise; 34 | 35 | declare type OpenAPI = { 36 | HttpClient: HttpClient; 37 | Indent: Indent; 38 | generate: typeof generate; 39 | }; 40 | 41 | export default OpenAPI; 42 | --------------------------------------------------------------------------------