From fc1da2c2389458817c9c27db98867103e34c1b02 Mon Sep 17 00:00:00 2001 From: Kavalar Date: Fri, 24 May 2024 15:27:07 +0300 Subject: [PATCH] api --- backend/modules/api/views/categoty/index.php | 9 + common/behaviors/GsCors.php | 22 + common/models/Product.php | 22 +- common/services/CompanyService.php | 11 + common/services/ProductService.php | 30 + composer.json | 4 +- composer.lock | 644 +- console/controllers/SwaggerController.php | 26 + ...dd_category_id_column_at_product_table.php | 62 + frontend/assets/AppAsset.php | 1 + frontend/assets/MainAsset.php | 16 + frontend/config/main.php | 3 + frontend/modules/api/Api.php | 24 + .../modules/api/controllers/ApiController.php | 80 + .../api/controllers/CategoryController.php | 82 + .../api/controllers/DefaultController.php | 20 + .../api/controllers/ProductController.php | 114 + frontend/modules/api/views/default/index.php | 12 + .../modules/product/models/ProductSearch.php | 4 +- .../modules/product/views/product/_form.php | 4 +- .../modules/product/views/product/view.php | 4 + .../controllers/ProductCategoryController.php | 19 + .../product-category/_category_select.php | 15 + .../views/product-category/view.php | 5 +- frontend/views/layouts/main.php | 1 + frontend/web/api-doc/.agignore | 1 + frontend/web/api-doc/.commitlintrc.json | 25 + frontend/web/api-doc/.dockerignore | 8 + frontend/web/api-doc/.editorconfig | 10 + frontend/web/api-doc/.eslintignore | 2 + frontend/web/api-doc/.eslintrc | 40 + frontend/web/api-doc/.gitattributes | 1 + .../.github/ISSUE_TEMPLATE/Bug_report.md | 73 + .../.github/ISSUE_TEMPLATE/Feature_request.md | 42 + .../api-doc/.github/ISSUE_TEMPLATE/Support.md | 46 + frontend/web/api-doc/.github/dependabot.yaml | 23 + frontend/web/api-doc/.github/lock.yml | 15 + .../api-doc/.github/pull_request_template.md | 55 + .../.github/workflows/dependabot-merge.yml | 37 + .../.github/workflows/docker-image-check.yml | 20 + .../web/api-doc/.github/workflows/nodejs.yml | 83 + .../workflows/release-swagger-ui-react.yml | 79 + .../.github/workflows/release-swagger-ui.yml | 73 + frontend/web/api-doc/.gitignore | 28 + frontend/web/api-doc/.husky/commit-msg | 4 + frontend/web/api-doc/.husky/pre-commit | 4 + frontend/web/api-doc/.lintstagedrc | 3 + frontend/web/api-doc/.mocharc.yaml | 2 + frontend/web/api-doc/.npmignore | 16 + frontend/web/api-doc/.npmrc | 1 + frontend/web/api-doc/.nvmrc | 1 + frontend/web/api-doc/.prettierrc.yaml | 5 + frontend/web/api-doc/.releaserc | 35 + frontend/web/api-doc/Dockerfile | 26 + frontend/web/api-doc/LICENSE | 202 + frontend/web/api-doc/NOTICE | 2 + frontend/web/api-doc/README.md | 98 + frontend/web/api-doc/SECURITY.md | 22 + frontend/web/api-doc/babel.config.js | 167 + frontend/web/api-doc/composer.json | 40 + frontend/web/api-doc/config/.eslintrc | 8 + .../config/jest/jest.artifact.config.js | 8 + .../api-doc/config/jest/jest.unit.config.js | 23 + frontend/web/api-doc/cypress.json | 11 + .../dev-helpers/dev-helper-initializer.js | 33 + frontend/web/api-doc/dev-helpers/index.html | 21 + .../api-doc/dev-helpers/oauth2-redirect.html | 76 + frontend/web/api-doc/dev-helpers/style.css | 19 + .../web/api-doc/dist/definitions/request.json | 16 + frontend/web/api-doc/dist/favicon-16x16.png | Bin 0 -> 665 bytes frontend/web/api-doc/dist/favicon-32x32.png | Bin 0 -> 628 bytes frontend/web/api-doc/dist/index.css | 16 + frontend/web/api-doc/dist/index.html | 19 + .../web/api-doc/dist/oauth2-redirect.html | 79 + frontend/web/api-doc/dist/swagger-config.yaml | 4 + .../web/api-doc/dist/swagger-initializer.js | 20 + .../web/api-doc/dist/swagger-ui-bundle.js | 3 + .../dist/swagger-ui-bundle.js.LICENSE.txt | 144 + .../web/api-doc/dist/swagger-ui-bundle.js.map | 1 + .../api-doc/dist/swagger-ui-es-bundle-core.js | 3 + .../swagger-ui-es-bundle-core.js.LICENSE.txt | 35 + .../dist/swagger-ui-es-bundle-core.js.map | 1 + .../web/api-doc/dist/swagger-ui-es-bundle.js | 3 + .../dist/swagger-ui-es-bundle.js.LICENSE.txt | 144 + .../api-doc/dist/swagger-ui-es-bundle.js.map | 1 + .../dist/swagger-ui-standalone-preset.js | 3 + ...wagger-ui-standalone-preset.js.LICENSE.txt | 27 + .../dist/swagger-ui-standalone-preset.js.map | 1 + frontend/web/api-doc/dist/swagger-ui.css | 3 + frontend/web/api-doc/dist/swagger-ui.css.map | 1 + frontend/web/api-doc/dist/swagger-ui.js | 2 + frontend/web/api-doc/dist/swagger-ui.js.map | 1 + frontend/web/api-doc/dist/swagger.yaml | 79 + .../api-doc/docker/configurator/helpers.js | 13 + .../web/api-doc/docker/configurator/index.js | 52 + .../web/api-doc/docker/configurator/oauth.js | 55 + .../api-doc/docker/configurator/translator.js | 111 + .../api-doc/docker/configurator/variables.js | 125 + frontend/web/api-doc/docker/cors.conf | 14 + .../docker-entrypoint.d/40-swagger-ui.sh | 52 + frontend/web/api-doc/docker/nginx.conf | 56 + frontend/web/api-doc/docs/README.md | 24 + frontend/web/api-doc/docs/book.json | 3 + .../docs/customization/custom-layout.md | 92 + .../api-doc/docs/customization/overview.md | 71 + .../api-doc/docs/customization/plug-points.md | 415 + .../api-doc/docs/customization/plugin-api.md | 459 + .../web/api-doc/docs/development/scripts.md | 39 + .../api-doc/docs/development/setting-up.md | 37 + .../web/api-doc/docs/images/swagger-ui2.png | Bin 0 -> 99657 bytes .../web/api-doc/docs/images/swagger-ui3.png | Bin 0 -> 79791 bytes .../samples/webpack-getting-started/README.md | 14 + .../_sample_package.json | 26 + .../webpack-getting-started/index.html | 10 + .../webpack-getting-started/src/index.js | 15 + .../src/swagger-config.yaml | 30 + .../webpack-getting-started/webpack.config.js | 52 + .../web/api-doc/docs/usage/configuration.md | 391 + frontend/web/api-doc/docs/usage/cors.md | 60 + .../web/api-doc/docs/usage/deep-linking.md | 36 + .../web/api-doc/docs/usage/installation.md | 178 + .../web/api-doc/docs/usage/limitations.md | 38 + frontend/web/api-doc/docs/usage/oauth2.md | 31 + .../api-doc/docs/usage/version-detection.md | 54 + .../flavors/swagger-ui-react/README.md | 197 + .../flavors/swagger-ui-react/index.jsx | 176 + .../release/create-manifest.js | 9 + .../flavors/swagger-ui-react/release/run.sh | 38 + .../swagger-ui-react/release/template.json | 53 + frontend/web/api-doc/package-lock.json | 50828 ++++++++++++++++ frontend/web/api-doc/package.json | 193 + .../api-doc/patches/tachyons-sass+4.9.5.patch | 13 + frontend/web/api-doc/release/.release-it.json | 28 + .../release/check-for-breaking-changes.sh | 14 + frontend/web/api-doc/release/get-changelog.sh | 5 + frontend/web/api-doc/renovate.json | 45 + frontend/web/api-doc/snapcraft.yaml | 28 + frontend/web/api-doc/src/.eslintrc | 10 + .../api-doc/src/core/brace-snippets-yaml.js | 6 + .../web/api-doc/src/core/components/app.jsx | 28 + .../src/core/components/array-model.jsx | 80 + .../src/core/components/auth/api-key-auth.jsx | 84 + .../src/core/components/auth/auth-item.jsx | 56 + .../components/auth/authorization-popup.jsx | 60 + .../core/components/auth/authorize-btn.jsx | 30 + .../auth/authorize-operation-btn.jsx | 33 + .../src/core/components/auth/auths.jsx | 123 + .../src/core/components/auth/basic-auth.jsx | 94 + .../src/core/components/auth/error.jsx | 24 + .../src/core/components/auth/oauth2.jsx | 281 + .../web/api-doc/src/core/components/clear.jsx | 25 + .../src/core/components/content-type.jsx | 61 + .../core/components/copy-to-clipboard-btn.jsx | 26 + .../web/api-doc/src/core/components/curl.jsx | 44 + .../web/api-doc/src/core/components/debug.jsx | 50 + .../api-doc/src/core/components/deep-link.jsx | 20 + .../src/core/components/enum-model.jsx | 19 + .../api-doc/src/core/components/errors.jsx | 136 + .../api-doc/src/core/components/example.jsx | 43 + .../examples-select-value-retainer.jsx | 258 + .../src/core/components/examples-select.jsx | 139 + .../api-doc/src/core/components/execute.jsx | 103 + .../api-doc/src/core/components/footer.jsx | 9 + .../api-doc/src/core/components/headers.jsx | 58 + .../src/core/components/highlight-code.jsx | 89 + .../web/api-doc/src/core/components/info.jsx | 166 + .../src/core/components/initialized-input.jsx | 36 + .../src/core/components/jump-to-path.jsx | 9 + .../src/core/components/layout-utils.jsx | 263 + .../src/core/components/layouts/base.jsx | 124 + .../src/core/components/layouts/xpane.jsx | 73 + .../src/core/components/live-response.jsx | 132 + .../src/core/components/model-collapse.jsx | 99 + .../src/core/components/model-example.jsx | 142 + .../src/core/components/model-wrapper.jsx | 44 + .../web/api-doc/src/core/components/model.jsx | 114 + .../api-doc/src/core/components/models.jsx | 137 + .../src/core/components/object-model.jsx | 256 + .../components/online-validator-badge.jsx | 119 + .../components/operation-extension-row.jsx | 17 + .../core/components/operation-extensions.jsx | 35 + .../components/operation-summary-method.jsx | 25 + .../components/operation-summary-path.jsx | 54 + .../src/core/components/operation-summary.jsx | 106 + .../src/core/components/operation-tag.jsx | 122 + .../api-doc/src/core/components/operation.jsx | 259 + .../src/core/components/operations.jsx | 115 + .../api-doc/src/core/components/overview.jsx | 119 + .../src/core/components/param-body.jsx | 156 + .../core/components/parameter-extension.jsx | 12 + .../components/parameter-include-empty.jsx | 53 + .../src/core/components/parameter-row.jsx | 384 + .../src/core/components/parameters/index.js | 1 + .../core/components/parameters/parameters.jsx | 279 + .../src/core/components/primitive-model.jsx | 88 + .../api-doc/src/core/components/property.jsx | 16 + .../src/core/components/providers/README.md | 6 + .../core/components/providers/markdown.jsx | 76 + .../src/core/components/response-body.jsx | 169 + .../core/components/response-extension.jsx | 12 + .../api-doc/src/core/components/response.jsx | 276 + .../api-doc/src/core/components/responses.jsx | 169 + .../api-doc/src/core/components/schemes.jsx | 53 + .../src/core/components/svg-assets.jsx | 48 + .../src/core/components/system-wrapper.jsx | 0 .../src/core/components/try-it-out-button.jsx | 41 + .../core/components/version-pragma-filter.jsx | 54 + .../src/core/components/version-stamp.jsx | 12 + .../core/containers/OperationContainer.jsx | 253 + .../src/core/containers/authorize-btn.jsx | 31 + .../api-doc/src/core/containers/filter.jsx | 44 + .../web/api-doc/src/core/containers/info.jsx | 34 + .../api-doc/src/core/containers/schemes.jsx | 30 + frontend/web/api-doc/src/core/index.js | 225 + .../src/core/json-schema-components.jsx | 420 + .../web/api-doc/src/core/oauth2-authorize.js | 132 + frontend/web/api-doc/src/core/plugins/all.js | 24 + .../api-doc/src/core/plugins/auth/actions.js | 287 + .../api-doc/src/core/plugins/auth/index.js | 72 + .../api-doc/src/core/plugins/auth/reducers.js | 77 + .../src/core/plugins/auth/selectors.js | 113 + .../core/plugins/auth/spec-wrap-actions.js | 10 + .../src/core/plugins/configs/actions.js | 37 + .../src/core/plugins/configs/helpers.js | 12 + .../api-doc/src/core/plugins/configs/index.js | 30 + .../src/core/plugins/configs/reducers.js | 20 + .../src/core/plugins/configs/selectors.js | 4 + .../src/core/plugins/configs/spec-actions.js | 25 + .../src/core/plugins/deep-linking/README.md | 1 + .../src/core/plugins/deep-linking/helpers.js | 7 + .../src/core/plugins/deep-linking/index.js | 24 + .../src/core/plugins/deep-linking/layout.js | 209 + .../deep-linking/operation-tag-wrapper.jsx | 25 + .../deep-linking/operation-wrapper.jsx | 27 + .../api-doc/src/core/plugins/download-url.js | 110 + .../api-doc/src/core/plugins/err/actions.js | 60 + .../plugins/err/error-transformers/README.md | 31 + .../plugins/err/error-transformers/hook.js | 38 + .../transformers/not-of-type.js | 29 + .../transformers/parameter-oneof.js | 59 + .../web/api-doc/src/core/plugins/err/index.js | 15 + .../api-doc/src/core/plugins/err/reducers.js | 100 + .../api-doc/src/core/plugins/err/selectors.js | 15 + .../api-doc/src/core/plugins/filter/index.js | 9 + .../src/core/plugins/filter/opsFilter.js | 3 + .../src/core/plugins/layout/actions.js | 39 + .../api-doc/src/core/plugins/layout/index.js | 19 + .../src/core/plugins/layout/reducers.js | 32 + .../src/core/plugins/layout/selectors.js | 24 + .../layout/spec-extensions/wrap-selector.js | 22 + .../api-doc/src/core/plugins/logs/index.js | 28 + .../api-doc/src/core/plugins/oas3/actions.js | 99 + .../oas3/auth-extensions/wrap-selectors.js | 94 + .../plugins/oas3/components/callbacks.jsx | 56 + .../plugins/oas3/components/http-auth.jsx | 132 + .../src/core/plugins/oas3/components/index.js | 19 + .../oas3/components/operation-link.jsx | 43 + .../oas3/components/operation-servers.jsx | 102 + .../oas3/components/request-body-editor.jsx | 103 + .../plugins/oas3/components/request-body.jsx | 335 + .../oas3/components/servers-container.jsx | 33 + .../core/plugins/oas3/components/servers.jsx | 176 + .../api-doc/src/core/plugins/oas3/helpers.jsx | 40 + .../api-doc/src/core/plugins/oas3/index.js | 31 + .../api-doc/src/core/plugins/oas3/reducers.js | 113 + .../src/core/plugins/oas3/selectors.js | 260 + .../plugins/oas3/spec-extensions/selectors.js | 50 + .../oas3/spec-extensions/wrap-selectors.js | 85 + .../oas3/wrap-components/auth-item.jsx | 23 + .../plugins/oas3/wrap-components/index.js | 15 + .../wrap-components/json-schema-string.jsx | 27 + .../plugins/oas3/wrap-components/markdown.jsx | 49 + .../plugins/oas3/wrap-components/model.jsx | 40 + .../wrap-components/online-validator-badge.js | 5 + .../oas3/wrap-components/version-stamp.jsx | 13 + .../src/core/plugins/on-complete/index.js | 28 + .../src/core/plugins/request-snippets/fn.js | 160 + .../core/plugins/request-snippets/index.js | 16 + .../request-snippets/request-snippets.jsx | 159 + .../plugins/request-snippets/selectors.js | 45 + .../safe-render/components/error-boundary.jsx | 50 + .../safe-render/components/fallback.jsx | 13 + .../src/core/plugins/safe-render/fn.jsx | 32 + .../src/core/plugins/safe-render/index.js | 42 + .../api-doc/src/core/plugins/samples/fn.js | 636 + .../api-doc/src/core/plugins/samples/index.js | 5 + .../api-doc/src/core/plugins/spec/actions.js | 530 + .../api-doc/src/core/plugins/spec/index.js | 17 + .../api-doc/src/core/plugins/spec/reducers.js | 177 + .../src/core/plugins/spec/selectors.js | 538 + .../src/core/plugins/spec/wrap-actions.js | 38 + .../swagger-js/configs-wrap-actions.js | 8 + .../src/core/plugins/swagger-js/index.js | 39 + .../api-doc/src/core/plugins/util/index.js | 7 + .../web/api-doc/src/core/plugins/view/fn.js | 1 + .../api-doc/src/core/plugins/view/index.js | 34 + .../src/core/plugins/view/root-injects.jsx | 115 + frontend/web/api-doc/src/core/presets/apis.js | 12 + frontend/web/api-doc/src/core/presets/base.js | 202 + frontend/web/api-doc/src/core/proptypes.js | 16 + .../api-doc/src/core/syntax-highlighting.js | 38 + frontend/web/api-doc/src/core/system.js | 509 + frontend/web/api-doc/src/core/utils.js | 925 + .../web/api-doc/src/core/utils/jsonParse.js | 15 + frontend/web/api-doc/src/core/utils/url.js | 39 + frontend/web/api-doc/src/core/window.js | 29 + .../src/helpers/create-html-ready-id.js | 10 + .../src/helpers/get-parameter-schema.js | 92 + frontend/web/api-doc/src/helpers/memoizeN.js | 48 + frontend/web/api-doc/src/img/logo_small.png | Bin 0 -> 581 bytes frontend/web/api-doc/src/img/rolling-load.svg | 1 + frontend/web/api-doc/src/index.js | 3 + .../web/api-doc/src/plugins/add-plugin.md | 127 + frontend/web/api-doc/src/plugins/index.js | 7 + .../web/api-doc/src/plugins/topbar/index.js | 11 + .../web/api-doc/src/plugins/topbar/logo.jsx | 8 + .../api-doc/src/plugins/topbar/logo_small.svg | 60 + .../web/api-doc/src/plugins/topbar/topbar.jsx | 171 + frontend/web/api-doc/src/standalone/index.js | 24 + .../web/api-doc/src/standalone/layout.jsx | 38 + .../web/api-doc/src/style/_authorize.scss | 107 + frontend/web/api-doc/src/style/_buttons.scss | 214 + frontend/web/api-doc/src/style/_errors.scss | 83 + frontend/web/api-doc/src/style/_form.scss | 235 + .../web/api-doc/src/style/_information.scss | 104 + frontend/web/api-doc/src/style/_layout.scss | 1005 + frontend/web/api-doc/src/style/_markdown.scss | 29 + frontend/web/api-doc/src/style/_mixins.scss | 173 + frontend/web/api-doc/src/style/_modal.scss | 102 + frontend/web/api-doc/src/style/_models.scss | 366 + frontend/web/api-doc/src/style/_servers.scss | 66 + .../api-doc/src/style/_split-pane-mode.scss | 3 + frontend/web/api-doc/src/style/_table.scss | 206 + frontend/web/api-doc/src/style/_topbar.scss | 96 + frontend/web/api-doc/src/style/_type.scss | 21 + .../web/api-doc/src/style/_variables.scss | 229 + frontend/web/api-doc/src/style/main.scss | 21 + frontend/web/api-doc/swagger-config.yaml | 4 + .../swagger-ui-dist-package/.npmignore | 2 + .../api-doc/swagger-ui-dist-package/.npmrc | 1 + .../api-doc/swagger-ui-dist-package/README.md | 22 + .../swagger-ui-dist-package/absolute-path.js | 14 + .../api-doc/swagger-ui-dist-package/deploy.sh | 25 + .../api-doc/swagger-ui-dist-package/index.js | 17 + .../swagger-ui-dist-package/package.json | 18 + frontend/web/api-doc/swagger.yaml | 6 + frontend/web/api-doc/test/.eslintrc | 7 + .../api-doc/test/build-artifacts/.eslintrc | 14 + .../test/build-artifacts/es-bundle-core.js | 8 + .../api-doc/test/build-artifacts/es-bundle.js | 8 + .../web/api-doc/test/build-artifacts/umd.js | 8 + .../test/components/highlight-code.jsx | 30 + .../api-doc/test/components/response-body.jsx | 41 + .../web/api-doc/test/e2e-cypress/.eslintrc | 8 + .../test/e2e-cypress/fixtures/example.json | 5 + .../e2e-cypress/helpers/multiple-examples.js | 679 + .../helpers/oauth2-server/index.js | 50 + .../helpers/oauth2-server/model.js | 141 + .../helpers/oauth2-server/swagger.yaml | 36 + .../api-doc/test/e2e-cypress/plugins/index.js | 19 + .../static/configs/urls-primary-name.yaml | 6 + .../static/configs/urls-server-variables.yaml | 5 + .../test/e2e-cypress/static/configs/urls.yaml | 5 + .../static/documents/bugs/4442.yaml | 41 + .../static/documents/bugs/4641.yaml | 47 + .../static/documents/bugs/4838.yaml | 12 + .../static/documents/bugs/4867.yaml | 24 + .../static/documents/bugs/4943.yaml | 43 + .../static/documents/bugs/5043/status.yaml | 17 + .../static/documents/bugs/5043/swagger.yaml | 44 + .../static/documents/bugs/5060.yaml | 10 + .../documents/bugs/5072/additional.yaml | 35 + .../static/documents/bugs/5072/empty.yaml | 33 + .../static/documents/bugs/5129.yaml | 26 + .../static/documents/bugs/5164.yaml | 23 + .../static/documents/bugs/5181.yaml | 27 + .../static/documents/bugs/5188.yaml | 24 + .../static/documents/bugs/5452/openapi.yaml | 20 + .../static/documents/bugs/5452/swagger.yaml | 19 + .../static/documents/bugs/5453.yaml | 10 + .../static/documents/bugs/5455.yaml | 25 + .../static/documents/bugs/5458.yaml | 37 + .../static/documents/bugs/5660-model.yaml | 14 + .../static/documents/bugs/5660-property.yaml | 17 + .../static/documents/bugs/6016-oas2.yaml | 28 + .../static/documents/bugs/6016-oas3.yaml | 31 + .../static/documents/bugs/6158.yaml | 57 + .../static/documents/bugs/6183.yaml | 37 + .../static/documents/bugs/6351.yaml | 13 + .../documents/bugs/6369-oas2-display.yaml | 26 + .../documents/bugs/6369-oas2-no-display.yaml | 26 + .../documents/bugs/6369-oas3-display.yaml | 28 + .../documents/bugs/6369-oas3-no-display.yaml | 28 + .../static/documents/bugs/6442.yaml | 52 + .../static/documents/bugs/6475.yaml | 57 + .../static/documents/bugs/6540.yaml | 85 + .../static/documents/bugs/6627.yaml | 41 + .../bugs/7996-tags-externalDocs.yaml | 28 + .../static/documents/bugs/8217.yaml | 28 + .../static/documents/bugs/editor-1868.yaml | 13 + .../documents/features/auth-bearer-flow.yaml | 20 + .../auth-code-flow-pkce-without-secret.yaml | 25 + .../features/deep-linking.openapi.yaml | 58 + .../features/deep-linking.swagger.yaml | 34 + .../features/external-docs.openapi.yaml | 148 + .../features/external-docs.swagger.yaml | 116 + .../documents/features/models.openapi.yaml | 93 + .../documents/features/models.swagger.yaml | 92 + .../multiple-examples-core.openapi.yaml | 400 + .../features/oas3-multiple-media-type.yaml | 50 + .../oas3-multiple-servers-switch.yaml | 16 + .../features/oas3-multiple-servers.yaml | 16 + .../static/documents/features/oas3-xml.json | 309 + .../parameter-array-missing-items.yaml | 24 + .../documents/features/parameter-order.yaml | 63 + .../features/petstore-only-pet.openapi.yaml | 418 + .../features/request-body-upload-file.yaml | 105 + .../request-body/multipart/default-views.yaml | 28 + .../features/request-body/multipart/enum.yaml | 29 + .../features/response-extension.openapi.yaml | 19 + .../features/response-extension.swagger.yaml | 15 + .../documents/features/schema-form-core.yaml | 339 + .../features/schema-form-enum-boolean.yaml | 87 + .../features/schema-form-missing-values.yaml | 119 + .../features/spec-parse-to-json.yaml | 26 + .../syntax-highlighting-json-oas2.yaml | 97 + .../syntax-highlighting-json-oas3.yaml | 94 + .../features/try-it-out-enabled.yaml | 13 + ...-out-schema-required-override-allowed.yaml | 34 + .../static/documents/features/urls/1.yaml | 12 + .../static/documents/features/urls/2.yaml | 12 + .../features/urls/server-variables-1.yaml | 38 + .../features/urls/server-variables-2.yaml | 38 + .../documents/petstore-expanded.openapi.yaml | 166 + .../static/documents/petstore.swagger.yaml | 707 + .../static/documents/security/anonymous.yaml | 35 + .../sequential-import-chaining/injection.css | 7 + .../sequential-import-chaining/openapi.yaml | 10 + .../sequential-import-chaining/swagger.yaml | 10 + .../static/documents/security/xss-oauth2.yaml | 5 + .../test/e2e-cypress/static/index.html | 77 + .../e2e-cypress/static/pages/5085/index.html | 67 + .../static/pages/5138/api-with-examples.yaml | 167 + .../e2e-cypress/static/pages/5138/index.html | 77 + .../e2e-cypress/static/pages/5138/uspto.yaml | 210 + .../static/pages/multiple-urls/index.html | 80 + .../test/e2e-cypress/support/commands.js | 25 + .../api-doc/test/e2e-cypress/support/index.js | 32 + .../e2e-cypress/tests/a11y/response-tabs.js | 35 + .../test/e2e-cypress/tests/bugs/4442.js | 37 + .../test/e2e-cypress/tests/bugs/4641.js | 99 + .../test/e2e-cypress/tests/bugs/4838.js | 11 + .../test/e2e-cypress/tests/bugs/4865.js | 19 + .../test/e2e-cypress/tests/bugs/4867.js | 17 + .../test/e2e-cypress/tests/bugs/4943.js | 20 + .../test/e2e-cypress/tests/bugs/5043.js | 30 + .../test/e2e-cypress/tests/bugs/5060.js | 15 + .../test/e2e-cypress/tests/bugs/5070.js | 32 + .../test/e2e-cypress/tests/bugs/5072.js | 22 + .../test/e2e-cypress/tests/bugs/5129.js | 121 + .../test/e2e-cypress/tests/bugs/5138.js | 10 + .../test/e2e-cypress/tests/bugs/5164.js | 19 + .../test/e2e-cypress/tests/bugs/5188.js | 18 + .../test/e2e-cypress/tests/bugs/5452.js | 38 + .../test/e2e-cypress/tests/bugs/5453.js | 11 + .../test/e2e-cypress/tests/bugs/5455.js | 11 + .../test/e2e-cypress/tests/bugs/5458.js | 22 + .../test/e2e-cypress/tests/bugs/5660.js | 20 + .../test/e2e-cypress/tests/bugs/6016.js | 42 + .../test/e2e-cypress/tests/bugs/6158.js | 54 + .../test/e2e-cypress/tests/bugs/6183.js | 45 + .../test/e2e-cypress/tests/bugs/6276.js | 43 + .../test/e2e-cypress/tests/bugs/6351.js | 11 + .../test/e2e-cypress/tests/bugs/6369.js | 44 + .../test/e2e-cypress/tests/bugs/6442.js | 33 + .../test/e2e-cypress/tests/bugs/6475.js | 65 + .../test/e2e-cypress/tests/bugs/6540.js | 11 + .../test/e2e-cypress/tests/bugs/6627.js | 11 + .../test/e2e-cypress/tests/bugs/7996.js | 14 + .../test/e2e-cypress/tests/bugs/8217.js | 24 + .../e2e-cypress/tests/bugs/editor-1868.js | 26 + .../test/e2e-cypress/tests/bugs/swos-63.js | 35 + .../tests/features/auth-bearer-flow.js | 51 + .../auth-code-flow-pkce-without-secret.js | 47 + .../tests/features/deep-linking.js | 292 + .../tests/features/dynamic-default-oauth.js | 26 + .../tests/features/external-docs.js | 108 + .../tests/features/model-collapse.js | 50 + .../tests/features/multiple-examples-core.js | 662 + .../features/oas3-multiple-media-type.js | 184 + .../tests/features/oas3-multiple-servers.js | 82 + .../oas3-request-body-allow-empty-values.js | 145 + .../oas3-request-body-default-views.js | 16 + .../features/oas3-request-body-required.js | 265 + .../oas3-user-edit-request-body-flows.js | 106 + .../e2e-cypress/tests/features/oas3-xml.js | 70 + .../features/oauth2-flows/application.js | 66 + .../tests/features/oauth2-flows/password.js | 122 + .../features/parameter-array-missing-items.js | 10 + .../tests/features/parameter-order.js | 30 + .../topbar/linking-to-configured-urls.js | 23 + .../features/request-body-upload-file.js | 114 + .../tests/features/response-extension.js | 49 + .../features/schema-form-enum-boolean.js | 129 + .../e2e-cypress/tests/features/schema-form.js | 830 + .../tests/features/spec-parse-to-json.js | 31 + .../features/syntax-highlighting-json.js | 40 + .../tests/features/try-it-out-enabled.js | 22 + ...it-out-schema-required-override-allowed.js | 15 + .../test/e2e-cypress/tests/features/urls.js | 111 + .../e2e-cypress/tests/security/anonymous.js | 22 + .../test/e2e-cypress/tests/security/oauth2.js | 23 + .../security/sequential-import-chaining.js | 58 + .../web/api-doc/test/e2e-selenium/README.md | 2 + .../web/api-doc/test/e2e-selenium/db.json | 220 + .../api-doc/test/e2e-selenium/nightwatch.json | 31 + .../api-doc/test/e2e-selenium/pages/main.js | 544 + .../test/e2e-selenium/scenarios/bugs/4196.js | 42 + .../test/e2e-selenium/scenarios/bugs/4374.js | 31 + .../test/e2e-selenium/scenarios/bugs/4409.js | 28 + .../test/e2e-selenium/scenarios/bugs/4445.js | 28 + .../test/e2e-selenium/scenarios/bugs/4485.js | 28 + .../test/e2e-selenium/scenarios/bugs/4536.js | 28 + .../test/e2e-selenium/scenarios/bugs/4587.js | 35 + .../test/e2e-selenium/scenarios/bugs/4756.js | 42 + .../scenarios/bugs/frozen-array-input.js | 44 + .../features/allow-empty-value.openapi.js | 433 + .../features/allow-empty-value.swagger.js | 433 + .../scenarios/features/example.js | 240 + .../features/parameter-enum-rendering.js | 58 + .../features/parameter-example-rendering.js | 64 + .../scenarios/informationContainer.js | 78 + .../test/e2e-selenium/scenarios/models.js | 78 + .../e2e-selenium/scenarios/oas3/callbacks.js | 40 + .../test/e2e-selenium/scenarios/oas3/pet.js | 52 + .../e2e-selenium/scenarios/on-complete.js | 28 + .../e2e-selenium/scenarios/operations/pet.js | 112 + .../scenarios/operations/store.js | 112 + .../e2e-selenium/scenarios/operations/user.js | 78 + .../test/e2e-selenium/scenarios/refs.js | 26 + .../e2e-selenium/scenarios/schemeContainer.js | 61 + .../test/e2e-selenium/scenarios/topbar.js | 51 + .../test/e2e-selenium/static/index.html | 111 + .../static/test-specs/bugs/4196.yaml | 85 + .../static/test-specs/bugs/4374.yaml | 228 + .../static/test-specs/bugs/4409.yaml | 24 + .../static/test-specs/bugs/4445.yaml | 64 + .../static/test-specs/bugs/4485/book.yaml | 15 + .../static/test-specs/bugs/4485/main.yaml | 5 + .../static/test-specs/bugs/4536.yaml | 9 + .../static/test-specs/bugs/4587.yaml | 126 + .../static/test-specs/bugs/4756.yaml | 51 + .../test-specs/bugs/frozen-array-input.yaml | 35 + .../static/test-specs/callbacks.openapi.yaml | 41 + .../features/allow-empty-value.openapi.yaml | 64 + .../features/allow-empty-value.swagger.yaml | 57 + .../test-specs/features/example.openapi.yaml | 156 + .../test-specs/features/example.swagger.yaml | 127 + .../parameter-enum-rendering.openapi.yaml | 21 + .../parameter-enum-rendering.swagger.yaml | 20 + .../static/test-specs/petstore.json | 1043 + .../static/test-specs/petstore.openapi.yaml | 689 + .../static/test-specs/refs/api1.yaml | 32 + .../static/test-specs/refs/api2.yaml | 32 + frontend/web/api-doc/test/mocha/.eslinrc | 7 + .../test/mocha/components/live-response.jsx | 104 + .../components/online-validator-badge.jsx | 79 + frontend/web/api-doc/test/mocha/setup.js | 28 + .../online-validator-badge.jsx | 32 + frontend/web/api-doc/test/unit/.eslintrc | 0 .../unit/bugs/3199-sanitization-escaping.jsx | 21 + .../unit/bugs/3279-empty-markdown-source.jsx | 35 + .../bugs/4557-default-parameter-values.jsx | 78 + .../api-doc/test/unit/components/filter.jsx | 62 + .../test/unit/components/info-wrapper.jsx | 67 + .../test/unit/components/json-schema-form.jsx | 257 + .../test/unit/components/live-response.jsx | 101 + .../api-doc/test/unit/components/markdown.jsx | 110 + .../test/unit/components/model-example.jsx | 122 + .../api-doc/test/unit/components/models.jsx | 54 + .../test/unit/components/object-model.jsx | 109 + .../components/online-validator-badge.jsx | 76 + .../test/unit/components/operation-tag.jsx | 53 + .../test/unit/components/operation.jsx | 30 + .../test/unit/components/operations.jsx | 126 + .../test/unit/components/parameter-row.jsx | 257 + .../test/unit/components/primitive-model.jsx | 55 + .../test/unit/components/response-body.jsx | 42 + .../api-doc/test/unit/components/response.jsx | 63 + .../test/unit/components/schemes-wrapper.jsx | 88 + .../api-doc/test/unit/components/schemes.jsx | 69 + .../unit/components/version-pragma-filter.jsx | 67 + .../web/api-doc/test/unit/core/curlify.js | 389 + .../test/unit/core/helpers/get-model-name.js | 39 + .../unit/core/helpers/get-parameter-schema.js | 147 + .../test/unit/core/oauth2-authorize.js | 201 + .../test/unit/core/plugins/auth/actions.js | 346 + .../unit/core/plugins/auth/preauthorize.js | 153 + .../test/unit/core/plugins/auth/selectors.js | 228 + .../core/plugins/auth/wrap-spec-actions.js | 40 + .../test/unit/core/plugins/configs/actions.js | 65 + .../plugins/err/transformers/not-of-type.js | 54 + .../err/transformers/parameter-oneof.js | 131 + .../unit/core/plugins/filter/opsFilter.js | 24 + .../test/unit/core/plugins/oas3/helpers.js | 67 + .../test/unit/core/plugins/oas3/reducers.js | 582 + .../core/plugins/oas3/servers-wrapper.jsx | 75 + .../core/plugins/oas3/state-integration.js | 358 + .../core/plugins/oas3/wrap-auth-selectors.js | 240 + .../core/plugins/oas3/wrap-spec-selectors.js | 98 + .../unit/core/plugins/safe-render/index.jsx | 253 + .../test/unit/core/plugins/samples/fn.js | 2360 + .../test/unit/core/plugins/spec/actions.js | 229 + .../core/plugins/spec/assets/petstore.json | 1035 + .../test/unit/core/plugins/spec/reducer.js | 202 + .../test/unit/core/plugins/spec/selectors.js | 1378 + .../plugins/swagger-js/withCredentials.js | 93 + .../api-doc/test/unit/core/system/system.jsx | 915 + .../test/unit/core/system/wrapComponent.jsx | 307 + .../test/unit/core/system/wrapSelectors.js | 45 + frontend/web/api-doc/test/unit/core/utils.js | 1913 + .../web/api-doc/test/unit/docker/oauth.js | 66 + .../api-doc/test/unit/docker/translator.js | 345 + frontend/web/api-doc/test/unit/jest-shim.js | 4 + frontend/web/api-doc/test/unit/setup.js | 36 + .../swagger-ui-dist-package/absolute-path.js | 12 + .../test/unit/xss/anchor-target-rel/info.jsx | 106 + .../test/unit/xss/anchor-target-rel/link.jsx | 39 + .../unit/xss/anchor-target-rel/markdown.jsx | 64 + .../online-validator-badge.jsx | 29 + .../test/unit/xss/info-sanitization.jsx | 32 + .../unit/xss/markdown-script-sanitization.jsx | 46 + .../web/api-doc/webpack/_config-builder.js | 150 + frontend/web/api-doc/webpack/_helpers.js | 17 + frontend/web/api-doc/webpack/bundle.babel.js | 54 + frontend/web/api-doc/webpack/core.babel.js | 31 + frontend/web/api-doc/webpack/dev-e2e.babel.js | 76 + frontend/web/api-doc/webpack/dev.babel.js | 121 + .../api-doc/webpack/es-bundle-core.babel.js | 87 + .../web/api-doc/webpack/es-bundle.babel.js | 54 + .../web/api-doc/webpack/standalone.babel.js | 28 + .../web/api-doc/webpack/stylesheets.babel.js | 86 + frontend/web/js/company_select.js | 9 + 643 files changed, 110185 insertions(+), 231 deletions(-) create mode 100644 backend/modules/api/views/categoty/index.php create mode 100755 common/behaviors/GsCors.php create mode 100644 common/services/ProductService.php create mode 100644 console/controllers/SwaggerController.php create mode 100644 console/migrations/m240507_141733_add_category_id_column_at_product_table.php create mode 100644 frontend/assets/MainAsset.php create mode 100644 frontend/modules/api/Api.php create mode 100644 frontend/modules/api/controllers/ApiController.php create mode 100644 frontend/modules/api/controllers/CategoryController.php create mode 100644 frontend/modules/api/controllers/DefaultController.php create mode 100644 frontend/modules/api/controllers/ProductController.php create mode 100644 frontend/modules/api/views/default/index.php create mode 100644 frontend/modules/product_category/views/product-category/_category_select.php create mode 100644 frontend/web/api-doc/.agignore create mode 100644 frontend/web/api-doc/.commitlintrc.json create mode 100644 frontend/web/api-doc/.dockerignore create mode 100644 frontend/web/api-doc/.editorconfig create mode 100644 frontend/web/api-doc/.eslintignore create mode 100644 frontend/web/api-doc/.eslintrc create mode 100644 frontend/web/api-doc/.gitattributes create mode 100644 frontend/web/api-doc/.github/ISSUE_TEMPLATE/Bug_report.md create mode 100644 frontend/web/api-doc/.github/ISSUE_TEMPLATE/Feature_request.md create mode 100644 frontend/web/api-doc/.github/ISSUE_TEMPLATE/Support.md create mode 100644 frontend/web/api-doc/.github/dependabot.yaml create mode 100644 frontend/web/api-doc/.github/lock.yml create mode 100644 frontend/web/api-doc/.github/pull_request_template.md create mode 100644 frontend/web/api-doc/.github/workflows/dependabot-merge.yml create mode 100644 frontend/web/api-doc/.github/workflows/docker-image-check.yml create mode 100644 frontend/web/api-doc/.github/workflows/nodejs.yml create mode 100644 frontend/web/api-doc/.github/workflows/release-swagger-ui-react.yml create mode 100644 frontend/web/api-doc/.github/workflows/release-swagger-ui.yml create mode 100644 frontend/web/api-doc/.gitignore create mode 100755 frontend/web/api-doc/.husky/commit-msg create mode 100755 frontend/web/api-doc/.husky/pre-commit create mode 100644 frontend/web/api-doc/.lintstagedrc create mode 100644 frontend/web/api-doc/.mocharc.yaml create mode 100644 frontend/web/api-doc/.npmignore create mode 100644 frontend/web/api-doc/.npmrc create mode 100644 frontend/web/api-doc/.nvmrc create mode 100644 frontend/web/api-doc/.prettierrc.yaml create mode 100644 frontend/web/api-doc/.releaserc create mode 100644 frontend/web/api-doc/Dockerfile create mode 100644 frontend/web/api-doc/LICENSE create mode 100644 frontend/web/api-doc/NOTICE create mode 100644 frontend/web/api-doc/README.md create mode 100644 frontend/web/api-doc/SECURITY.md create mode 100644 frontend/web/api-doc/babel.config.js create mode 100644 frontend/web/api-doc/composer.json create mode 100644 frontend/web/api-doc/config/.eslintrc create mode 100644 frontend/web/api-doc/config/jest/jest.artifact.config.js create mode 100644 frontend/web/api-doc/config/jest/jest.unit.config.js create mode 100644 frontend/web/api-doc/cypress.json create mode 100644 frontend/web/api-doc/dev-helpers/dev-helper-initializer.js create mode 100644 frontend/web/api-doc/dev-helpers/index.html create mode 100644 frontend/web/api-doc/dev-helpers/oauth2-redirect.html create mode 100644 frontend/web/api-doc/dev-helpers/style.css create mode 100644 frontend/web/api-doc/dist/definitions/request.json create mode 100644 frontend/web/api-doc/dist/favicon-16x16.png create mode 100644 frontend/web/api-doc/dist/favicon-32x32.png create mode 100644 frontend/web/api-doc/dist/index.css create mode 100644 frontend/web/api-doc/dist/index.html create mode 100644 frontend/web/api-doc/dist/oauth2-redirect.html create mode 100644 frontend/web/api-doc/dist/swagger-config.yaml create mode 100644 frontend/web/api-doc/dist/swagger-initializer.js create mode 100644 frontend/web/api-doc/dist/swagger-ui-bundle.js create mode 100644 frontend/web/api-doc/dist/swagger-ui-bundle.js.LICENSE.txt create mode 100644 frontend/web/api-doc/dist/swagger-ui-bundle.js.map create mode 100644 frontend/web/api-doc/dist/swagger-ui-es-bundle-core.js create mode 100644 frontend/web/api-doc/dist/swagger-ui-es-bundle-core.js.LICENSE.txt create mode 100644 frontend/web/api-doc/dist/swagger-ui-es-bundle-core.js.map create mode 100644 frontend/web/api-doc/dist/swagger-ui-es-bundle.js create mode 100644 frontend/web/api-doc/dist/swagger-ui-es-bundle.js.LICENSE.txt create mode 100644 frontend/web/api-doc/dist/swagger-ui-es-bundle.js.map create mode 100644 frontend/web/api-doc/dist/swagger-ui-standalone-preset.js create mode 100644 frontend/web/api-doc/dist/swagger-ui-standalone-preset.js.LICENSE.txt create mode 100644 frontend/web/api-doc/dist/swagger-ui-standalone-preset.js.map create mode 100644 frontend/web/api-doc/dist/swagger-ui.css create mode 100644 frontend/web/api-doc/dist/swagger-ui.css.map create mode 100644 frontend/web/api-doc/dist/swagger-ui.js create mode 100644 frontend/web/api-doc/dist/swagger-ui.js.map create mode 100644 frontend/web/api-doc/dist/swagger.yaml create mode 100644 frontend/web/api-doc/docker/configurator/helpers.js create mode 100755 frontend/web/api-doc/docker/configurator/index.js create mode 100644 frontend/web/api-doc/docker/configurator/oauth.js create mode 100644 frontend/web/api-doc/docker/configurator/translator.js create mode 100644 frontend/web/api-doc/docker/configurator/variables.js create mode 100644 frontend/web/api-doc/docker/cors.conf create mode 100755 frontend/web/api-doc/docker/docker-entrypoint.d/40-swagger-ui.sh create mode 100644 frontend/web/api-doc/docker/nginx.conf create mode 100644 frontend/web/api-doc/docs/README.md create mode 100644 frontend/web/api-doc/docs/book.json create mode 100644 frontend/web/api-doc/docs/customization/custom-layout.md create mode 100644 frontend/web/api-doc/docs/customization/overview.md create mode 100644 frontend/web/api-doc/docs/customization/plug-points.md create mode 100644 frontend/web/api-doc/docs/customization/plugin-api.md create mode 100644 frontend/web/api-doc/docs/development/scripts.md create mode 100644 frontend/web/api-doc/docs/development/setting-up.md create mode 100644 frontend/web/api-doc/docs/images/swagger-ui2.png create mode 100644 frontend/web/api-doc/docs/images/swagger-ui3.png create mode 100644 frontend/web/api-doc/docs/samples/webpack-getting-started/README.md create mode 100644 frontend/web/api-doc/docs/samples/webpack-getting-started/_sample_package.json create mode 100644 frontend/web/api-doc/docs/samples/webpack-getting-started/index.html create mode 100644 frontend/web/api-doc/docs/samples/webpack-getting-started/src/index.js create mode 100644 frontend/web/api-doc/docs/samples/webpack-getting-started/src/swagger-config.yaml create mode 100644 frontend/web/api-doc/docs/samples/webpack-getting-started/webpack.config.js create mode 100644 frontend/web/api-doc/docs/usage/configuration.md create mode 100644 frontend/web/api-doc/docs/usage/cors.md create mode 100644 frontend/web/api-doc/docs/usage/deep-linking.md create mode 100644 frontend/web/api-doc/docs/usage/installation.md create mode 100644 frontend/web/api-doc/docs/usage/limitations.md create mode 100644 frontend/web/api-doc/docs/usage/oauth2.md create mode 100644 frontend/web/api-doc/docs/usage/version-detection.md create mode 100644 frontend/web/api-doc/flavors/swagger-ui-react/README.md create mode 100644 frontend/web/api-doc/flavors/swagger-ui-react/index.jsx create mode 100644 frontend/web/api-doc/flavors/swagger-ui-react/release/create-manifest.js create mode 100755 frontend/web/api-doc/flavors/swagger-ui-react/release/run.sh create mode 100644 frontend/web/api-doc/flavors/swagger-ui-react/release/template.json create mode 100644 frontend/web/api-doc/package-lock.json create mode 100644 frontend/web/api-doc/package.json create mode 100644 frontend/web/api-doc/patches/tachyons-sass+4.9.5.patch create mode 100644 frontend/web/api-doc/release/.release-it.json create mode 100755 frontend/web/api-doc/release/check-for-breaking-changes.sh create mode 100755 frontend/web/api-doc/release/get-changelog.sh create mode 100644 frontend/web/api-doc/renovate.json create mode 100644 frontend/web/api-doc/snapcraft.yaml create mode 100644 frontend/web/api-doc/src/.eslintrc create mode 100644 frontend/web/api-doc/src/core/brace-snippets-yaml.js create mode 100644 frontend/web/api-doc/src/core/components/app.jsx create mode 100644 frontend/web/api-doc/src/core/components/array-model.jsx create mode 100644 frontend/web/api-doc/src/core/components/auth/api-key-auth.jsx create mode 100644 frontend/web/api-doc/src/core/components/auth/auth-item.jsx create mode 100644 frontend/web/api-doc/src/core/components/auth/authorization-popup.jsx create mode 100644 frontend/web/api-doc/src/core/components/auth/authorize-btn.jsx create mode 100644 frontend/web/api-doc/src/core/components/auth/authorize-operation-btn.jsx create mode 100644 frontend/web/api-doc/src/core/components/auth/auths.jsx create mode 100644 frontend/web/api-doc/src/core/components/auth/basic-auth.jsx create mode 100644 frontend/web/api-doc/src/core/components/auth/error.jsx create mode 100644 frontend/web/api-doc/src/core/components/auth/oauth2.jsx create mode 100644 frontend/web/api-doc/src/core/components/clear.jsx create mode 100644 frontend/web/api-doc/src/core/components/content-type.jsx create mode 100644 frontend/web/api-doc/src/core/components/copy-to-clipboard-btn.jsx create mode 100644 frontend/web/api-doc/src/core/components/curl.jsx create mode 100644 frontend/web/api-doc/src/core/components/debug.jsx create mode 100644 frontend/web/api-doc/src/core/components/deep-link.jsx create mode 100644 frontend/web/api-doc/src/core/components/enum-model.jsx create mode 100644 frontend/web/api-doc/src/core/components/errors.jsx create mode 100644 frontend/web/api-doc/src/core/components/example.jsx create mode 100644 frontend/web/api-doc/src/core/components/examples-select-value-retainer.jsx create mode 100644 frontend/web/api-doc/src/core/components/examples-select.jsx create mode 100644 frontend/web/api-doc/src/core/components/execute.jsx create mode 100644 frontend/web/api-doc/src/core/components/footer.jsx create mode 100644 frontend/web/api-doc/src/core/components/headers.jsx create mode 100644 frontend/web/api-doc/src/core/components/highlight-code.jsx create mode 100644 frontend/web/api-doc/src/core/components/info.jsx create mode 100644 frontend/web/api-doc/src/core/components/initialized-input.jsx create mode 100644 frontend/web/api-doc/src/core/components/jump-to-path.jsx create mode 100644 frontend/web/api-doc/src/core/components/layout-utils.jsx create mode 100644 frontend/web/api-doc/src/core/components/layouts/base.jsx create mode 100644 frontend/web/api-doc/src/core/components/layouts/xpane.jsx create mode 100644 frontend/web/api-doc/src/core/components/live-response.jsx create mode 100644 frontend/web/api-doc/src/core/components/model-collapse.jsx create mode 100644 frontend/web/api-doc/src/core/components/model-example.jsx create mode 100644 frontend/web/api-doc/src/core/components/model-wrapper.jsx create mode 100644 frontend/web/api-doc/src/core/components/model.jsx create mode 100644 frontend/web/api-doc/src/core/components/models.jsx create mode 100644 frontend/web/api-doc/src/core/components/object-model.jsx create mode 100644 frontend/web/api-doc/src/core/components/online-validator-badge.jsx create mode 100644 frontend/web/api-doc/src/core/components/operation-extension-row.jsx create mode 100644 frontend/web/api-doc/src/core/components/operation-extensions.jsx create mode 100644 frontend/web/api-doc/src/core/components/operation-summary-method.jsx create mode 100644 frontend/web/api-doc/src/core/components/operation-summary-path.jsx create mode 100644 frontend/web/api-doc/src/core/components/operation-summary.jsx create mode 100644 frontend/web/api-doc/src/core/components/operation-tag.jsx create mode 100644 frontend/web/api-doc/src/core/components/operation.jsx create mode 100644 frontend/web/api-doc/src/core/components/operations.jsx create mode 100644 frontend/web/api-doc/src/core/components/overview.jsx create mode 100644 frontend/web/api-doc/src/core/components/param-body.jsx create mode 100644 frontend/web/api-doc/src/core/components/parameter-extension.jsx create mode 100644 frontend/web/api-doc/src/core/components/parameter-include-empty.jsx create mode 100644 frontend/web/api-doc/src/core/components/parameter-row.jsx create mode 100644 frontend/web/api-doc/src/core/components/parameters/index.js create mode 100644 frontend/web/api-doc/src/core/components/parameters/parameters.jsx create mode 100644 frontend/web/api-doc/src/core/components/primitive-model.jsx create mode 100644 frontend/web/api-doc/src/core/components/property.jsx create mode 100644 frontend/web/api-doc/src/core/components/providers/README.md create mode 100644 frontend/web/api-doc/src/core/components/providers/markdown.jsx create mode 100644 frontend/web/api-doc/src/core/components/response-body.jsx create mode 100644 frontend/web/api-doc/src/core/components/response-extension.jsx create mode 100644 frontend/web/api-doc/src/core/components/response.jsx create mode 100644 frontend/web/api-doc/src/core/components/responses.jsx create mode 100644 frontend/web/api-doc/src/core/components/schemes.jsx create mode 100644 frontend/web/api-doc/src/core/components/svg-assets.jsx create mode 100644 frontend/web/api-doc/src/core/components/system-wrapper.jsx create mode 100644 frontend/web/api-doc/src/core/components/try-it-out-button.jsx create mode 100644 frontend/web/api-doc/src/core/components/version-pragma-filter.jsx create mode 100644 frontend/web/api-doc/src/core/components/version-stamp.jsx create mode 100644 frontend/web/api-doc/src/core/containers/OperationContainer.jsx create mode 100644 frontend/web/api-doc/src/core/containers/authorize-btn.jsx create mode 100644 frontend/web/api-doc/src/core/containers/filter.jsx create mode 100644 frontend/web/api-doc/src/core/containers/info.jsx create mode 100644 frontend/web/api-doc/src/core/containers/schemes.jsx create mode 100644 frontend/web/api-doc/src/core/index.js create mode 100644 frontend/web/api-doc/src/core/json-schema-components.jsx create mode 100644 frontend/web/api-doc/src/core/oauth2-authorize.js create mode 100644 frontend/web/api-doc/src/core/plugins/all.js create mode 100644 frontend/web/api-doc/src/core/plugins/auth/actions.js create mode 100644 frontend/web/api-doc/src/core/plugins/auth/index.js create mode 100644 frontend/web/api-doc/src/core/plugins/auth/reducers.js create mode 100644 frontend/web/api-doc/src/core/plugins/auth/selectors.js create mode 100644 frontend/web/api-doc/src/core/plugins/auth/spec-wrap-actions.js create mode 100644 frontend/web/api-doc/src/core/plugins/configs/actions.js create mode 100644 frontend/web/api-doc/src/core/plugins/configs/helpers.js create mode 100644 frontend/web/api-doc/src/core/plugins/configs/index.js create mode 100644 frontend/web/api-doc/src/core/plugins/configs/reducers.js create mode 100644 frontend/web/api-doc/src/core/plugins/configs/selectors.js create mode 100644 frontend/web/api-doc/src/core/plugins/configs/spec-actions.js create mode 100644 frontend/web/api-doc/src/core/plugins/deep-linking/README.md create mode 100644 frontend/web/api-doc/src/core/plugins/deep-linking/helpers.js create mode 100644 frontend/web/api-doc/src/core/plugins/deep-linking/index.js create mode 100644 frontend/web/api-doc/src/core/plugins/deep-linking/layout.js create mode 100644 frontend/web/api-doc/src/core/plugins/deep-linking/operation-tag-wrapper.jsx create mode 100644 frontend/web/api-doc/src/core/plugins/deep-linking/operation-wrapper.jsx create mode 100644 frontend/web/api-doc/src/core/plugins/download-url.js create mode 100644 frontend/web/api-doc/src/core/plugins/err/actions.js create mode 100644 frontend/web/api-doc/src/core/plugins/err/error-transformers/README.md create mode 100644 frontend/web/api-doc/src/core/plugins/err/error-transformers/hook.js create mode 100644 frontend/web/api-doc/src/core/plugins/err/error-transformers/transformers/not-of-type.js create mode 100644 frontend/web/api-doc/src/core/plugins/err/error-transformers/transformers/parameter-oneof.js create mode 100644 frontend/web/api-doc/src/core/plugins/err/index.js create mode 100644 frontend/web/api-doc/src/core/plugins/err/reducers.js create mode 100644 frontend/web/api-doc/src/core/plugins/err/selectors.js create mode 100644 frontend/web/api-doc/src/core/plugins/filter/index.js create mode 100644 frontend/web/api-doc/src/core/plugins/filter/opsFilter.js create mode 100644 frontend/web/api-doc/src/core/plugins/layout/actions.js create mode 100644 frontend/web/api-doc/src/core/plugins/layout/index.js create mode 100644 frontend/web/api-doc/src/core/plugins/layout/reducers.js create mode 100644 frontend/web/api-doc/src/core/plugins/layout/selectors.js create mode 100644 frontend/web/api-doc/src/core/plugins/layout/spec-extensions/wrap-selector.js create mode 100644 frontend/web/api-doc/src/core/plugins/logs/index.js create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/actions.js create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/auth-extensions/wrap-selectors.js create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/components/callbacks.jsx create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/components/http-auth.jsx create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/components/index.js create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/components/operation-link.jsx create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/components/operation-servers.jsx create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/components/request-body-editor.jsx create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/components/request-body.jsx create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/components/servers-container.jsx create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/components/servers.jsx create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/helpers.jsx create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/index.js create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/reducers.js create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/selectors.js create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/spec-extensions/selectors.js create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/spec-extensions/wrap-selectors.js create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/wrap-components/auth-item.jsx create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/wrap-components/index.js create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/wrap-components/json-schema-string.jsx create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/wrap-components/markdown.jsx create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/wrap-components/model.jsx create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/wrap-components/online-validator-badge.js create mode 100644 frontend/web/api-doc/src/core/plugins/oas3/wrap-components/version-stamp.jsx create mode 100644 frontend/web/api-doc/src/core/plugins/on-complete/index.js create mode 100644 frontend/web/api-doc/src/core/plugins/request-snippets/fn.js create mode 100644 frontend/web/api-doc/src/core/plugins/request-snippets/index.js create mode 100644 frontend/web/api-doc/src/core/plugins/request-snippets/request-snippets.jsx create mode 100644 frontend/web/api-doc/src/core/plugins/request-snippets/selectors.js create mode 100644 frontend/web/api-doc/src/core/plugins/safe-render/components/error-boundary.jsx create mode 100644 frontend/web/api-doc/src/core/plugins/safe-render/components/fallback.jsx create mode 100644 frontend/web/api-doc/src/core/plugins/safe-render/fn.jsx create mode 100644 frontend/web/api-doc/src/core/plugins/safe-render/index.js create mode 100644 frontend/web/api-doc/src/core/plugins/samples/fn.js create mode 100644 frontend/web/api-doc/src/core/plugins/samples/index.js create mode 100644 frontend/web/api-doc/src/core/plugins/spec/actions.js create mode 100644 frontend/web/api-doc/src/core/plugins/spec/index.js create mode 100644 frontend/web/api-doc/src/core/plugins/spec/reducers.js create mode 100644 frontend/web/api-doc/src/core/plugins/spec/selectors.js create mode 100644 frontend/web/api-doc/src/core/plugins/spec/wrap-actions.js create mode 100644 frontend/web/api-doc/src/core/plugins/swagger-js/configs-wrap-actions.js create mode 100644 frontend/web/api-doc/src/core/plugins/swagger-js/index.js create mode 100644 frontend/web/api-doc/src/core/plugins/util/index.js create mode 100644 frontend/web/api-doc/src/core/plugins/view/fn.js create mode 100644 frontend/web/api-doc/src/core/plugins/view/index.js create mode 100644 frontend/web/api-doc/src/core/plugins/view/root-injects.jsx create mode 100644 frontend/web/api-doc/src/core/presets/apis.js create mode 100644 frontend/web/api-doc/src/core/presets/base.js create mode 100644 frontend/web/api-doc/src/core/proptypes.js create mode 100644 frontend/web/api-doc/src/core/syntax-highlighting.js create mode 100644 frontend/web/api-doc/src/core/system.js create mode 100644 frontend/web/api-doc/src/core/utils.js create mode 100644 frontend/web/api-doc/src/core/utils/jsonParse.js create mode 100644 frontend/web/api-doc/src/core/utils/url.js create mode 100644 frontend/web/api-doc/src/core/window.js create mode 100644 frontend/web/api-doc/src/helpers/create-html-ready-id.js create mode 100644 frontend/web/api-doc/src/helpers/get-parameter-schema.js create mode 100644 frontend/web/api-doc/src/helpers/memoizeN.js create mode 100644 frontend/web/api-doc/src/img/logo_small.png create mode 100644 frontend/web/api-doc/src/img/rolling-load.svg create mode 100644 frontend/web/api-doc/src/index.js create mode 100644 frontend/web/api-doc/src/plugins/add-plugin.md create mode 100644 frontend/web/api-doc/src/plugins/index.js create mode 100644 frontend/web/api-doc/src/plugins/topbar/index.js create mode 100644 frontend/web/api-doc/src/plugins/topbar/logo.jsx create mode 100644 frontend/web/api-doc/src/plugins/topbar/logo_small.svg create mode 100644 frontend/web/api-doc/src/plugins/topbar/topbar.jsx create mode 100644 frontend/web/api-doc/src/standalone/index.js create mode 100644 frontend/web/api-doc/src/standalone/layout.jsx create mode 100644 frontend/web/api-doc/src/style/_authorize.scss create mode 100644 frontend/web/api-doc/src/style/_buttons.scss create mode 100644 frontend/web/api-doc/src/style/_errors.scss create mode 100644 frontend/web/api-doc/src/style/_form.scss create mode 100644 frontend/web/api-doc/src/style/_information.scss create mode 100644 frontend/web/api-doc/src/style/_layout.scss create mode 100644 frontend/web/api-doc/src/style/_markdown.scss create mode 100644 frontend/web/api-doc/src/style/_mixins.scss create mode 100644 frontend/web/api-doc/src/style/_modal.scss create mode 100644 frontend/web/api-doc/src/style/_models.scss create mode 100644 frontend/web/api-doc/src/style/_servers.scss create mode 100644 frontend/web/api-doc/src/style/_split-pane-mode.scss create mode 100644 frontend/web/api-doc/src/style/_table.scss create mode 100644 frontend/web/api-doc/src/style/_topbar.scss create mode 100644 frontend/web/api-doc/src/style/_type.scss create mode 100644 frontend/web/api-doc/src/style/_variables.scss create mode 100644 frontend/web/api-doc/src/style/main.scss create mode 100644 frontend/web/api-doc/swagger-config.yaml create mode 100644 frontend/web/api-doc/swagger-ui-dist-package/.npmignore create mode 100644 frontend/web/api-doc/swagger-ui-dist-package/.npmrc create mode 100644 frontend/web/api-doc/swagger-ui-dist-package/README.md create mode 100644 frontend/web/api-doc/swagger-ui-dist-package/absolute-path.js create mode 100755 frontend/web/api-doc/swagger-ui-dist-package/deploy.sh create mode 100644 frontend/web/api-doc/swagger-ui-dist-package/index.js create mode 100644 frontend/web/api-doc/swagger-ui-dist-package/package.json create mode 100644 frontend/web/api-doc/swagger.yaml create mode 100644 frontend/web/api-doc/test/.eslintrc create mode 100644 frontend/web/api-doc/test/build-artifacts/.eslintrc create mode 100644 frontend/web/api-doc/test/build-artifacts/es-bundle-core.js create mode 100644 frontend/web/api-doc/test/build-artifacts/es-bundle.js create mode 100644 frontend/web/api-doc/test/build-artifacts/umd.js create mode 100644 frontend/web/api-doc/test/components/highlight-code.jsx create mode 100644 frontend/web/api-doc/test/components/response-body.jsx create mode 100644 frontend/web/api-doc/test/e2e-cypress/.eslintrc create mode 100644 frontend/web/api-doc/test/e2e-cypress/fixtures/example.json create mode 100644 frontend/web/api-doc/test/e2e-cypress/helpers/multiple-examples.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/helpers/oauth2-server/index.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/helpers/oauth2-server/model.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/helpers/oauth2-server/swagger.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/plugins/index.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/configs/urls-primary-name.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/configs/urls-server-variables.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/configs/urls.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/4442.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/4641.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/4838.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/4867.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/4943.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5043/status.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5043/swagger.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5060.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5072/additional.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5072/empty.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5129.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5164.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5181.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5188.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5452/openapi.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5452/swagger.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5453.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5455.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5458.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5660-model.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5660-property.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6016-oas2.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6016-oas3.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6158.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6183.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6351.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6369-oas2-display.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6369-oas2-no-display.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6369-oas3-display.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6369-oas3-no-display.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6442.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6475.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6540.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6627.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/7996-tags-externalDocs.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/8217.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/editor-1868.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/auth-bearer-flow.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/auth-code-flow-pkce-without-secret.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/deep-linking.openapi.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/deep-linking.swagger.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/external-docs.openapi.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/external-docs.swagger.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/models.openapi.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/models.swagger.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/multiple-examples-core.openapi.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/oas3-multiple-media-type.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/oas3-multiple-servers-switch.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/oas3-multiple-servers.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/oas3-xml.json create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/parameter-array-missing-items.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/parameter-order.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/petstore-only-pet.openapi.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/request-body-upload-file.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/request-body/multipart/default-views.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/request-body/multipart/enum.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/response-extension.openapi.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/response-extension.swagger.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/schema-form-core.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/schema-form-enum-boolean.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/schema-form-missing-values.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/spec-parse-to-json.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/syntax-highlighting-json-oas2.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/syntax-highlighting-json-oas3.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/try-it-out-enabled.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/try-it-out-schema-required-override-allowed.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/urls/1.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/urls/2.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/urls/server-variables-1.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/features/urls/server-variables-2.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/petstore-expanded.openapi.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/petstore.swagger.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/security/anonymous.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/security/sequential-import-chaining/injection.css create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/security/sequential-import-chaining/openapi.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/security/sequential-import-chaining/swagger.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/documents/security/xss-oauth2.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/index.html create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/pages/5085/index.html create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/pages/5138/api-with-examples.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/pages/5138/index.html create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/pages/5138/uspto.yaml create mode 100644 frontend/web/api-doc/test/e2e-cypress/static/pages/multiple-urls/index.html create mode 100644 frontend/web/api-doc/test/e2e-cypress/support/commands.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/support/index.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/a11y/response-tabs.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/4442.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/4641.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/4838.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/4865.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/4867.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/4943.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/5043.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/5060.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/5070.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/5072.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/5129.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/5138.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/5164.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/5188.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/5452.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/5453.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/5455.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/5458.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/5660.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/6016.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/6158.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/6183.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/6276.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/6351.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/6369.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/6442.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/6475.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/6540.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/6627.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/7996.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/8217.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/editor-1868.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/bugs/swos-63.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/auth-bearer-flow.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/auth-code-flow-pkce-without-secret.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/deep-linking.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/dynamic-default-oauth.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/external-docs.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/model-collapse.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/multiple-examples-core.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-multiple-media-type.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-multiple-servers.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-request-body-allow-empty-values.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-request-body-default-views.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-request-body-required.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-user-edit-request-body-flows.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-xml.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/oauth2-flows/application.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/oauth2-flows/password.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/parameter-array-missing-items.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/parameter-order.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/plugins/topbar/linking-to-configured-urls.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/request-body-upload-file.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/response-extension.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/schema-form-enum-boolean.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/schema-form.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/spec-parse-to-json.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/syntax-highlighting-json.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/try-it-out-enabled.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/try-it-out-schema-required-override-allowed.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/features/urls.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/security/anonymous.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/security/oauth2.js create mode 100644 frontend/web/api-doc/test/e2e-cypress/tests/security/sequential-import-chaining.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/README.md create mode 100644 frontend/web/api-doc/test/e2e-selenium/db.json create mode 100644 frontend/web/api-doc/test/e2e-selenium/nightwatch.json create mode 100644 frontend/web/api-doc/test/e2e-selenium/pages/main.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/bugs/4196.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/bugs/4374.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/bugs/4409.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/bugs/4445.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/bugs/4485.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/bugs/4536.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/bugs/4587.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/bugs/4756.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/bugs/frozen-array-input.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/features/allow-empty-value.openapi.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/features/allow-empty-value.swagger.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/features/example.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/features/parameter-enum-rendering.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/features/parameter-example-rendering.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/informationContainer.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/models.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/oas3/callbacks.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/oas3/pet.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/on-complete.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/operations/pet.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/operations/store.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/operations/user.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/refs.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/schemeContainer.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/scenarios/topbar.js create mode 100644 frontend/web/api-doc/test/e2e-selenium/static/index.html create mode 100644 frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4196.yaml create mode 100644 frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4374.yaml create mode 100644 frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4409.yaml create mode 100644 frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4445.yaml create mode 100644 frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4485/book.yaml create mode 100644 frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4485/main.yaml create mode 100644 frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4536.yaml create mode 100644 frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4587.yaml create mode 100644 frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4756.yaml create mode 100644 frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/frozen-array-input.yaml create mode 100644 frontend/web/api-doc/test/e2e-selenium/static/test-specs/callbacks.openapi.yaml create mode 100644 frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/allow-empty-value.openapi.yaml create mode 100644 frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/allow-empty-value.swagger.yaml create mode 100644 frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/example.openapi.yaml create mode 100644 frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/example.swagger.yaml create mode 100644 frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/parameter-enum-rendering.openapi.yaml create mode 100644 frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/parameter-enum-rendering.swagger.yaml create mode 100644 frontend/web/api-doc/test/e2e-selenium/static/test-specs/petstore.json create mode 100644 frontend/web/api-doc/test/e2e-selenium/static/test-specs/petstore.openapi.yaml create mode 100644 frontend/web/api-doc/test/e2e-selenium/static/test-specs/refs/api1.yaml create mode 100644 frontend/web/api-doc/test/e2e-selenium/static/test-specs/refs/api2.yaml create mode 100644 frontend/web/api-doc/test/mocha/.eslinrc create mode 100644 frontend/web/api-doc/test/mocha/components/live-response.jsx create mode 100644 frontend/web/api-doc/test/mocha/components/online-validator-badge.jsx create mode 100644 frontend/web/api-doc/test/mocha/setup.js create mode 100644 frontend/web/api-doc/test/mocha/xss/anchor-target-rel/online-validator-badge.jsx create mode 100644 frontend/web/api-doc/test/unit/.eslintrc create mode 100644 frontend/web/api-doc/test/unit/bugs/3199-sanitization-escaping.jsx create mode 100644 frontend/web/api-doc/test/unit/bugs/3279-empty-markdown-source.jsx create mode 100644 frontend/web/api-doc/test/unit/bugs/4557-default-parameter-values.jsx create mode 100644 frontend/web/api-doc/test/unit/components/filter.jsx create mode 100644 frontend/web/api-doc/test/unit/components/info-wrapper.jsx create mode 100644 frontend/web/api-doc/test/unit/components/json-schema-form.jsx create mode 100644 frontend/web/api-doc/test/unit/components/live-response.jsx create mode 100644 frontend/web/api-doc/test/unit/components/markdown.jsx create mode 100644 frontend/web/api-doc/test/unit/components/model-example.jsx create mode 100644 frontend/web/api-doc/test/unit/components/models.jsx create mode 100644 frontend/web/api-doc/test/unit/components/object-model.jsx create mode 100644 frontend/web/api-doc/test/unit/components/online-validator-badge.jsx create mode 100644 frontend/web/api-doc/test/unit/components/operation-tag.jsx create mode 100644 frontend/web/api-doc/test/unit/components/operation.jsx create mode 100644 frontend/web/api-doc/test/unit/components/operations.jsx create mode 100644 frontend/web/api-doc/test/unit/components/parameter-row.jsx create mode 100644 frontend/web/api-doc/test/unit/components/primitive-model.jsx create mode 100644 frontend/web/api-doc/test/unit/components/response-body.jsx create mode 100644 frontend/web/api-doc/test/unit/components/response.jsx create mode 100644 frontend/web/api-doc/test/unit/components/schemes-wrapper.jsx create mode 100644 frontend/web/api-doc/test/unit/components/schemes.jsx create mode 100644 frontend/web/api-doc/test/unit/components/version-pragma-filter.jsx create mode 100644 frontend/web/api-doc/test/unit/core/curlify.js create mode 100644 frontend/web/api-doc/test/unit/core/helpers/get-model-name.js create mode 100644 frontend/web/api-doc/test/unit/core/helpers/get-parameter-schema.js create mode 100644 frontend/web/api-doc/test/unit/core/oauth2-authorize.js create mode 100644 frontend/web/api-doc/test/unit/core/plugins/auth/actions.js create mode 100644 frontend/web/api-doc/test/unit/core/plugins/auth/preauthorize.js create mode 100644 frontend/web/api-doc/test/unit/core/plugins/auth/selectors.js create mode 100644 frontend/web/api-doc/test/unit/core/plugins/auth/wrap-spec-actions.js create mode 100644 frontend/web/api-doc/test/unit/core/plugins/configs/actions.js create mode 100644 frontend/web/api-doc/test/unit/core/plugins/err/transformers/not-of-type.js create mode 100644 frontend/web/api-doc/test/unit/core/plugins/err/transformers/parameter-oneof.js create mode 100644 frontend/web/api-doc/test/unit/core/plugins/filter/opsFilter.js create mode 100644 frontend/web/api-doc/test/unit/core/plugins/oas3/helpers.js create mode 100644 frontend/web/api-doc/test/unit/core/plugins/oas3/reducers.js create mode 100644 frontend/web/api-doc/test/unit/core/plugins/oas3/servers-wrapper.jsx create mode 100644 frontend/web/api-doc/test/unit/core/plugins/oas3/state-integration.js create mode 100644 frontend/web/api-doc/test/unit/core/plugins/oas3/wrap-auth-selectors.js create mode 100644 frontend/web/api-doc/test/unit/core/plugins/oas3/wrap-spec-selectors.js create mode 100644 frontend/web/api-doc/test/unit/core/plugins/safe-render/index.jsx create mode 100644 frontend/web/api-doc/test/unit/core/plugins/samples/fn.js create mode 100644 frontend/web/api-doc/test/unit/core/plugins/spec/actions.js create mode 100644 frontend/web/api-doc/test/unit/core/plugins/spec/assets/petstore.json create mode 100644 frontend/web/api-doc/test/unit/core/plugins/spec/reducer.js create mode 100644 frontend/web/api-doc/test/unit/core/plugins/spec/selectors.js create mode 100644 frontend/web/api-doc/test/unit/core/plugins/swagger-js/withCredentials.js create mode 100644 frontend/web/api-doc/test/unit/core/system/system.jsx create mode 100644 frontend/web/api-doc/test/unit/core/system/wrapComponent.jsx create mode 100644 frontend/web/api-doc/test/unit/core/system/wrapSelectors.js create mode 100644 frontend/web/api-doc/test/unit/core/utils.js create mode 100644 frontend/web/api-doc/test/unit/docker/oauth.js create mode 100644 frontend/web/api-doc/test/unit/docker/translator.js create mode 100644 frontend/web/api-doc/test/unit/jest-shim.js create mode 100644 frontend/web/api-doc/test/unit/setup.js create mode 100644 frontend/web/api-doc/test/unit/swagger-ui-dist-package/absolute-path.js create mode 100644 frontend/web/api-doc/test/unit/xss/anchor-target-rel/info.jsx create mode 100644 frontend/web/api-doc/test/unit/xss/anchor-target-rel/link.jsx create mode 100644 frontend/web/api-doc/test/unit/xss/anchor-target-rel/markdown.jsx create mode 100644 frontend/web/api-doc/test/unit/xss/anchor-target-rel/online-validator-badge.jsx create mode 100644 frontend/web/api-doc/test/unit/xss/info-sanitization.jsx create mode 100644 frontend/web/api-doc/test/unit/xss/markdown-script-sanitization.jsx create mode 100644 frontend/web/api-doc/webpack/_config-builder.js create mode 100644 frontend/web/api-doc/webpack/_helpers.js create mode 100644 frontend/web/api-doc/webpack/bundle.babel.js create mode 100644 frontend/web/api-doc/webpack/core.babel.js create mode 100644 frontend/web/api-doc/webpack/dev-e2e.babel.js create mode 100644 frontend/web/api-doc/webpack/dev.babel.js create mode 100644 frontend/web/api-doc/webpack/es-bundle-core.babel.js create mode 100644 frontend/web/api-doc/webpack/es-bundle.babel.js create mode 100644 frontend/web/api-doc/webpack/standalone.babel.js create mode 100644 frontend/web/api-doc/webpack/stylesheets.babel.js create mode 100644 frontend/web/js/company_select.js diff --git a/backend/modules/api/views/categoty/index.php b/backend/modules/api/views/categoty/index.php new file mode 100644 index 0000000..d9c8825 --- /dev/null +++ b/backend/modules/api/views/categoty/index.php @@ -0,0 +1,9 @@ + +

category/index

+ +

+ You may change the content of this page by modifying + the file . +

diff --git a/common/behaviors/GsCors.php b/common/behaviors/GsCors.php new file mode 100755 index 0000000..71e4b75 --- /dev/null +++ b/common/behaviors/GsCors.php @@ -0,0 +1,22 @@ +cors['Access-Control-Allow-Headers'])) { + $responseHeaders['Access-Control-Allow-Headers'] = implode(', ', $this->cors['Access-Control-Allow-Headers']); + } + + return $responseHeaders; + } + +} \ No newline at end of file diff --git a/common/models/Product.php b/common/models/Product.php index d7172c9..9d81946 100644 --- a/common/models/Product.php +++ b/common/models/Product.php @@ -15,6 +15,7 @@ use function Symfony\Component\String\s; * @property int|null $type * @property int|null $price * @property int|null $status + * @property int $product_category_id * * @property Company $company */ @@ -51,7 +52,7 @@ class Product extends \yii\db\ActiveRecord /** * {@inheritdoc} */ - public static function tableName() + public static function tableName(): string { return 'product'; } @@ -59,11 +60,11 @@ class Product extends \yii\db\ActiveRecord /** * {@inheritdoc} */ - public function rules() + public function rules(): array { return [ - [['title', 'article', 'company_id'], 'required'], - [['company_id', 'type', 'price', 'status'], 'integer'], + [['title', 'article', 'company_id', 'product_category_id'], 'required'], + [['company_id', 'type', 'price', 'status', 'product_category_id'], 'integer'], [['title', 'article'], 'string', 'max' => 255], [['company_id'], 'exist', 'skipOnError' => true, 'targetClass' => Company::class, 'targetAttribute' => ['company_id' => 'id']], ]; @@ -72,7 +73,7 @@ class Product extends \yii\db\ActiveRecord /** * {@inheritdoc} */ - public function attributeLabels() + public function attributeLabels(): array { return [ 'id' => 'ID', @@ -82,6 +83,7 @@ class Product extends \yii\db\ActiveRecord 'type' => 'Тип', 'price' => 'Цена', 'status' => 'Статус', + 'product_category_id' => 'Категория', ]; } @@ -90,8 +92,16 @@ class Product extends \yii\db\ActiveRecord * * @return \yii\db\ActiveQuery */ - public function getCompany() + public function getCompany(): \yii\db\ActiveQuery { return $this->hasOne(Company::class, ['id' => 'company_id']); } + + /** + * @return \yii\db\ActiveQuery + */ + public function getProductCategory(): \yii\db\ActiveQuery + { + return $this->hasOne(ProductCategory::class, ['id' => 'product_category_id']); + } } diff --git a/common/services/CompanyService.php b/common/services/CompanyService.php index 0e76fe3..87abe71 100644 --- a/common/services/CompanyService.php +++ b/common/services/CompanyService.php @@ -3,6 +3,7 @@ namespace common\services; use common\models\Company; +use common\models\ProductCategory; use Yii; use yii\helpers\ArrayHelper; @@ -61,4 +62,14 @@ class CompanyService return ArrayHelper::map($this->getAddressesByUser($id), 'id', 'address'); } + /** + * @param int $id + * @return array + */ + public function getCategoryByCompanyId(int $id): array + { + return ProductCategory::find()->where(['company_id' => $id])->all(); + } + + } \ No newline at end of file diff --git a/common/services/ProductService.php b/common/services/ProductService.php new file mode 100644 index 0000000..9497016 --- /dev/null +++ b/common/services/ProductService.php @@ -0,0 +1,30 @@ +model = new Product(); + } + + /** + * @param int $company_id + * @param int|null $category_id + * @return array + */ + public function findBy(int $company_id, int $category_id = null): array + { + return $this->model->find()->where(['company_id' => $company_id]) + ->andWhere(['status' => Product::STATUS_ACTIVE]) + ->andFilterWhere(['product_category_id' => $category_id]) + ->all(); + } + +} \ No newline at end of file diff --git a/composer.json b/composer.json index f47f179..e405d22 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,9 @@ "yiisoft/yii2": "~2.0.45", "yiisoft/yii2-bootstrap5": "~2.0.2", "yiisoft/yii2-symfonymailer": "~2.0.3", - "hail812/yii2-adminlte3": "~1.1" + "hail812/yii2-adminlte3": "~1.1", + "zircote/swagger-php": "^4.9", + "doctrine/annotations": "^2.0" }, "require-dev": { "yiisoft/yii2-debug": "~2.1.0", diff --git a/composer.lock b/composer.lock index fd69d6d..df4d2cd 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "825cc56cf0c423874be51a9d82a526fe", + "content-hash": "ee2265371cea7b99aa6147cb7830ae18", "packages": [ { "name": "almasaeed2010/adminlte", @@ -203,6 +203,82 @@ }, "time": "2018-03-26T11:24:36+00:00" }, + { + "name": "doctrine/annotations", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", + "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2 || ^3", + "ext-tokenizer": "*", + "php": "^7.2 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^2.0", + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^5.4 || ^6", + "vimeo/psalm": "^4.10" + }, + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/2.0.1" + }, + "time": "2023-02-02T22:02:53+00:00" + }, { "name": "doctrine/lexer", "version": "3.0.0", @@ -564,6 +640,55 @@ }, "time": "2020-10-15T08:29:30+00:00" }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, { "name": "psr/container", "version": "2.0.2", @@ -940,6 +1065,70 @@ ], "time": "2023-05-23T14:45:45+00:00" }, + { + "name": "symfony/finder", + "version": "v6.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "11d736e97f116ac375a81f96e662911a34cd50ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/11d736e97f116ac375a81f96e662911a34cd50ce", + "reference": "11d736e97f116ac375a81f96e662911a34cd50ce", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v6.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-10-31T17:30:12+00:00" + }, { "name": "symfony/mailer", "version": "v6.4.0", @@ -1104,6 +1293,88 @@ ], "time": "2023-10-17T11:49:05+00:00" }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-26T09:26:14+00:00" + }, { "name": "symfony/polyfill-intl-idn", "version": "v1.28.0", @@ -1516,6 +1787,78 @@ ], "time": "2023-07-30T20:28:31+00:00" }, + { + "name": "symfony/yaml", + "version": "v6.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "4f9237a1bb42455d609e6687d2613dde5b41a587" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/4f9237a1bb42455d609e6687d2613dde5b41a587", + "reference": "4f9237a1bb42455d609e6687d2613dde5b41a587", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<5.4" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0|^7.0" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v6.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-11-06T11:00:25+00:00" + }, { "name": "yiisoft/yii2", "version": "2.0.49.3", @@ -1969,6 +2312,87 @@ } ], "time": "2022-09-04T10:48:21+00:00" + }, + { + "name": "zircote/swagger-php", + "version": "4.9.2", + "source": { + "type": "git", + "url": "https://github.com/zircote/swagger-php.git", + "reference": "256d42cb07ba1c2206d66bc7516ee3d3e3e9f0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/256d42cb07ba1c2206d66bc7516ee3d3e3e9f0b2", + "reference": "256d42cb07ba1c2206d66bc7516ee3d3e3e9f0b2", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=7.2", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "symfony/deprecation-contracts": "^2 || ^3", + "symfony/finder": ">=2.2", + "symfony/yaml": ">=3.3" + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.11", + "doctrine/annotations": "^1.7 || ^2.0", + "friendsofphp/php-cs-fixer": "^2.17 || ^3.47.1", + "phpstan/phpstan": "^1.6", + "phpunit/phpunit": ">=8", + "vimeo/psalm": "^4.23" + }, + "suggest": { + "doctrine/annotations": "^1.7 || ^2.0" + }, + "bin": [ + "bin/openapi" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "OpenApi\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Robert Allen", + "email": "zircote@gmail.com" + }, + { + "name": "Bob Fanger", + "email": "bfanger@gmail.com", + "homepage": "https://bfanger.nl" + }, + { + "name": "Martin Rademacher", + "email": "mano@radebatz.net", + "homepage": "https://radebatz.net" + } + ], + "description": "swagger-php - Generate interactive documentation for your RESTful API using phpdoc annotations", + "homepage": "https://github.com/zircote/swagger-php/", + "keywords": [ + "api", + "json", + "rest", + "service discovery" + ], + "support": { + "issues": "https://github.com/zircote/swagger-php/issues", + "source": "https://github.com/zircote/swagger-php/tree/4.9.2" + }, + "time": "2024-05-02T21:36:00+00:00" } ], "packages-dev": [ @@ -5074,152 +5498,6 @@ ], "time": "2023-11-20T16:41:16+00:00" }, - { - "name": "symfony/finder", - "version": "v6.4.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "11d736e97f116ac375a81f96e662911a34cd50ce" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/11d736e97f116ac375a81f96e662911a34cd50ce", - "reference": "11d736e97f116ac375a81f96e662911a34cd50ce", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "symfony/filesystem": "^6.0|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Finds files and directories via an intuitive fluent interface", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-10-31T17:30:12+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.28.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-01-26T09:26:14+00:00" - }, { "name": "symfony/polyfill-intl-grapheme", "version": "v1.28.0", @@ -5472,78 +5750,6 @@ ], "time": "2023-11-09T08:28:32+00:00" }, - { - "name": "symfony/yaml", - "version": "v6.4.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "4f9237a1bb42455d609e6687d2613dde5b41a587" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/4f9237a1bb42455d609e6687d2613dde5b41a587", - "reference": "4f9237a1bb42455d609e6687d2613dde5b41a587", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "symfony/console": "<5.4" - }, - "require-dev": { - "symfony/console": "^5.4|^6.0|^7.0" - }, - "bin": [ - "Resources/bin/yaml-lint" - ], - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Loads and dumps YAML files", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/yaml/tree/v6.4.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-11-06T11:00:25+00:00" - }, { "name": "theseer/tokenizer", "version": "1.2.2", diff --git a/console/controllers/SwaggerController.php b/console/controllers/SwaggerController.php new file mode 100644 index 0000000..36a34db --- /dev/null +++ b/console/controllers/SwaggerController.php @@ -0,0 +1,26 @@ +toYaml()); + fclose($handle); + echo $this->ansiFormat('Created \n", Console::FG_BLUE'); + return ExitCode::OK; + } + +} \ No newline at end of file diff --git a/console/migrations/m240507_141733_add_category_id_column_at_product_table.php b/console/migrations/m240507_141733_add_category_id_column_at_product_table.php new file mode 100644 index 0000000..bf8e946 --- /dev/null +++ b/console/migrations/m240507_141733_add_category_id_column_at_product_table.php @@ -0,0 +1,62 @@ +addColumn('product', 'product_category_id', $this->integer(11)->defaultValue(0)->notNull()); + + $this->createIndex('idx-product-product_category_id', 'product', 'product_category_id'); + + $this->addForeignKey( + 'fk-product-product_category_id', + 'product', + 'product_category_id', + 'product_category', + 'id', + 'CASCADE' + ); + } + + /** + * {@inheritdoc} + */ + public function safeDown() + { + $this->dropForeignKey( + 'fk-product-product_category_id', + 'product' + ); + + // drops index for column `author_id` + $this->dropIndex( + 'idx-product-product_category_id', + 'product' + ); + + $this->dropColumn('product', 'product_category_id'); + } + + /* + // Use up()/down() to run migration code without a transaction. + public function up() + { + + } + + public function down() + { + echo "m240507_141733_add_category_id_column_at_product_table cannot be reverted.\n"; + + return false; + } + */ +} diff --git a/frontend/assets/AppAsset.php b/frontend/assets/AppAsset.php index f8478f6..0f00769 100644 --- a/frontend/assets/AppAsset.php +++ b/frontend/assets/AppAsset.php @@ -15,6 +15,7 @@ class AppAsset extends AssetBundle 'css/site.css', ]; public $js = [ + 'js/company_select.js' ]; public $depends = [ 'yii\web\YiiAsset', diff --git a/frontend/assets/MainAsset.php b/frontend/assets/MainAsset.php new file mode 100644 index 0000000..4898e15 --- /dev/null +++ b/frontend/assets/MainAsset.php @@ -0,0 +1,16 @@ + [ 'class' => 'frontend\modules\product_category\ProductCategory', ], + 'api' => [ + 'class' => 'frontend\modules\api\Api', + ], ], 'components' => [ 'request' => [ diff --git a/frontend/modules/api/Api.php b/frontend/modules/api/Api.php new file mode 100644 index 0000000..df9e98a --- /dev/null +++ b/frontend/modules/api/Api.php @@ -0,0 +1,24 @@ + [ + 'class' => GsCors::class, + 'cors' => [ + 'Origin' => ['*'], + //'Access-Control-Allow-Credentials' => true, + 'Access-Control-Allow-Headers' => [ + 'Access-Control-Allow-Origin', + 'Access-Control-Allow-Methods', + 'Content-Type', + 'Access-Control-Allow-Headers', + 'Authorization', + 'X-Requested-With' + ], + 'Access-Control-Allow-Methods' => ['POST', 'GET', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], + 'Access-Control-Request-Method' => ['POST', 'GET', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], + ] + ], + 'authenticator' => [ + 'class' => CompositeAuth::class, + 'authMethods' => [ + HttpBearerAuth::class, + ], + ], + [ + 'class' => ContentNegotiator::className(), + 'formats' => [ + 'application/json' => Response::FORMAT_JSON, + ], + ], + ]; + } + +} \ No newline at end of file diff --git a/frontend/modules/api/controllers/CategoryController.php b/frontend/modules/api/controllers/CategoryController.php new file mode 100644 index 0000000..9811e91 --- /dev/null +++ b/frontend/modules/api/controllers/CategoryController.php @@ -0,0 +1,82 @@ +companyService = new CompanyService(); + } + + public function behaviors(): array + { + $behaviors = parent::behaviors(); + unset($behaviors['authenticator']); + return $behaviors; + } + + /** + * + * @OA\Get(path="/category", + * summary="Список категорий компании", + * description="Получить список всех категорий компании", + * tags={"Category"}, + * @OA\Parameter( + * name="company_id", + * in="query", + * required=true, + * @OA\Schema( + * type="integer", + * default=null + * ) + * ), + * @OA\Response( + * response=200, + * description="Возвращает массив", + * @OA\MediaType( + * mediaType="application/json", + * @OA\Schema( + * type="array", + * @OA\Items( + * @OA\Property( + * property="id", + * type="integer", + * example="1", + * ), + * @OA\Property( + * property="title", + * type="string", + * example="Кальянный клуб LA", + * ), + * @OA\Property( + * property="parent_id", + * type="integer", + * example="33", + * ), + * @OA\Property( + * property="company_id", + * type="integer", + * example="23", + * ), + * ) + * ), + * ), + * + * ), + * ) + * + * @return array + */ + public function actionIndex(int $company_id): array + { + return $this->companyService->getCategoryByCompanyId($company_id); + } + +} diff --git a/frontend/modules/api/controllers/DefaultController.php b/frontend/modules/api/controllers/DefaultController.php new file mode 100644 index 0000000..271158c --- /dev/null +++ b/frontend/modules/api/controllers/DefaultController.php @@ -0,0 +1,20 @@ +render('index'); + } +} diff --git a/frontend/modules/api/controllers/ProductController.php b/frontend/modules/api/controllers/ProductController.php new file mode 100644 index 0000000..2ca018e --- /dev/null +++ b/frontend/modules/api/controllers/ProductController.php @@ -0,0 +1,114 @@ +companyService = new CompanyService(); + $this->productService = new ProductService(); + } + + public function behaviors(): array + { + $behaviors = parent::behaviors(); + unset($behaviors['authenticator']); + return $behaviors; + } + + /** + * + * @OA\Get(path="/product", + * summary="Список товаров компании", + * description="Получить список всех товаров компании", + * tags={"Product"}, + * @OA\Parameter( + * name="company_id", + * in="query", + * required=true, + * @OA\Schema( + * type="integer", + * default=null + * ) + * ), + * @OA\Parameter( + * name="category_id", + * in="query", + * required=false, + * @OA\Schema( + * type="integer", + * default=null + * ) + * ), + * @OA\Response( + * response=200, + * description="Возвращает массив", + * @OA\MediaType( + * mediaType="application/json", + * @OA\Schema( + * type="array", + * @OA\Items( + * @OA\Property( + * property="id", + * type="integer", + * example="1", + * ), + * @OA\Property( + * property="title", + * type="string", + * example="Кальянный клуб LA", + * ), + * @OA\Property( + * property="article", + * type="string", + * example="005", + * ), + * @OA\Property( + * property="type", + * type="integer", + * example="1", + * ), + * @OA\Property( + * property="price", + * type="integer", + * example="250", + * ), + * @OA\Property( + * property="status", + * type="integer", + * example="1", + * ), + * @OA\Property( + * property="product_category_id", + * type="integer", + * example="5", + * ), + * @OA\Property( + * property="company_id", + * type="integer", + * example="23", + * ), + * ) + * ), + * ), + * + * ), + * ) + * + * @return array + */ + public function actionIndex(int $company_id, int $category_id = null): array + { + return $this->productService->findBy($company_id, $category_id); + } + +} \ No newline at end of file diff --git a/frontend/modules/api/views/default/index.php b/frontend/modules/api/views/default/index.php new file mode 100644 index 0000000..aa260c4 --- /dev/null +++ b/frontend/modules/api/views/default/index.php @@ -0,0 +1,12 @@ +
+

context->action->uniqueId ?>

+

+ This is the view content for action "context->action->id ?>". + The action belongs to the controller "context) ?>" + in the "context->module->id ?>" module. +

+

+ You may customize this page by editing the following file:
+ +

+
diff --git a/frontend/modules/product/models/ProductSearch.php b/frontend/modules/product/models/ProductSearch.php index fc7ba37..e6fdd65 100644 --- a/frontend/modules/product/models/ProductSearch.php +++ b/frontend/modules/product/models/ProductSearch.php @@ -14,10 +14,10 @@ class ProductSearch extends Product /** * {@inheritdoc} */ - public function rules() + public function rules(): array { return [ - [['id', 'company_id', 'type', 'price', 'status'], 'integer'], + [['id', 'company_id', 'type', 'price', 'status', 'product_category_id'], 'integer'], [['title', 'article'], 'safe'], ]; } diff --git a/frontend/modules/product/views/product/_form.php b/frontend/modules/product/views/product/_form.php index aeca88c..2fcb352 100644 --- a/frontend/modules/product/views/product/_form.php +++ b/frontend/modules/product/views/product/_form.php @@ -17,7 +17,9 @@ use yii\bootstrap4\ActiveForm; field($model, 'article')->textInput(['maxlength' => true]) ?> - field($model, 'company_id')->dropDownList($companies) ?> + field($model, 'company_id')->dropDownList($companies, ['id' => 'company_select']) ?> + +
field($model, 'type')->dropDownList(\common\models\Product::getType()) ?> diff --git a/frontend/modules/product/views/product/view.php b/frontend/modules/product/views/product/view.php index 86e2e40..000d59c 100644 --- a/frontend/modules/product/views/product/view.php +++ b/frontend/modules/product/views/product/view.php @@ -40,6 +40,10 @@ $this->params['breadcrumbs'][] = $this->title; 'attribute' => 'company_id', 'value' => $companyService->getCompany($model->company_id)->name ?? null ], + [ + 'attribute' => 'product_category_id', + 'value' => $model->getProductCategory()->one()->title ?? null + ], [ 'attribute' => 'type', 'value' => \common\models\Product::getType()[$model->type] diff --git a/frontend/modules/product_category/controllers/ProductCategoryController.php b/frontend/modules/product_category/controllers/ProductCategoryController.php index 9c09e16..6a11450 100644 --- a/frontend/modules/product_category/controllers/ProductCategoryController.php +++ b/frontend/modules/product_category/controllers/ProductCategoryController.php @@ -4,6 +4,7 @@ namespace frontend\modules\product_category\controllers; use common\classes\Debug; use common\models\User; +use common\services\CompanyService; use Yii; use frontend\modules\product_category\models\ProductCategory; use frontend\modules\product_category\models\ProductCategorySearch; @@ -17,6 +18,14 @@ use yii\filters\VerbFilter; */ class ProductCategoryController extends Controller { + public CompanyService $companyService; + + public function init() + { + parent::init(); + $this->companyService = new CompanyService(); + } + /** * {@inheritdoc} */ @@ -114,6 +123,16 @@ class ProductCategoryController extends Controller return $this->redirect(['index']); } + /** + * @param $company_id + * @return string + */ + public function actionGetCategorySelectByCompanyId($company_id): string + { + $category = $this->companyService->getCategoryByCompanyId($company_id); + return $this->renderAjax('_category_select', ['category' => $category]); + } + /** * Finds the ProductCategory model based on its primary key value. * If the model is not found, a 404 HTTP exception will be thrown. diff --git a/frontend/modules/product_category/views/product-category/_category_select.php b/frontend/modules/product_category/views/product-category/_category_select.php new file mode 100644 index 0000000..6eb4056 --- /dev/null +++ b/frontend/modules/product_category/views/product-category/_category_select.php @@ -0,0 +1,15 @@ + 'form-control', + 'id' => 'product_category', + ] +); \ No newline at end of file diff --git a/frontend/modules/product_category/views/product-category/view.php b/frontend/modules/product_category/views/product-category/view.php index 8dc5a28..6e86585 100644 --- a/frontend/modules/product_category/views/product-category/view.php +++ b/frontend/modules/product_category/views/product-category/view.php @@ -18,8 +18,9 @@ $this->params['breadcrumbs'][] = $this->title;

- $model->id], ['class' => 'btn btn-primary']) ?> - $model->id], [ + 'btn btn-primary']) ?> + $model->id], ['class' => 'btn btn-primary']) ?> + $model->id], [ 'class' => 'btn btn-danger', 'data' => [ 'confirm' => 'Are you sure you want to delete this item?', diff --git a/frontend/views/layouts/main.php b/frontend/views/layouts/main.php index 80ca4b5..2a973ad 100644 --- a/frontend/views/layouts/main.php +++ b/frontend/views/layouts/main.php @@ -7,6 +7,7 @@ use yii\helpers\Html; \hail812\adminlte3\assets\FontAwesomeAsset::register($this); \hail812\adminlte3\assets\AdminLteAsset::register($this); +\frontend\assets\MainAsset::register($this); $this->registerCssFile('https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700&display=fallback'); $assetDir = Yii::$app->assetManager->getPublishedUrl('@vendor/almasaeed2010/adminlte/dist'); diff --git a/frontend/web/api-doc/.agignore b/frontend/web/api-doc/.agignore new file mode 100644 index 0000000..849ddff --- /dev/null +++ b/frontend/web/api-doc/.agignore @@ -0,0 +1 @@ +dist/ diff --git a/frontend/web/api-doc/.commitlintrc.json b/frontend/web/api-doc/.commitlintrc.json new file mode 100644 index 0000000..a55865b --- /dev/null +++ b/frontend/web/api-doc/.commitlintrc.json @@ -0,0 +1,25 @@ +{ + "extends": [ + "@commitlint/config-conventional" + ], + "rules": { + "header-max-length": [ + 2, + "always", + 69 + ], + "scope-case": [ + 2, + "always", + [ + "camel-case", + "kebab-case", + "upper-case" + ] + ], + "subject-case": [ + 0, + "always" + ] + } +} diff --git a/frontend/web/api-doc/.dockerignore b/frontend/web/api-doc/.dockerignore new file mode 100644 index 0000000..47c624e --- /dev/null +++ b/frontend/web/api-doc/.dockerignore @@ -0,0 +1,8 @@ +/.git +/.github +/dev-helpers +/docs +/src +/swagger-ui-dist-package +/test +/node_modules \ No newline at end of file diff --git a/frontend/web/api-doc/.editorconfig b/frontend/web/api-doc/.editorconfig new file mode 100644 index 0000000..2d95946 --- /dev/null +++ b/frontend/web/api-doc/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +end_of_line = lf +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +insert_final_newline = true +[*.md] +trim_trailing_whitespace = false diff --git a/frontend/web/api-doc/.eslintignore b/frontend/web/api-doc/.eslintignore new file mode 100644 index 0000000..1eae0cf --- /dev/null +++ b/frontend/web/api-doc/.eslintignore @@ -0,0 +1,2 @@ +dist/ +node_modules/ diff --git a/frontend/web/api-doc/.eslintrc b/frontend/web/api-doc/.eslintrc new file mode 100644 index 0000000..502479e --- /dev/null +++ b/frontend/web/api-doc/.eslintrc @@ -0,0 +1,40 @@ +parser: "@babel/eslint-parser" +env: + browser: true + node: true + es6: true + jest: true + jest/globals: true +parserOptions: + ecmaFeatures: + jsx: true +extends: + - eslint:recommended + - plugin:react/recommended +plugins: + - react + - mocha + - import + - jest +settings: + react: + pragma: React + version: '15.0' +rules: + semi: [2, never] + strict: 0 + quotes: [2, double, { allowTemplateLiterals: true }] + no-unused-vars: 2 + no-multi-spaces: 1 + camelcase: 1 + no-use-before-define: [2, nofunc] + no-underscore-dangle: 0 + no-unused-expressions: 1 + comma-dangle: 0 + no-console: [2, { allow: [warn, error] }] + react/jsx-no-bind: 1 + react/jsx-no-target-blank: 2 + react/display-name: 0 + mocha/no-exclusive-tests: 2 + import/no-extraneous-dependencies: 2 + react/jsx-filename-extension: 2 \ No newline at end of file diff --git a/frontend/web/api-doc/.gitattributes b/frontend/web/api-doc/.gitattributes new file mode 100644 index 0000000..dad06c5 --- /dev/null +++ b/frontend/web/api-doc/.gitattributes @@ -0,0 +1 @@ +docker-run.sh text eol=lf diff --git a/frontend/web/api-doc/.github/ISSUE_TEMPLATE/Bug_report.md b/frontend/web/api-doc/.github/ISSUE_TEMPLATE/Bug_report.md new file mode 100644 index 0000000..48861bf --- /dev/null +++ b/frontend/web/api-doc/.github/ISSUE_TEMPLATE/Bug_report.md @@ -0,0 +1,73 @@ +--- +name: Bug report +about: Report an issue you're experiencing + +--- + + + +### Q&A (please complete the following information) + - OS: [e.g. macOS] + - Browser: [e.g. chrome, safari] + - Version: [e.g. 22] + - Method of installation: [e.g. npm, dist assets] + - Swagger-UI version: [e.g. 3.10.0] + - Swagger/OpenAPI version: [e.g. Swagger 2.0, OpenAPI 3.0] + +### Content & configuration + + +Example Swagger/OpenAPI definition: +```yaml +# your YAML here +``` + +Swagger-UI configuration options: +```js +SwaggerUI({ + // your config options here +}) +``` + +``` +?yourQueryStringConfig +``` + +### Describe the bug you're encountering + + +### To reproduce... + +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +### Expected behavior + + +### Screenshots + + +### Additional context or thoughts + diff --git a/frontend/web/api-doc/.github/ISSUE_TEMPLATE/Feature_request.md b/frontend/web/api-doc/.github/ISSUE_TEMPLATE/Feature_request.md new file mode 100644 index 0000000..5b18f43 --- /dev/null +++ b/frontend/web/api-doc/.github/ISSUE_TEMPLATE/Feature_request.md @@ -0,0 +1,42 @@ +--- +name: Feature request +about: Suggest a new feature or enhancement for this project + +--- + + ### Content & configuration + +Swagger/OpenAPI definition: + ```yaml + # your YAML here + ``` + + Swagger-UI configuration options: + ```js + SwaggerUI({ + // your config options here + }) + ``` + + ``` + ?yourQueryStringConfig + ``` + + +### Is your feature request related to a problem? + + +### Describe the solution you'd like + + +### Describe alternatives you've considered + + +### Additional context + diff --git a/frontend/web/api-doc/.github/ISSUE_TEMPLATE/Support.md b/frontend/web/api-doc/.github/ISSUE_TEMPLATE/Support.md new file mode 100644 index 0000000..92eb5e9 --- /dev/null +++ b/frontend/web/api-doc/.github/ISSUE_TEMPLATE/Support.md @@ -0,0 +1,46 @@ +--- +name: Support +about: Ask a question or request help with your implementation. + +--- + + + +### Q&A (please complete the following information) + - OS: [e.g. macOS] + - Browser: [e.g. chrome, safari] + - Version: [e.g. 22] + - Method of installation: [e.g. npm, dist assets] + - Swagger-UI version: [e.g. 3.10.0] + - Swagger/OpenAPI version: [e.g. Swagger 2.0, OpenAPI 3.0] + +### Content & configuration + + +Swagger/OpenAPI definition: +```yaml +# your YAML here +``` + +Swagger-UI configuration options: +```js +SwaggerUI({ + // your config options here +}) +``` + +``` +?yourQueryStringConfig +``` + +### Screenshots + + +### How can we help? + diff --git a/frontend/web/api-doc/.github/dependabot.yaml b/frontend/web/api-doc/.github/dependabot.yaml new file mode 100644 index 0000000..888c20a --- /dev/null +++ b/frontend/web/api-doc/.github/dependabot.yaml @@ -0,0 +1,23 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: "/" + schedule: + interval: daily + commit-message: + prefix: "chore" + include: "scope" + open-pull-requests-limit: 3 + ignore: + # node-fetch must be synced manually + - dependency-name: "node-fetch" + - dependency-name: "release-it" + - dependency-name: "@release-it/conventional-changelog" + + - package-ecosystem: "docker" + # Look for a `Dockerfile` in the `root` directory + directory: "/" + # Check for updates once a week + schedule: + interval: "weekly" + diff --git a/frontend/web/api-doc/.github/lock.yml b/frontend/web/api-doc/.github/lock.yml new file mode 100644 index 0000000..e0b8c11 --- /dev/null +++ b/frontend/web/api-doc/.github/lock.yml @@ -0,0 +1,15 @@ +daysUntilLock: 365 +skipCreatedBefore: 2017-03-29 # initial release of Swagger UI 3.0.0 +exemptLabels: [] +lockLabel: "locked-by: lock-bot" +setLockReason: false +only: issues +lockComment: false +# lockComment: | +# Locking due to inactivity. + +# This is done to avoid resurrecting old issues and bumping long threads with new, possibly unrelated content. + +# If you think you're experiencing something similar to what you've found here: please [open a new issue](https://github.com/swagger-api/swagger-ui/issues/new/choose), follow the template, and reference this issue in your report. + +# Thanks! diff --git a/frontend/web/api-doc/.github/pull_request_template.md b/frontend/web/api-doc/.github/pull_request_template.md new file mode 100644 index 0000000..64e9cf6 --- /dev/null +++ b/frontend/web/api-doc/.github/pull_request_template.md @@ -0,0 +1,55 @@ + + +### Description + + + + +### Motivation and Context + + + + + + + +### How Has This Been Tested? + + + + + + +### Screenshots (if appropriate): + + + +## Checklist + + + +### My PR contains... + +- [ ] No code changes (`src/` is unmodified: changes to documentation, CI, metadata, etc.) +- [ ] Dependency changes (any modification to dependencies in `package.json`) +- [ ] Bug fixes (non-breaking change which fixes an issue) +- [ ] Improvements (misc. changes to existing features) +- [ ] Features (non-breaking change which adds functionality) + +### My changes... +- [ ] are breaking changes to a public API (config options, System API, major UI change, etc). +- [ ] are breaking changes to a private API (Redux, component props, utility functions, etc.). +- [ ] are breaking changes to a developer API (npm script behavior changes, new dev system dependencies, etc). +- [ ] are not breaking changes. + +### Documentation +- [ ] My changes do not require a change to the project documentation. +- [ ] My changes require a change to the project documentation. +- [ ] If yes to above: I have updated the documentation accordingly. + +### Automated tests +- [ ] My changes can not or do not need to be tested. +- [ ] My changes can and should be tested by unit and/or integration tests. +- [ ] If yes to above: I have added tests to cover my changes. +- [ ] If yes to above: I have taken care to cover edge cases in my tests. +- [ ] All new and existing tests passed. diff --git a/frontend/web/api-doc/.github/workflows/dependabot-merge.yml b/frontend/web/api-doc/.github/workflows/dependabot-merge.yml new file mode 100644 index 0000000..bed017b --- /dev/null +++ b/frontend/web/api-doc/.github/workflows/dependabot-merge.yml @@ -0,0 +1,37 @@ +name: Merge me! + +on: + pull_request_target: + branches: [ master, next ] + +jobs: + merge-me: + name: Merge me! + if: github.actor == 'dependabot[bot]' + runs-on: ubuntu-latest + steps: + # This first step will fail if there's no metadata and so the approval + # will not occur. + - name: Dependabot metadata + id: dependabot-metadata + uses: dependabot/fetch-metadata@v1.1.1 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + # Here the PR gets approved. + - name: Approve a PR + if: ${{ steps.dependabot-metadata.outputs.update-type != 'version-update:semver-major' }} + run: gh pr review --approve "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GITHUB_TOKEN: ${{ secrets.SWAGGER_BOT_GITHUB_TOKEN }} + # Finally, tell dependabot to merge the PR if all checks are successful + - name: Instruct dependabot to squash & merge + if: ${{ steps.dependabot-metadata.outputs.update-type != 'version-update:semver-major' }} + uses: mshick/add-pr-comment@v2 + with: + repo-token: ${{ secrets.SWAGGER_BOT_GITHUB_TOKEN }} + allow-repeats: true + message: | + @dependabot squash and merge + env: + GITHUB_TOKEN: ${{ secrets.SWAGGER_BOT_GITHUB_TOKEN }} diff --git a/frontend/web/api-doc/.github/workflows/docker-image-check.yml b/frontend/web/api-doc/.github/workflows/docker-image-check.yml new file mode 100644 index 0000000..60588dd --- /dev/null +++ b/frontend/web/api-doc/.github/workflows/docker-image-check.yml @@ -0,0 +1,20 @@ +name: Security scan for docker image + +on: + workflow_dispatch: + schedule: + - cron: '30 4 * * *' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + image-ref: 'docker.io/swaggerapi/swagger-ui:unstable' + format: 'table' + exit-code: '1' + ignore-unfixed: true + vuln-type: 'os,library' + severity: 'CRITICAL,HIGH' diff --git a/frontend/web/api-doc/.github/workflows/nodejs.yml b/frontend/web/api-doc/.github/workflows/nodejs.yml new file mode 100644 index 0000000..7460c34 --- /dev/null +++ b/frontend/web/api-doc/.github/workflows/nodejs.yml @@ -0,0 +1,83 @@ +# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions + +name: Node.js CI + +on: + push: + branches: [ master, next ] + pull_request: + branches: [ master, next ] + +env: + CYPRESS_CACHE_FOLDER: cypress/cache + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Use Node.js 16.x + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Cache Node Modules and Cypress binary + uses: actions/cache@v3 + id: cache-primes + with: + path: | + node_modules + ${{ env.CYPRESS_CACHE_FOLDER }} + key: ${{ runner.os }}-node-and-cypress-${{ hashFiles('package-lock.json') }} + + - name: Install dependencies + if: steps.cache-primes.outputs.cache-hit != 'true' + run: npm ci + + - name: Lint code for errors only + run: npm run lint-errors + + - name: Run all tests + run: npm run just-test-in-node && npm run test:unit-jest + env: + CI: true + + - name: Build SwaggerUI + run: npm run build + + - name: Test build artifacts + run: npm run test:artifact + + e2e-tests: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + containers: ['+(a11y|security|bugs)/**/*.js', 'features/**/+(o|d)*.js', 'features/**/m*.js', 'features/**/!(o|d|m)*.js'] + + steps: + - uses: actions/checkout@v3 + + - name: Use Node.js 16.x + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Cache Node Modules and Cypress binary + uses: actions/cache@v3 + id: cache-primes + with: + path: | + node_modules + ${{ env.CYPRESS_CACHE_FOLDER }} + key: ${{ runner.os }}-node-and-cypress-${{ hashFiles('package-lock.json') }} + + - name: Install dependencies + if: steps.cache-primes.outputs.cache-hit != 'true' + run: npm ci + + - name: Cypress Test + run: npx start-server-and-test cy:start http://localhost:3204 'npm run cy:run -- --spec "test/e2e-cypress/tests/${{ matrix.containers }}"' diff --git a/frontend/web/api-doc/.github/workflows/release-swagger-ui-react.yml b/frontend/web/api-doc/.github/workflows/release-swagger-ui-react.yml new file mode 100644 index 0000000..442322f --- /dev/null +++ b/frontend/web/api-doc/.github/workflows/release-swagger-ui-react.yml @@ -0,0 +1,79 @@ +name: Build & Release SwaggerUI-React@next + +# single-stage +on: + workflow_dispatch: + branches: + - next + +# multi-stage automation +# on: +# workflow_run: +# workflows: ["Release SwaggerUI@next"] +# types: +# - completed +# branches: [next] + +defaults: + run: + working-directory: flavors/swagger-ui-react/release +jobs: + release-swagger-ui-react: + name: Release SwaggerUI React + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + persist-credentials: false + ref: next + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Prepare SwaggerUI dist + run: | + cd ../../../ + npm ci + npm run build + + - name: Install dependencies (to create package manifest) + run: npm ci + + - name: MKDIR `dist` working directory + run: mkdir -p ../dist + + - name: Copy SwaggerUI dist files to MKDIR + run: | + ls ../dist + cp ../../../dist/swagger-ui-es-bundle-core.js ../dist + cp ../../../dist/swagger-ui-es-bundle-core.js.map ../dist + cp ../../../dist/swagger-ui.css ../dist + cp ../../../dist/swagger-ui.css.map ../dist + + - name: Create a releasable package manifest + run: node create-manifest.js > ../dist/package.json + + - name: Transpile our top-level React Component + run: | + ../../../node_modules/.bin/cross-env BABEL_ENV=commonjs ../../../node_modules/.bin/babel --config-file ../../../babel.config.js ../index.jsx > ../dist/commonjs.js + ../../../node_modules/.bin/cross-env BABEL_ENV=es ../../../node_modules/.bin/babel --config-file ../../../babel.config.js ../index.jsx > ../dist/index.js + + - name: Copy our README into the dist folder for npm + run: cp ../README.md ../dist + + - name: Copy LICENSE & NOTICE into the dist folder for npm + run: | + cp ../../../LICENSE ../dist + cp ../../../NOTICE ../dist + + - name: Run the release from the dist folder + run: | + cd ../dist + pwd + npm pack . --tag alpha + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/frontend/web/api-doc/.github/workflows/release-swagger-ui.yml b/frontend/web/api-doc/.github/workflows/release-swagger-ui.yml new file mode 100644 index 0000000..d55149d --- /dev/null +++ b/frontend/web/api-doc/.github/workflows/release-swagger-ui.yml @@ -0,0 +1,73 @@ +name: Release SwaggerUI@next + +on: + workflow_dispatch: + branches: + - next + +jobs: + release-swagger-ui: + name: Release SwaggerUI + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + persist-credentials: false + ref: next + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Determine the next release version + uses: cycjimmy/semantic-release-action@v3 + with: + dry_run: true + extra_plugins: | + @semantic-release/git + @semantic-release/exec + env: + GITHUB_TOKEN: ${{ secrets.SWAGGER_BOT_GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Nothing to release + if: ${{ env.NEXT_RELEASE_VERSION == '' }} + uses: actions/github-script@v6 + with: + script: | + core.setFailed('Nothing to release') + + - name: Install dependencies + run: npm ci + + - name: Prepare release + run: | + npm run build + + - name: Semantic Release + id: semantic + uses: cycjimmy/semantic-release-action@v3 + with: + dry_run: false + extra_plugins: | + @semantic-release/git + env: + GITHUB_TOKEN: ${{ secrets.SWAGGER_BOT_GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Release failed + if: steps.semantic.outputs.new_release_published == 'false' + uses: actions/github-script@v6 + with: + script: | + core.setFailed('Release failed') + + - name: Release published + run: | + echo ${{ steps.semantic.outputs.new_release_version }} + echo ${{ steps.semantic.outputs.new_release_major_version }} + echo ${{ steps.semantic.outputs.new_release_minor_version }} + echo ${{ steps.semantic.outputs.new_release_patch_version }} diff --git a/frontend/web/api-doc/.gitignore b/frontend/web/api-doc/.gitignore new file mode 100644 index 0000000..c9af1ad --- /dev/null +++ b/frontend/web/api-doc/.gitignore @@ -0,0 +1,28 @@ +node_modules +.idea +.vscode +.deps_check +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local +.nyc_output +npm-debug.log* +.eslintcache +*.iml +selenium-debug.log +chromedriver.log +test/e2e/db.json +docs/_book +dev-helpers/examples + +# dist +flavors/**/dist/* +/lib +/es +dist/log* + +# Cypress +test/e2e-cypress/screenshots +test/e2e-cypress/videos diff --git a/frontend/web/api-doc/.husky/commit-msg b/frontend/web/api-doc/.husky/commit-msg new file mode 100755 index 0000000..378f80f --- /dev/null +++ b/frontend/web/api-doc/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx commitlint -e diff --git a/frontend/web/api-doc/.husky/pre-commit b/frontend/web/api-doc/.husky/pre-commit new file mode 100755 index 0000000..36af219 --- /dev/null +++ b/frontend/web/api-doc/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx lint-staged diff --git a/frontend/web/api-doc/.lintstagedrc b/frontend/web/api-doc/.lintstagedrc new file mode 100644 index 0000000..5189da9 --- /dev/null +++ b/frontend/web/api-doc/.lintstagedrc @@ -0,0 +1,3 @@ +{ + "*.js": "eslint" +} diff --git a/frontend/web/api-doc/.mocharc.yaml b/frontend/web/api-doc/.mocharc.yaml new file mode 100644 index 0000000..f25954a --- /dev/null +++ b/frontend/web/api-doc/.mocharc.yaml @@ -0,0 +1,2 @@ +recursive: true +require: ['esm','@babel/register','source-map-support', 'test/mocha/setup.js'] diff --git a/frontend/web/api-doc/.npmignore b/frontend/web/api-doc/.npmignore new file mode 100644 index 0000000..4d2a277 --- /dev/null +++ b/frontend/web/api-doc/.npmignore @@ -0,0 +1,16 @@ +* +*/ +!README.md +!NOTICE +!package.json +!dist/swagger-ui.js +!dist/swagger-ui.js.map +!dist/swagger-ui-standalone-preset.js +!dist/swagger-ui-standalone-preset.js.map +!dist/swagger-ui-es-bundle.js +!dist/swagger-ui-es-bundle.js.map +!dist/swagger-ui-es-bundle-core.js +!dist/swagger-ui-es-bundle-core.js.map +!dist/swagger-ui.css +!dist/swagger-ui.css.map +!dist/oauth2-redirect.html diff --git a/frontend/web/api-doc/.npmrc b/frontend/web/api-doc/.npmrc new file mode 100644 index 0000000..d508edc --- /dev/null +++ b/frontend/web/api-doc/.npmrc @@ -0,0 +1 @@ +save-prefix="=" diff --git a/frontend/web/api-doc/.nvmrc b/frontend/web/api-doc/.nvmrc new file mode 100644 index 0000000..23d9c36 --- /dev/null +++ b/frontend/web/api-doc/.nvmrc @@ -0,0 +1 @@ +16.13.2 diff --git a/frontend/web/api-doc/.prettierrc.yaml b/frontend/web/api-doc/.prettierrc.yaml new file mode 100644 index 0000000..2e34d75 --- /dev/null +++ b/frontend/web/api-doc/.prettierrc.yaml @@ -0,0 +1,5 @@ +semi: false +trailingComma: es5 +endOfLine: lf +requirePragma: true +insertPragma: true diff --git a/frontend/web/api-doc/.releaserc b/frontend/web/api-doc/.releaserc new file mode 100644 index 0000000..4efddee --- /dev/null +++ b/frontend/web/api-doc/.releaserc @@ -0,0 +1,35 @@ +{ + "branches": [ + { + "name": "master" + }, + { + "name": "next", + "channel": "alpha", + "prerelease": "alpha" + } + ], + "tagFormat": "v${version}", + "plugins": [ + "@semantic-release/commit-analyzer", + [ + "@semantic-release/exec", + { + "verifyReleaseCmd": "echo \"NEXT_RELEASE_VERSION=${nextRelease.version}\" >> $GITHUB_ENV" + } + ], + "@semantic-release/release-notes-generator", + "@semantic-release/npm", + "@semantic-release/github", + [ + "@semantic-release/git", + { + "assets": [ + "package.json", + "package-lock.json" + ], + "message": "chore(release): cut the ${nextRelease.version} release\n\n${nextRelease.notes}" + } + ] + ] +} \ No newline at end of file diff --git a/frontend/web/api-doc/Dockerfile b/frontend/web/api-doc/Dockerfile new file mode 100644 index 0000000..26603c0 --- /dev/null +++ b/frontend/web/api-doc/Dockerfile @@ -0,0 +1,26 @@ +# Looking for information on environment variables? +# We don't declare them here — take a look at our docs. +# https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md + +FROM nginx:1.23.4-alpine + +RUN apk update && apk add --no-cache "nodejs>=18.14.1-r0 " + +LABEL maintainer="fehguy" + +ENV API_KEY "**None**" +ENV SWAGGER_JSON "/app/swagger.json" +ENV PORT 8080 +ENV BASE_URL "" +ENV SWAGGER_JSON_URL "" + +COPY --chown=nginx:nginx --chmod=0666 ./docker/nginx.conf ./docker/cors.conf /etc/nginx/ + +# copy swagger files to the `/js` folder +COPY --chmod=0666 ./dist/* /usr/share/nginx/html/ +COPY --chmod=0555 ./docker/docker-entrypoint.d/ /docker-entrypoint.d/ +COPY --chmod=0666 ./docker/configurator /usr/share/nginx/configurator + +RUN chmod 777 /usr/share/nginx/html/ /etc/nginx/ /var/cache/nginx/ /var/run/ + +EXPOSE 8080 diff --git a/frontend/web/api-doc/LICENSE b/frontend/web/api-doc/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/frontend/web/api-doc/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/frontend/web/api-doc/NOTICE b/frontend/web/api-doc/NOTICE new file mode 100644 index 0000000..ab788a2 --- /dev/null +++ b/frontend/web/api-doc/NOTICE @@ -0,0 +1,2 @@ +swagger-ui +Copyright 2020-2021 SmartBear Software Inc. diff --git a/frontend/web/api-doc/README.md b/frontend/web/api-doc/README.md new file mode 100644 index 0000000..1cf6c73 --- /dev/null +++ b/frontend/web/api-doc/README.md @@ -0,0 +1,98 @@ +# + +[![NPM version](https://badge.fury.io/js/swagger-ui.svg)](http://badge.fury.io/js/swagger-ui) +[![Build Status](https://jenkins.swagger.io/view/OSS%20-%20JavaScript/job/oss-swagger-ui-master/badge/icon?subject=jenkins%20build)](https://jenkins.swagger.io/view/OSS%20-%20JavaScript/job/oss-swagger-ui-master/) +[![npm audit](https://jenkins.swagger.io/buildStatus/icon?job=oss-swagger-ui-security-audit&subject=npm%20audit)](https://jenkins.swagger.io/job/oss-swagger-ui-security-audit/lastBuild/console) +![total GitHub contributors](https://img.shields.io/github/contributors-anon/swagger-api/swagger-ui.svg) + +![monthly npm installs](https://img.shields.io/npm/dm/swagger-ui.svg?label=npm%20downloads) +![total docker pulls](https://img.shields.io/docker/pulls/swaggerapi/swagger-ui.svg) +![monthly packagist installs](https://img.shields.io/packagist/dm/swagger-api/swagger-ui.svg?label=packagist%20installs) +![gzip size](https://img.shields.io/bundlephobia/minzip/swagger-ui.svg?label=gzip%20size) + +## Introduction +[Swagger UI](https://swagger.io/tools/swagger-ui/) allows anyone — be it your development team or your end consumers — to visualize and interact with the API’s resources without having any of the implementation logic in place. It’s automatically generated from your OpenAPI (formerly known as Swagger) Specification, with the visual documentation making it easy for back end implementation and client side consumption. + +## General +**👉🏼 Want to score an easy open-source contribution?** Check out our [Good first issue](https://github.com/swagger-api/swagger-ui/issues?q=is%3Aissue+is%3Aopen+label%3A%22Good+first+issue%22) label. + +**🕰️ Looking for the older version of Swagger UI?** Refer to the [*2.x* branch](https://github.com/swagger-api/swagger-ui/tree/2.x). + + +This repository publishes three different NPM modules: + +* [swagger-ui](https://www.npmjs.com/package/swagger-ui) is a traditional npm module intended for use in single-page applications that are capable of resolving dependencies (via Webpack, Browserify, etc). +* [swagger-ui-dist](https://www.npmjs.com/package/swagger-ui-dist) is a dependency-free module that includes everything you need to serve Swagger UI in a server-side project, or a single-page application that can't resolve npm module dependencies. +* [swagger-ui-react](https://www.npmjs.com/package/swagger-ui-react) is Swagger UI packaged as a React component for use in React applications. + +We strongly suggest that you use `swagger-ui` instead of `swagger-ui-dist` if you're building a single-page application, since `swagger-ui-dist` is significantly larger. + +If you are looking for plain ol' HTML/JS/CSS, [download the latest release](https://github.com/swagger-api/swagger-ui/releases/latest) and copy the contents of the `/dist` folder to your server. + + +## Compatibility +The OpenAPI Specification has undergone 5 revisions since initial creation in 2010. Compatibility between Swagger UI and the OpenAPI Specification is as follows: + +Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes +------------------ | ------------ | -------------------------- | ----- +4.0.0 | 2021-11-03 | 2.0, 3.0 | [tag v4.0.0](https://github.com/swagger-api/swagger-ui/tree/v4.0.0) +3.18.3 | 2018-08-03 | 2.0, 3.0 | [tag v3.18.3](https://github.com/swagger-api/swagger-ui/tree/v3.18.3) +3.0.21 | 2017-07-26 | 2.0 | [tag v3.0.21](https://github.com/swagger-api/swagger-ui/tree/v3.0.21) +2.2.10 | 2017-01-04 | 1.1, 1.2, 2.0 | [tag v2.2.10](https://github.com/swagger-api/swagger-ui/tree/v2.2.10) +2.1.5 | 2016-07-20 | 1.1, 1.2, 2.0 | [tag v2.1.5](https://github.com/swagger-api/swagger-ui/tree/v2.1.5) +2.0.24 | 2014-09-12 | 1.1, 1.2 | [tag v2.0.24](https://github.com/swagger-api/swagger-ui/tree/v2.0.24) +1.0.13 | 2013-03-08 | 1.1, 1.2 | [tag v1.0.13](https://github.com/swagger-api/swagger-ui/tree/v1.0.13) +1.0.1 | 2011-10-11 | 1.0, 1.1 | [tag v1.0.1](https://github.com/swagger-api/swagger-ui/tree/v1.0.1) + +## Documentation + +#### Usage +- [Installation](docs/usage/installation.md) +- [Configuration](docs/usage/configuration.md) +- [CORS](docs/usage/cors.md) +- [OAuth2](docs/usage/oauth2.md) +- [Deep Linking](docs/usage/deep-linking.md) +- [Limitations](docs/usage/limitations.md) +- [Version detection](docs/usage/version-detection.md) + +#### Customization +- [Overview](docs/customization/overview.md) +- [Plugin API](docs/customization/plugin-api.md) +- [Custom layout](docs/customization/custom-layout.md) + +#### Development +- [Setting up](docs/development/setting-up.md) +- [Scripts](docs/development/scripts.md) + +#### Contributing +- [Contributing](https://github.com/swagger-api/.github/blob/master/CONTRIBUTING.md) + +##### Integration Tests + +You will need JDK of version 7 or higher as instructed here +https://nightwatchjs.org/guide/getting-started/installation.html#install-selenium-server + +Integration tests can be run locally with `npm run e2e` - be sure you aren't running a dev server when testing! + +### Browser support +Swagger UI works in the latest versions of Chrome, Safari, Firefox, and Edge. + +### Known Issues + +To help with the migration, here are the currently known issues with 3.X. This list will update regularly, and will not include features that were not implemented in previous versions. + +- Only part of the parameters previously supported are available. +- The JSON Form Editor is not implemented. +- Support for `collectionFormat` is partial. +- l10n (translations) is not implemented. +- Relative path support for external files is not implemented. + +## Security contact + +Please disclose any security-related issues or vulnerabilities by emailing [security@swagger.io](mailto:security@swagger.io), instead of using the public issue tracker. + +## License + +SwaggerUI is licensed under [Apache 2.0 license](https://github.com/swagger-api/swagger-ui/blob/master/LICENSE). +SwaggerUI comes with an explicit [NOTICE](https://github.com/swagger-api/swagger-ui/blob/master/NOTICE) file +containing additional legal notices and information. diff --git a/frontend/web/api-doc/SECURITY.md b/frontend/web/api-doc/SECURITY.md new file mode 100644 index 0000000..cb4f9e7 --- /dev/null +++ b/frontend/web/api-doc/SECURITY.md @@ -0,0 +1,22 @@ +# Security Policy + +If you believe you've found an exploitable security issue in Swagger UI, +**please don't create a public issue**. + + +## Supported versions + +This is the list of versions of `swagger-ui` which are +currently being supported with security updates. + +| Version | Supported | Notes | +| -------- | ------------------ | ---------------------- | +| 4.x | :white_check_mark: | | +| 3.x | :x: | End-of-life as of November 2021 | +| 2.x | :x: | End-of-life as of 2017 | + +## Reporting a vulnerability + +To report a vulnerability please send an email with the details to [security@swagger.io](mailto:security@swagger.io). + +We'll acknowledge receipt of your report ASAP, and set expectations on how we plan to handle it. diff --git a/frontend/web/api-doc/babel.config.js b/frontend/web/api-doc/babel.config.js new file mode 100644 index 0000000..4d14430 --- /dev/null +++ b/frontend/web/api-doc/babel.config.js @@ -0,0 +1,167 @@ +module.exports = { + "env": { + "commonjs": { + "presets": [ + [ + "@babel/preset-env", + { + "debug": false, + "modules": "commonjs", + "targets": { + "node": "8" + }, + "forceAllTransforms": false, + "ignoreBrowserslistConfig": true + } + ], + "@babel/preset-react", + ], + "plugins": [ + [ + "@babel/plugin-transform-modules-commonjs", + { + "loose": true + } + ], + "@babel/proposal-class-properties", + "@babel/proposal-object-rest-spread", + "@babel/plugin-proposal-optional-chaining", + ] + }, + "es": { + "presets": [ + [ + "@babel/preset-env", + { + "debug": false, + "modules": false + } + ], + "@babel/preset-react", + ], + "plugins": [ + [ + "@babel/plugin-transform-runtime", + { + "absoluteRuntime": false, + "corejs": 3, + "version": "^7.11.2" + } + ], + "@babel/proposal-class-properties", + "@babel/proposal-object-rest-spread", + "@babel/plugin-proposal-optional-chaining", + ] + }, + "development": { + "presets": [ + [ + "@babel/env", + { + "targets": { + "browsers": [ + /* benefit of C/S/FF/Edge only? */ + "> 1%", + "last 2 versions", + "Firefox ESR", + "not dead" + ] + }, + "useBuiltIns": false, + "corejs": { version: 3 }, + "include": [ + "@babel/plugin-proposal-logical-assignment-operators" + ] + } + ], + "@babel/preset-react" + ], + "plugins": [ + [ + "@babel/plugin-transform-runtime", + { + "corejs": 3, + "absoluteRuntime": false, + "version": "^7.11.2" + } + ], + "@babel/plugin-proposal-class-properties", + "@babel/plugin-proposal-optional-chaining", + [ + "transform-react-remove-prop-types", + { + "additionalLibraries": [ + "react-immutable-proptypes" + ] + } + ], + [ + "babel-plugin-module-resolver", + { + "alias": { + "root": ".", + "components": "./src/core/components", + "containers": "./src/core/containers", + "core": "./src/core", + "plugins": "./src/plugins", + "img": "./src/img", + "corePlugins": "./src/core/plugins", + "less": "./src/less" + } + } + ] + ] + }, + "test": { + "presets": [ + [ + "@babel/env", + { + "targets": { + "node": "10" + }, + "useBuiltIns": false, + "corejs": { version: 3 } + } + ], + "@babel/preset-react" + ], + "plugins": [ + [ + "@babel/plugin-transform-runtime", + { + "corejs": 3, + "absoluteRuntime": false, + "version": "^7.11.2" + } + ], + "@babel/plugin-proposal-class-properties", + "@babel/plugin-proposal-optional-chaining", + [ + "transform-react-remove-prop-types", + { + "additionalLibraries": [ + "react-immutable-proptypes" + ] + } + ], + [ + "babel-plugin-module-resolver", + { + "alias": { + "root": ".", + "components": "./src/core/components", + "containers": "./src/core/containers", + "core": "./src/core", + "plugins": "./src/plugins", + "img": "./src/img", + "corePlugins": "./src/core/plugins", + "less": "./src/less" + } + } + ] + ] + } + } +} + diff --git a/frontend/web/api-doc/composer.json b/frontend/web/api-doc/composer.json new file mode 100644 index 0000000..76288f6 --- /dev/null +++ b/frontend/web/api-doc/composer.json @@ -0,0 +1,40 @@ +{ + "name": "swagger-api/swagger-ui", + "description": " Swagger UI is a collection of HTML, Javascript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API.", + "keywords": [ + "Swagger", + "OpenAPI", + "specification", + "documentation", + "API", + "UI" + ], + "homepage": "http://swagger.io", + "license": "Apache-2.0", + "authors": [ + { + "name": "Anna Bodnia", + "email": "anna.bodnia@gmail.com" + }, + { + "name": "Buu Nguyen", + "email": "buunguyen@gmail.com" + }, + { + "name": "Josh Ponelat", + "email": "jponelat@gmail.com" + }, + { + "name": "Kyle Shockey", + "email": "kyleshockey1@gmail.com" + }, + { + "name": "Robert Barnwell", + "email": "robert@robertismy.name" + }, + { + "name": "Sahar Jafari", + "email": "shr.jafari@gmail.com" + } + ] +} diff --git a/frontend/web/api-doc/config/.eslintrc b/frontend/web/api-doc/config/.eslintrc new file mode 100644 index 0000000..85ec702 --- /dev/null +++ b/frontend/web/api-doc/config/.eslintrc @@ -0,0 +1,8 @@ +{ + "rules": { + "import/no-unresolved": 0, + "import/extensions": 0, + "quotes": ["error", "single"], + "semi": ["error", "always"] + } +} diff --git a/frontend/web/api-doc/config/jest/jest.artifact.config.js b/frontend/web/api-doc/config/jest/jest.artifact.config.js new file mode 100644 index 0000000..2a539b1 --- /dev/null +++ b/frontend/web/api-doc/config/jest/jest.artifact.config.js @@ -0,0 +1,8 @@ +const path = require('path'); + +module.exports = { + rootDir: path.join(__dirname, '..', '..'), + testEnvironment: 'jsdom', + testMatch: ['**/test/build-artifacts/**/*.js'], + transformIgnorePatterns: ['/node_modules/(?!(swagger-client|react-syntax-highlighter)/)'], +}; diff --git a/frontend/web/api-doc/config/jest/jest.unit.config.js b/frontend/web/api-doc/config/jest/jest.unit.config.js new file mode 100644 index 0000000..2a8ac91 --- /dev/null +++ b/frontend/web/api-doc/config/jest/jest.unit.config.js @@ -0,0 +1,23 @@ +const path = require('path'); + +module.exports = { + rootDir: path.join(__dirname, '..', '..'), + testEnvironment: 'jest-environment-jsdom', + testMatch: [ + '**/test/unit/*.js?(x)', + '**/test/unit/**/*.js?(x)', + ], + setupFiles: ['/test/unit/jest-shim.js'], + setupFilesAfterEnv: ['/test/unit/setup.js'], + testPathIgnorePatterns: [ + '/node_modules/', + '/test/build-artifacts/', + '/test/mocha', + '/test/unit/jest-shim.js', + '/test/unit/setup.js', + '/test/unit/xss/anchor-target-rel/online-validator-badge.jsx', + '/test/unit/components/online-validator-badge.jsx', + '/test/unit/components/live-response.jsx', + ], + silent: true, // set to `false` to allow console.* calls to be printed +}; diff --git a/frontend/web/api-doc/cypress.json b/frontend/web/api-doc/cypress.json new file mode 100644 index 0000000..6d11f51 --- /dev/null +++ b/frontend/web/api-doc/cypress.json @@ -0,0 +1,11 @@ +{ + "fileServerFolder": "test/e2e-cypress/static", + "fixturesFolder": "test/e2e-cypress/fixtures", + "integrationFolder": "test/e2e-cypress/tests", + "pluginsFile": "test/e2e-cypress/plugins/index.js", + "screenshotsFolder": "test/e2e-cypress/screenshots", + "supportFile": "test/e2e-cypress/support/index.js", + "videosFolder": "test/e2e-cypress/videos", + "baseUrl": "http://localhost:3230/", + "video": false +} diff --git a/frontend/web/api-doc/dev-helpers/dev-helper-initializer.js b/frontend/web/api-doc/dev-helpers/dev-helper-initializer.js new file mode 100644 index 0000000..ec330dc --- /dev/null +++ b/frontend/web/api-doc/dev-helpers/dev-helper-initializer.js @@ -0,0 +1,33 @@ +/* eslint-disable no-undef */ +window.onload = function() { + window["SwaggerUIBundle"] = window["swagger-ui-bundle"] + window["SwaggerUIStandalonePreset"] = window["swagger-ui-standalone-preset"] + // Build a system + const ui = SwaggerUIBundle({ + url: "https://petstore.swagger.io/v2/swagger.json", + dom_id: "#swagger-ui", + presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIStandalonePreset + ], + plugins: [ + SwaggerUIBundle.plugins.DownloadUrl + ], + // requestSnippetsEnabled: true, + layout: "StandaloneLayout" + }) + + window.ui = ui + + ui.initOAuth({ + clientId: "your-client-id", + clientSecret: "your-client-secret-if-required", + realm: "your-realms", + appName: "your-app-name", + scopeSeparator: " ", + scopes: "openid profile email phone address", + additionalQueryStringParams: {}, + useBasicAuthenticationWithAccessCodeGrant: false, + usePkceWithAuthorizationCodeGrant: false + }) +} diff --git a/frontend/web/api-doc/dev-helpers/index.html b/frontend/web/api-doc/dev-helpers/index.html new file mode 100644 index 0000000..9432ef7 --- /dev/null +++ b/frontend/web/api-doc/dev-helpers/index.html @@ -0,0 +1,21 @@ + + + + + + + Swagger UI + + + + + +

+ + + + + + diff --git a/frontend/web/api-doc/dev-helpers/oauth2-redirect.html b/frontend/web/api-doc/dev-helpers/oauth2-redirect.html new file mode 100644 index 0000000..87a2eed --- /dev/null +++ b/frontend/web/api-doc/dev-helpers/oauth2-redirect.html @@ -0,0 +1,76 @@ + + + + + + diff --git a/frontend/web/api-doc/dev-helpers/style.css b/frontend/web/api-doc/dev-helpers/style.css new file mode 100644 index 0000000..75a5daa --- /dev/null +++ b/frontend/web/api-doc/dev-helpers/style.css @@ -0,0 +1,19 @@ +html +{ + box-sizing: border-box; + overflow: -moz-scrollbars-vertical; + overflow-y: scroll; +} + +*, +*:before, +*:after +{ + box-sizing: inherit; +} + +body +{ + margin:0; + background: #fafafa; +} diff --git a/frontend/web/api-doc/dist/definitions/request.json b/frontend/web/api-doc/dist/definitions/request.json new file mode 100644 index 0000000..97822ed --- /dev/null +++ b/frontend/web/api-doc/dist/definitions/request.json @@ -0,0 +1,16 @@ + + "components": { + "schemas": { + "user": { + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + } + } + } + diff --git a/frontend/web/api-doc/dist/favicon-16x16.png b/frontend/web/api-doc/dist/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..8b194e617af1c135e6b37939591d24ac3a5efa18 GIT binary patch literal 665 zcmV;K0%rY*P)}JKSduyL>)s!A4EhTMMEM%Q;aL6%l#xiZiF>S;#Y{N2Zz%pvTGHJduXuC6Lx-)0EGfRy*N{Tv4i8@4oJ41gw zKzThrcRe|7J~(YYIBq{SYCkn-KQm=N8$CrEK1CcqMI1dv9z#VRL_{D)L|`QmF8}}l zJ9JV`Q}p!p_4f7m_U`WQ@apR4;o;!mnU<7}iG_qr zF(e)x9~BG-3IzcG2M4an0002kNkl41`ZiN1i62V%{PM@Ry|IS_+Yc7{bb`MM~xm(7p4|kMHP&!VGuDW4kFixat zXw43VmgwEvB$hXt_u=vZ>+v4i7E}n~eG6;n4Z=zF1n?T*yg<;W6kOfxpC6nao>VR% z?fpr=asSJ&`L*wu^rLJ5Peq*PB0;alL#XazZCBxJLd&giTfw@!hW167F^`7kobi;( ze<<>qNlP|xy7S1zl@lZNIBR7#o9ybJsptO#%}P0hz~sBp00000NkvXXu0mjfUsDF? literal 0 HcmV?d00001 diff --git a/frontend/web/api-doc/dist/favicon-32x32.png b/frontend/web/api-doc/dist/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..249737fe44558e679f0b67134e274461d988fa98 GIT binary patch literal 628 zcmV-)0*n2LP)Ma*GM0}OV<074bNCP7P7GVd{iMr*I6y~TMLss@FjvgL~HxU z%Vvj33AwpD(Z4*$Mfx=HaU16axM zt2xG_rloN<$iy9j9I5 + + + + + Swagger UI + + + + + + + +
+ + + + + diff --git a/frontend/web/api-doc/dist/oauth2-redirect.html b/frontend/web/api-doc/dist/oauth2-redirect.html new file mode 100644 index 0000000..5640917 --- /dev/null +++ b/frontend/web/api-doc/dist/oauth2-redirect.html @@ -0,0 +1,79 @@ + + + + Swagger UI: OAuth2 Redirect + + + + + diff --git a/frontend/web/api-doc/dist/swagger-config.yaml b/frontend/web/api-doc/dist/swagger-config.yaml new file mode 100644 index 0000000..4ee3c5a --- /dev/null +++ b/frontend/web/api-doc/dist/swagger-config.yaml @@ -0,0 +1,4 @@ +--- +url: "swagger.yaml" +dom_id: "#swagger-ui" +validatorUrl: "https://validator.swagger.io/validator" diff --git a/frontend/web/api-doc/dist/swagger-initializer.js b/frontend/web/api-doc/dist/swagger-initializer.js new file mode 100644 index 0000000..f48f13f --- /dev/null +++ b/frontend/web/api-doc/dist/swagger-initializer.js @@ -0,0 +1,20 @@ +window.onload = function() { + // + + // the following lines will be replaced by docker/configurator, when it runs in a docker-container + window.ui = SwaggerUIBundle({ + url: "swagger.yaml", + dom_id: '#swagger-ui', + deepLinking: true, + presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIStandalonePreset + ], + plugins: [ + SwaggerUIBundle.plugins.DownloadUrl + ], + layout: "StandaloneLayout" + }); + + // +}; diff --git a/frontend/web/api-doc/dist/swagger-ui-bundle.js b/frontend/web/api-doc/dist/swagger-ui-bundle.js new file mode 100644 index 0000000..8e09644 --- /dev/null +++ b/frontend/web/api-doc/dist/swagger-ui-bundle.js @@ -0,0 +1,3 @@ +/*! For license information please see swagger-ui-bundle.js.LICENSE.txt */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUIBundle=t():e.SwaggerUIBundle=t()}(this,(()=>(()=>{var e={17967:(e,t)=>{"use strict";t.N=void 0;var n=/^([^\w]*)(javascript|data|vbscript)/im,r=/&#(\w+)(^\w|;)?/g,o=/&(newline|tab);/gi,a=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim,i=/^.+(:|:)/gim,s=[".","/"];t.N=function(e){var t,l=(t=e||"",t.replace(r,(function(e,t){return String.fromCharCode(t)}))).replace(o,"").replace(a,"").trim();if(!l)return"about:blank";if(function(e){return s.indexOf(e[0])>-1}(l))return l;var u=l.match(i);if(!u)return l;var c=u[0];return n.test(c)?"about:blank":l}},53795:(e,t,n)=>{"use strict";n.d(t,{Z:()=>I});var r=n(23101),o=n.n(r),a=n(61125),i=n.n(a),s=n(11882),l=n.n(s),u=n(97606),c=n.n(u),p=n(67294),f=n(43393);function h(e){return h="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},h(e)}function d(e,t){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=function(e,t){return function(n){if("string"==typeof n)return(0,f.is)(t[n],e[n]);if(Array.isArray(n))return(0,f.is)(x(t,n),x(e,n));throw new TypeError("Invalid key: expected Array or string: "+n)}}(t,n),o=e||Object.keys(function(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:{};return!_(this.updateOnProps,this.props,e,"updateOnProps")||!_(this.updateOnStates,this.state,t,"updateOnStates")}}],r&&d(n.prototype,r),o&&d(n,o),t}(p.Component);var A=n(23930),C=n.n(A),k=n(45697),O=n.n(k);const j=e=>{const t=e.replace(/~1/g,"/").replace(/~0/g,"~");try{return decodeURIComponent(t)}catch{return t}};class I extends S{constructor(){super(...arguments),i()(this,"getModelName",(e=>-1!==l()(e).call(e,"#/definitions/")?j(e.replace(/^.*#\/definitions\//,"")):-1!==l()(e).call(e,"#/components/schemas/")?j(e.replace(/^.*#\/components\/schemas\//,"")):void 0)),i()(this,"getRefSchema",(e=>{let{specSelectors:t}=this.props;return t.findDefinition(e)}))}render(){let{getComponent:e,getConfigs:t,specSelectors:r,schema:a,required:i,name:s,isRef:l,specPath:u,displayName:c,includeReadOnly:f,includeWriteOnly:h}=this.props;const d=e("ObjectModel"),m=e("ArrayModel"),g=e("PrimitiveModel");let y="object",v=a&&a.get("$$ref");if(!s&&v&&(s=this.getModelName(v)),!a&&v&&(a=this.getRefSchema(s)),!a)return p.createElement("span",{className:"model model-title"},p.createElement("span",{className:"model-title__text"},c||s),p.createElement("img",{src:n(2517),height:"20px",width:"20px"}));const b=r.isOAS3()&&a.get("deprecated");switch(l=void 0!==l?l:!!v,y=a&&a.get("type")||y,y){case"object":return p.createElement(d,o()({className:"object"},this.props,{specPath:u,getConfigs:t,schema:a,name:s,deprecated:b,isRef:l,includeReadOnly:f,includeWriteOnly:h}));case"array":return p.createElement(m,o()({className:"array"},this.props,{getConfigs:t,schema:a,name:s,deprecated:b,required:i,includeReadOnly:f,includeWriteOnly:h}));default:return p.createElement(g,o()({},this.props,{getComponent:e,getConfigs:t,schema:a,name:s,deprecated:b,required:i}))}}}i()(I,"propTypes",{schema:c()(C()).isRequired,getComponent:O().func.isRequired,getConfigs:O().func.isRequired,specSelectors:O().object.isRequired,name:O().string,displayName:O().string,isRef:O().bool,required:O().bool,expandDepth:O().number,depth:O().number,specPath:C().list.isRequired,includeReadOnly:O().bool,includeWriteOnly:O().bool})},5623:(e,t,n)=>{"use strict";n.d(t,{Z:()=>f});var r=n(61125),o=n.n(r),a=n(28222),i=n.n(a),s=n(67294),l=n(84564),u=n.n(l),c=n(90242),p=n(27504);class f extends s.Component{constructor(e,t){super(e,t),o()(this,"getDefinitionUrl",(()=>{let{specSelectors:e}=this.props;return new(u())(e.url(),p.Z.location).toString()}));let{getConfigs:n}=e,{validatorUrl:r}=n();this.state={url:this.getDefinitionUrl(),validatorUrl:void 0===r?"https://validator.swagger.io/validator":r}}UNSAFE_componentWillReceiveProps(e){let{getConfigs:t}=e,{validatorUrl:n}=t();this.setState({url:this.getDefinitionUrl(),validatorUrl:void 0===n?"https://validator.swagger.io/validator":n})}render(){let{getConfigs:e}=this.props,{spec:t}=e(),n=(0,c.Nm)(this.state.validatorUrl);return"object"==typeof t&&i()(t).length?null:this.state.url&&(0,c.hW)(this.state.validatorUrl)&&(0,c.hW)(this.state.url)?s.createElement("span",{className:"float-right"},s.createElement("a",{target:"_blank",rel:"noopener noreferrer",href:`${n}/debug?url=${encodeURIComponent(this.state.url)}`},s.createElement(h,{src:`${n}?url=${encodeURIComponent(this.state.url)}`,alt:"Online validator badge"}))):null}}class h extends s.Component{constructor(e){super(e),this.state={loaded:!1,error:!1}}componentDidMount(){const e=new Image;e.onload=()=>{this.setState({loaded:!0})},e.onerror=()=>{this.setState({error:!0})},e.src=this.props.src}UNSAFE_componentWillReceiveProps(e){if(e.src!==this.props.src){const t=new Image;t.onload=()=>{this.setState({loaded:!0})},t.onerror=()=>{this.setState({error:!0})},t.src=e.src}}render(){return this.state.error?s.createElement("img",{alt:"Error"}):this.state.loaded?s.createElement("img",{src:this.props.src,alt:this.props.alt}):null}}},94994:(e,t,n)=>{"use strict";n.d(t,{Z:()=>ye,s:()=>ve});var r=n(67294),o=n(89927);function a(e,t){if(Array.prototype.indexOf)return e.indexOf(t);for(var n=0,r=e.length;n=0;n--)!0===t(e[n])&&e.splice(n,1)}function s(e){throw new Error("Unhandled case for value: '".concat(e,"'"))}var l=function(){function e(e){void 0===e&&(e={}),this.tagName="",this.attrs={},this.innerHTML="",this.whitespaceRegex=/\s+/,this.tagName=e.tagName||"",this.attrs=e.attrs||{},this.innerHTML=e.innerHtml||e.innerHTML||""}return e.prototype.setTagName=function(e){return this.tagName=e,this},e.prototype.getTagName=function(){return this.tagName||""},e.prototype.setAttr=function(e,t){return this.getAttrs()[e]=t,this},e.prototype.getAttr=function(e){return this.getAttrs()[e]},e.prototype.setAttrs=function(e){return Object.assign(this.getAttrs(),e),this},e.prototype.getAttrs=function(){return this.attrs||(this.attrs={})},e.prototype.setClass=function(e){return this.setAttr("class",e)},e.prototype.addClass=function(e){for(var t,n=this.getClass(),r=this.whitespaceRegex,o=n?n.split(r):[],i=e.split(r);t=i.shift();)-1===a(o,t)&&o.push(t);return this.getAttrs().class=o.join(" "),this},e.prototype.removeClass=function(e){for(var t,n=this.getClass(),r=this.whitespaceRegex,o=n?n.split(r):[],i=e.split(r);o.length&&(t=i.shift());){var s=a(o,t);-1!==s&&o.splice(s,1)}return this.getAttrs().class=o.join(" "),this},e.prototype.getClass=function(){return this.getAttrs().class||""},e.prototype.hasClass=function(e){return-1!==(" "+this.getClass()+" ").indexOf(" "+e+" ")},e.prototype.setInnerHTML=function(e){return this.innerHTML=e,this},e.prototype.setInnerHtml=function(e){return this.setInnerHTML(e)},e.prototype.getInnerHTML=function(){return this.innerHTML||""},e.prototype.getInnerHtml=function(){return this.getInnerHTML()},e.prototype.toAnchorString=function(){var e=this.getTagName(),t=this.buildAttrsStr();return["<",e,t=t?" "+t:"",">",this.getInnerHtml(),""].join("")},e.prototype.buildAttrsStr=function(){if(!this.attrs)return"";var e=this.getAttrs(),t=[];for(var n in e)e.hasOwnProperty(n)&&t.push(n+'="'+e[n]+'"');return t.join(" ")},e}();var u=function(){function e(e){void 0===e&&(e={}),this.newWindow=!1,this.truncate={},this.className="",this.newWindow=e.newWindow||!1,this.truncate=e.truncate||{},this.className=e.className||""}return e.prototype.build=function(e){return new l({tagName:"a",attrs:this.createAttrs(e),innerHtml:this.processAnchorText(e.getAnchorText())})},e.prototype.createAttrs=function(e){var t={href:e.getAnchorHref()},n=this.createCssClass(e);return n&&(t.class=n),this.newWindow&&(t.target="_blank",t.rel="noopener noreferrer"),this.truncate&&this.truncate.length&&this.truncate.length=s)return l.host.length==t?(l.host.substr(0,t-o)+n).substr(0,s+r):i(c,s).substr(0,s+r);var p="";if(l.path&&(p+="/"+l.path),l.query&&(p+="?"+l.query),p){if((c+p).length>=s)return(c+p).length==t?(c+p).substr(0,t):(c+i(p,s-c.length)).substr(0,s+r);c+=p}if(l.fragment){var f="#"+l.fragment;if((c+f).length>=s)return(c+f).length==t?(c+f).substr(0,t):(c+i(f,s-c.length)).substr(0,s+r);c+=f}if(l.scheme&&l.host){var h=l.scheme+"://";if((c+h).length0&&(d=c.substr(-1*Math.floor(s/2))),(c.substr(0,Math.ceil(s/2))+n+d).substr(0,s+r)}(e,n):"middle"===r?function(e,t,n){if(e.length<=t)return e;var r,o;null==n?(n="…",r=8,o=3):(r=n.length,o=n.length);var a=t-o,i="";return a>0&&(i=e.substr(-1*Math.floor(a/2))),(e.substr(0,Math.ceil(a/2))+n+i).substr(0,a+r)}(e,n):function(e,t,n){return function(e,t,n){var r;return e.length>t&&(null==n?(n="…",r=3):r=n.length,e=e.substring(0,t-r)+n),e}(e,t,n)}(e,n)},e}(),c=function(){function e(e){this.__jsduckDummyDocProp=null,this.matchedText="",this.offset=0,this.tagBuilder=e.tagBuilder,this.matchedText=e.matchedText,this.offset=e.offset}return e.prototype.getMatchedText=function(){return this.matchedText},e.prototype.setOffset=function(e){this.offset=e},e.prototype.getOffset=function(){return this.offset},e.prototype.getCssClassSuffixes=function(){return[this.getType()]},e.prototype.buildTag=function(){return this.tagBuilder.build(this)},e}(),p=function(e,t){return p=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},p(e,t)};function f(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}p(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var h=function(){return h=Object.assign||function(e){for(var t,n=1,r=arguments.length;n-1},e.isValidUriScheme=function(e){var t=e.match(this.uriSchemeRegex),n=t&&t[0].toLowerCase();return"javascript:"!==n&&"vbscript:"!==n},e.urlMatchDoesNotHaveProtocolOrDot=function(e,t){return!(!e||t&&this.hasFullProtocolRegex.test(t)||-1!==e.indexOf("."))},e.urlMatchDoesNotHaveAtLeastOneWordChar=function(e,t){return!(!e||!t)&&(!this.hasFullProtocolRegex.test(t)&&!this.hasWordCharAfterProtocolRegex.test(e))},e.hasFullProtocolRegex=/^[A-Za-z][-.+A-Za-z0-9]*:\/\//,e.uriSchemeRegex=/^[A-Za-z][-.+A-Za-z0-9]*:/,e.hasWordCharAfterProtocolRegex=new RegExp(":[^\\s]*?["+k+"]"),e.ipRegex=/[0-9][0-9]?[0-9]?\.[0-9][0-9]?[0-9]?\.[0-9][0-9]?[0-9]?\.[0-9][0-9]?[0-9]?(:[0-9]*)?\/?$/,e}(),V=(d=new RegExp("[/?#](?:["+T+"\\-+&@#/%=~_()|'$*\\[\\]{}?!:,.;^✓]*["+T+"\\-+&@#/%=~_()|'$*\\[\\]{}✓])?"),new RegExp(["(?:","(",/(?:[A-Za-z][-.+A-Za-z0-9]{0,63}:(?![A-Za-z][-.+A-Za-z0-9]{0,63}:\/\/)(?!\d+\/?)(?:\/\/)?)/.source,D(2),")","|","(","(//)?",/(?:www\.)/.source,D(6),")","|","(","(//)?",D(10)+"\\.",B.source,"(?![-"+I+"])",")",")","(?::[0-9]+)?","(?:"+d.source+")?"].join(""),"gi")),W=new RegExp("["+T+"]"),H=function(e){function t(t){var n=e.call(this,t)||this;return n.stripPrefix={scheme:!0,www:!0},n.stripTrailingSlash=!0,n.decodePercentEncoding=!0,n.matcherRegex=V,n.wordCharRegExp=W,n.stripPrefix=t.stripPrefix,n.stripTrailingSlash=t.stripTrailingSlash,n.decodePercentEncoding=t.decodePercentEncoding,n}return f(t,e),t.prototype.parseMatches=function(e){for(var t,n=this.matcherRegex,r=this.stripPrefix,o=this.stripTrailingSlash,a=this.decodePercentEncoding,i=this.tagBuilder,s=[],l=function(){var n=t[0],l=t[1],c=t[4],p=t[5],f=t[9],h=t.index,d=p||f,m=e.charAt(h-1);if(!$.isValid(n,l))return"continue";if(h>0&&"@"===m)return"continue";if(h>0&&d&&u.wordCharRegExp.test(m))return"continue";if(/\?$/.test(n)&&(n=n.substr(0,n.length-1)),u.matchHasUnbalancedClosingParen(n))n=n.substr(0,n.length-1);else{var g=u.matchHasInvalidCharAfterTld(n,l);g>-1&&(n=n.substr(0,g))}var y=["http://","https://"].find((function(e){return!!l&&-1!==l.indexOf(e)}));if(y){var v=n.indexOf(y);n=n.substr(v),l=l.substr(v),h+=v}var w=l?"scheme":c?"www":"tld",E=!!l;s.push(new b({tagBuilder:i,matchedText:n,offset:h,urlMatchType:w,url:n,protocolUrlMatch:E,protocolRelativeMatch:!!d,stripPrefix:r,stripTrailingSlash:o,decodePercentEncoding:a}))},u=this;null!==(t=n.exec(e));)l();return s},t.prototype.matchHasUnbalancedClosingParen=function(e){var t,n=e.charAt(e.length-1);if(")"===n)t="(";else if("]"===n)t="[";else{if("}"!==n)return!1;t="{"}for(var r=0,o=0,a=e.length-1;o-1&&a-i<=140){var o=e.slice(i,a),s=new g({tagBuilder:t,matchedText:o,offset:i,serviceName:n,hashtag:o.slice(1)});r.push(s)}}},t}(w),G=["twitter","facebook","instagram","tiktok"],Z=new RegExp("".concat(/(?:(?:(?:(\+)?\d{1,3}[-\040.]?)?\(?\d{3}\)?[-\040.]?\d{3}[-\040.]?\d{4})|(?:(\+)(?:9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)[-\040.]?(?:\d[-\040.]?){6,12}\d+))([,;]+[0-9]+#?)*/.source,"|").concat(/(0([1-9]{1}-?[1-9]\d{3}|[1-9]{2}-?\d{3}|[1-9]{2}\d{1}-?\d{2}|[1-9]{2}\d{2}-?\d{1})-?\d{4}|0[789]0-?\d{4}-?\d{4}|050-?\d{4}-?\d{4})/.source),"g"),Y=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.matcherRegex=Z,t}return f(t,e),t.prototype.parseMatches=function(e){for(var t,n=this.matcherRegex,r=this.tagBuilder,o=[];null!==(t=n.exec(e));){var a=t[0],i=a.replace(/[^0-9,;#]/g,""),s=!(!t[1]&&!t[2]),l=0==t.index?"":e.substr(t.index-1,1),u=e.substr(t.index+a.length,1),c=!l.match(/\d/)&&!u.match(/\d/);this.testMatch(t[3])&&this.testMatch(a)&&c&&o.push(new v({tagBuilder:r,matchedText:a,offset:t.index,number:i,plusSign:s}))}return o},t.prototype.testMatch=function(e){return _.test(e)},t}(w),Q=new RegExp("@[_".concat(T,"]{1,50}(?![_").concat(T,"])"),"g"),X=new RegExp("@[_.".concat(T,"]{1,30}(?![_").concat(T,"])"),"g"),ee=new RegExp("@[-_.".concat(T,"]{1,50}(?![-_").concat(T,"])"),"g"),te=new RegExp("@[_.".concat(T,"]{1,23}[_").concat(T,"](?![_").concat(T,"])"),"g"),ne=new RegExp("[^"+T+"]"),re=function(e){function t(t){var n=e.call(this,t)||this;return n.serviceName="twitter",n.matcherRegexes={twitter:Q,instagram:X,soundcloud:ee,tiktok:te},n.nonWordCharRegex=ne,n.serviceName=t.serviceName,n}return f(t,e),t.prototype.parseMatches=function(e){var t,n=this.serviceName,r=this.matcherRegexes[this.serviceName],o=this.nonWordCharRegex,a=this.tagBuilder,i=[];if(!r)return i;for(;null!==(t=r.exec(e));){var s=t.index,l=e.charAt(s-1);if(0===s||o.test(l)){var u=t[0].replace(/\.+$/g,""),c=u.slice(1);i.push(new y({tagBuilder:a,matchedText:u,offset:s,serviceName:n,mention:c}))}}return i},t}(w);function oe(e,t){for(var n,r=t.onOpenTag,o=t.onCloseTag,a=t.onText,i=t.onComment,l=t.onDoctype,u=new ae,c=0,p=e.length,f=0,d=0,m=u;c"===e?(m=new ae(h(h({},m),{name:H()})),W()):E.test(e)||x.test(e)||":"===e||$()}function w(e){">"===e?$():E.test(e)?f=3:$()}function _(e){S.test(e)||("/"===e?f=12:">"===e?W():"<"===e?V():"="===e||A.test(e)||C.test(e)?$():f=5)}function k(e){S.test(e)?f=6:"/"===e?f=12:"="===e?f=7:">"===e?W():"<"===e?V():A.test(e)&&$()}function O(e){S.test(e)||("/"===e?f=12:"="===e?f=7:">"===e?W():"<"===e?V():A.test(e)?$():f=5)}function j(e){S.test(e)||('"'===e?f=8:"'"===e?f=9:/[>=`]/.test(e)?$():"<"===e?V():f=10)}function I(e){'"'===e&&(f=11)}function T(e){"'"===e&&(f=11)}function N(e){S.test(e)?f=4:">"===e?W():"<"===e&&V()}function P(e){S.test(e)?f=4:"/"===e?f=12:">"===e?W():"<"===e?V():(f=4,c--)}function R(e){">"===e?(m=new ae(h(h({},m),{isClosing:!0})),W()):f=4}function M(t){"--"===e.substr(c,2)?(c+=2,m=new ae(h(h({},m),{type:"comment"})),f=14):"DOCTYPE"===e.substr(c,7).toUpperCase()?(c+=7,m=new ae(h(h({},m),{type:"doctype"})),f=20):$()}function D(e){"-"===e?f=15:">"===e?$():f=16}function L(e){"-"===e?f=18:">"===e?$():f=16}function B(e){"-"===e&&(f=17)}function F(e){f="-"===e?18:16}function U(e){">"===e?W():"!"===e?f=19:"-"===e||(f=16)}function z(e){"-"===e?f=17:">"===e?W():f=16}function q(e){">"===e?W():"<"===e&&V()}function $(){f=0,m=u}function V(){f=1,m=new ae({idx:c})}function W(){var t=e.slice(d,m.idx);t&&a(t,d),"comment"===m.type?i(m.idx):"doctype"===m.type?l(m.idx):(m.isOpening&&r(m.name,m.idx),m.isClosing&&o(m.name,m.idx)),$(),d=c+1}function H(){var t=m.idx+(m.isClosing?2:1);return e.slice(t,c).toLowerCase()}d=0&&r++},onText:function(e,n){if(0===r){var a=function(e,t){if(!t.global)throw new Error("`splitRegex` must have the 'g' flag set");for(var n,r=[],o=0;n=t.exec(e);)r.push(e.substring(o,n.index)),r.push(n[0]),o=n.index+n[0].length;return r.push(e.substring(o)),r}(e,/( | |<|<|>|>|"|"|')/gi),i=n;a.forEach((function(e,n){if(n%2==0){var r=t.parseText(e,i);o.push.apply(o,r)}i+=e.length}))}},onCloseTag:function(e){n.indexOf(e)>=0&&(r=Math.max(r-1,0))},onComment:function(e){},onDoctype:function(e){}}),o=this.compactMatches(o),o=this.removeUnwantedMatches(o)},e.prototype.compactMatches=function(e){e.sort((function(e,t){return e.getOffset()-t.getOffset()}));for(var t=0;to?t:t+1;e.splice(i,1);continue}if(e[t+1].getOffset()/g,">"));for(var t=this.parse(e),n=[],r=0,o=0,a=t.length;o/i.test(e)}function ue(){var e=[],t=new ie({stripPrefix:!1,url:!0,email:!0,replaceFn:function(t){switch(t.getType()){case"url":e.push({text:t.matchedText,url:t.getUrl()});break;case"email":e.push({text:t.matchedText,url:"mailto:"+t.getEmail().replace(/^mailto:/i,"")})}return!1}});return{links:e,autolinker:t}}function ce(e){var t,n,r,o,a,i,s,l,u,c,p,f,h,d,m=e.tokens,g=null;for(n=0,r=m.length;n=0;t--)if("link_close"!==(a=o[t]).type){if("htmltag"===a.type&&(d=a.content,/^\s]/i.test(d)&&p>0&&p--,le(a.content)&&p++),!(p>0)&&"text"===a.type&&se.test(a.content)){if(g||(f=(g=ue()).links,h=g.autolinker),i=a.content,f.length=0,h.link(i),!f.length)continue;for(s=[],c=a.level,l=0;l({useUnsafeMarkdown:!1})};const ye=ge;function ve(e){let{useUnsafeMarkdown:t=!1}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const n=t,r=t?[]:["style","class"];return t&&!ve.hasWarnedAboutDeprecation&&(console.warn("useUnsafeMarkdown display configuration parameter is deprecated since >3.26.0 and will be removed in v4.0.0."),ve.hasWarnedAboutDeprecation=!0),he().sanitize(e,{ADD_ATTR:["target"],FORBID_TAGS:["style","form"],ALLOW_DATA_ATTR:n,FORBID_ATTR:r})}ve.hasWarnedAboutDeprecation=!1},45308:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>f});var r,o=n(86),a=n.n(o),i=n(8712),s=n.n(i),l=n(90242),u=n(27621);const c=n(95102),p={},f=p;a()(r=s()(c).call(c)).call(r,(function(e){if("./index.js"===e)return;let t=c(e);p[(0,l.Zl)(e)]=t.default?t.default:t})),p.SafeRender=u.default},55812:(e,t,n)=>{"use strict";n.r(t),n.d(t,{AUTHORIZE:()=>f,AUTHORIZE_OAUTH2:()=>m,CONFIGURE_AUTH:()=>y,LOGOUT:()=>h,PRE_AUTHORIZE_OAUTH2:()=>d,RESTORE_AUTHORIZATION:()=>v,SHOW_AUTH_POPUP:()=>p,VALIDATE:()=>g,authPopup:()=>M,authorize:()=>w,authorizeAccessCodeWithBasicAuthentication:()=>I,authorizeAccessCodeWithFormParams:()=>j,authorizeApplication:()=>O,authorizeOauth2:()=>A,authorizeOauth2WithPersistOption:()=>C,authorizePassword:()=>k,authorizeRequest:()=>T,authorizeWithPersistOption:()=>E,configureAuth:()=>N,logout:()=>x,logoutWithPersistOption:()=>_,persistAuthorizationIfNeeded:()=>R,preAuthorizeImplicit:()=>S,restoreAuthorization:()=>P,showDefinitions:()=>b});var r=n(35627),o=n.n(r),a=n(76986),i=n.n(a),s=n(84564),l=n.n(s),u=n(27504),c=n(90242);const p="show_popup",f="authorize",h="logout",d="pre_authorize_oauth2",m="authorize_oauth2",g="validate",y="configure_auth",v="restore_authorization";function b(e){return{type:p,payload:e}}function w(e){return{type:f,payload:e}}const E=e=>t=>{let{authActions:n}=t;n.authorize(e),n.persistAuthorizationIfNeeded()};function x(e){return{type:h,payload:e}}const _=e=>t=>{let{authActions:n}=t;n.logout(e),n.persistAuthorizationIfNeeded()},S=e=>t=>{let{authActions:n,errActions:r}=t,{auth:a,token:i,isValid:s}=e,{schema:l,name:c}=a,p=l.get("flow");delete u.Z.swaggerUIRedirectOauth2,"accessCode"===p||s||r.newAuthErr({authId:c,source:"auth",level:"warning",message:"Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"}),i.error?r.newAuthErr({authId:c,source:"auth",level:"error",message:o()(i)}):n.authorizeOauth2WithPersistOption({auth:a,token:i})};function A(e){return{type:m,payload:e}}const C=e=>t=>{let{authActions:n}=t;n.authorizeOauth2(e),n.persistAuthorizationIfNeeded()},k=e=>t=>{let{authActions:n}=t,{schema:r,name:o,username:a,password:s,passwordType:l,clientId:u,clientSecret:p}=e,f={grant_type:"password",scope:e.scopes.join(" "),username:a,password:s},h={};switch(l){case"request-body":!function(e,t,n){t&&i()(e,{client_id:t});n&&i()(e,{client_secret:n})}(f,u,p);break;case"basic":h.Authorization="Basic "+(0,c.r3)(u+":"+p);break;default:console.warn(`Warning: invalid passwordType ${l} was passed, not including client id and secret`)}return n.authorizeRequest({body:(0,c.GZ)(f),url:r.get("tokenUrl"),name:o,headers:h,query:{},auth:e})};const O=e=>t=>{let{authActions:n}=t,{schema:r,scopes:o,name:a,clientId:i,clientSecret:s}=e,l={Authorization:"Basic "+(0,c.r3)(i+":"+s)},u={grant_type:"client_credentials",scope:o.join(" ")};return n.authorizeRequest({body:(0,c.GZ)(u),name:a,url:r.get("tokenUrl"),auth:e,headers:l})},j=e=>{let{auth:t,redirectUrl:n}=e;return e=>{let{authActions:r}=e,{schema:o,name:a,clientId:i,clientSecret:s,codeVerifier:l}=t,u={grant_type:"authorization_code",code:t.code,client_id:i,client_secret:s,redirect_uri:n,code_verifier:l};return r.authorizeRequest({body:(0,c.GZ)(u),name:a,url:o.get("tokenUrl"),auth:t})}},I=e=>{let{auth:t,redirectUrl:n}=e;return e=>{let{authActions:r}=e,{schema:o,name:a,clientId:i,clientSecret:s,codeVerifier:l}=t,u={Authorization:"Basic "+(0,c.r3)(i+":"+s)},p={grant_type:"authorization_code",code:t.code,client_id:i,redirect_uri:n,code_verifier:l};return r.authorizeRequest({body:(0,c.GZ)(p),name:a,url:o.get("tokenUrl"),auth:t,headers:u})}},T=e=>t=>{let n,{fn:r,getConfigs:a,authActions:s,errActions:u,oas3Selectors:c,specSelectors:p,authSelectors:f}=t,{body:h,query:d={},headers:m={},name:g,url:y,auth:v}=e,{additionalQueryStringParams:b}=f.getConfigs()||{};if(p.isOAS3()){let e=c.serverEffectiveValue(c.selectedServer());n=l()(y,e,!0)}else n=l()(y,p.url(),!0);"object"==typeof b&&(n.query=i()({},n.query,b));const w=n.toString();let E=i()({Accept:"application/json, text/plain, */*","Content-Type":"application/x-www-form-urlencoded","X-Requested-With":"XMLHttpRequest"},m);r.fetch({url:w,method:"post",headers:E,query:d,body:h,requestInterceptor:a().requestInterceptor,responseInterceptor:a().responseInterceptor}).then((function(e){let t=JSON.parse(e.data),n=t&&(t.error||""),r=t&&(t.parseError||"");e.ok?n||r?u.newAuthErr({authId:g,level:"error",source:"auth",message:o()(t)}):s.authorizeOauth2WithPersistOption({auth:v,token:t}):u.newAuthErr({authId:g,level:"error",source:"auth",message:e.statusText})})).catch((e=>{let t=new Error(e).message;if(e.response&&e.response.data){const n=e.response.data;try{const e="string"==typeof n?JSON.parse(n):n;e.error&&(t+=`, error: ${e.error}`),e.error_description&&(t+=`, description: ${e.error_description}`)}catch(e){}}u.newAuthErr({authId:g,level:"error",source:"auth",message:t})}))};function N(e){return{type:y,payload:e}}function P(e){return{type:v,payload:e}}const R=()=>e=>{let{authSelectors:t,getConfigs:n}=e;if(n().persistAuthorization){const e=t.authorized();localStorage.setItem("authorized",o()(e.toJS()))}},M=(e,t)=>()=>{u.Z.swaggerUIRedirectOauth2=t,u.Z.open(e)}},93705:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>u,preauthorizeApiKey:()=>p,preauthorizeBasic:()=>c});var r=n(11189),o=n.n(r),a=n(43962),i=n(55812),s=n(60035),l=n(48302);function u(){return{afterLoad(e){this.rootInjects=this.rootInjects||{},this.rootInjects.initOAuth=e.authActions.configureAuth,this.rootInjects.preauthorizeApiKey=o()(p).call(p,null,e),this.rootInjects.preauthorizeBasic=o()(c).call(c,null,e)},statePlugins:{auth:{reducers:a.default,actions:i,selectors:s},spec:{wrapActions:l}}}}function c(e,t,n,r){const{authActions:{authorize:o},specSelectors:{specJson:a,isOAS3:i}}=e,s=i()?["components","securitySchemes"]:["securityDefinitions"],l=a().getIn([...s,t]);return l?o({[t]:{value:{username:n,password:r},schema:l.toJS()}}):null}function p(e,t,n){const{authActions:{authorize:r},specSelectors:{specJson:o,isOAS3:a}}=e,i=a()?["components","securitySchemes"]:["securityDefinitions"],s=o().getIn([...i,t]);return s?r({[t]:{value:n,schema:s.toJS()}}):null}},43962:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(86),o=n.n(r),a=n(76986),i=n.n(a),s=n(43393),l=n(90242),u=n(55812);const c={[u.SHOW_AUTH_POPUP]:(e,t)=>{let{payload:n}=t;return e.set("showDefinitions",n)},[u.AUTHORIZE]:(e,t)=>{var n;let{payload:r}=t,a=(0,s.fromJS)(r),i=e.get("authorized")||(0,s.Map)();return o()(n=a.entrySeq()).call(n,(t=>{let[n,r]=t;if(!(0,l.Wl)(r.getIn))return e.set("authorized",i);let o=r.getIn(["schema","type"]);if("apiKey"===o||"http"===o)i=i.set(n,r);else if("basic"===o){let e=r.getIn(["value","username"]),t=r.getIn(["value","password"]);i=i.setIn([n,"value"],{username:e,header:"Basic "+(0,l.r3)(e+":"+t)}),i=i.setIn([n,"schema"],r.get("schema"))}})),e.set("authorized",i)},[u.AUTHORIZE_OAUTH2]:(e,t)=>{let n,{payload:r}=t,{auth:o,token:a}=r;o.token=i()({},a),n=(0,s.fromJS)(o);let l=e.get("authorized")||(0,s.Map)();return l=l.set(n.get("name"),n),e.set("authorized",l)},[u.LOGOUT]:(e,t)=>{let{payload:n}=t,r=e.get("authorized").withMutations((e=>{o()(n).call(n,(t=>{e.delete(t)}))}));return e.set("authorized",r)},[u.CONFIGURE_AUTH]:(e,t)=>{let{payload:n}=t;return e.set("configs",n)},[u.RESTORE_AUTHORIZATION]:(e,t)=>{let{payload:n}=t;return e.set("authorized",(0,s.fromJS)(n.authorized))}}},60035:(e,t,n)=>{"use strict";n.r(t),n.d(t,{authorized:()=>x,definitionsForRequirements:()=>E,definitionsToAuthorize:()=>b,getConfigs:()=>S,getDefinitionsByNames:()=>w,isAuthorized:()=>_,shownDefinitions:()=>v});var r=n(86),o=n.n(r),a=n(51679),i=n.n(a),s=n(14418),l=n.n(s),u=n(11882),c=n.n(u),p=n(97606),f=n.n(p),h=n(28222),d=n.n(h),m=n(20573),g=n(43393);const y=e=>e,v=(0,m.P1)(y,(e=>e.get("showDefinitions"))),b=(0,m.P1)(y,(()=>e=>{var t;let{specSelectors:n}=e,r=n.securityDefinitions()||(0,g.Map)({}),a=(0,g.List)();return o()(t=r.entrySeq()).call(t,(e=>{let[t,n]=e,r=(0,g.Map)();r=r.set(t,n),a=a.push(r)})),a})),w=(e,t)=>e=>{var n;let{specSelectors:r}=e;console.warn("WARNING: getDefinitionsByNames is deprecated and will be removed in the next major version.");let a=r.securityDefinitions(),i=(0,g.List)();return o()(n=t.valueSeq()).call(n,(e=>{var t;let n=(0,g.Map)();o()(t=e.entrySeq()).call(t,(e=>{let t,[r,i]=e,s=a.get(r);var l;"oauth2"===s.get("type")&&i.size&&(t=s.get("scopes"),o()(l=t.keySeq()).call(l,(e=>{i.contains(e)||(t=t.delete(e))})),s=s.set("allowedScopes",t));n=n.set(r,s)})),i=i.push(n)})),i},E=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:(0,g.List)();return e=>{let{authSelectors:n}=e;const r=n.definitionsToAuthorize()||(0,g.List)();let a=(0,g.List)();return o()(r).call(r,(e=>{let n=i()(t).call(t,(t=>t.get(e.keySeq().first())));n&&(o()(e).call(e,((t,r)=>{if("oauth2"===t.get("type")){const i=n.get(r);let s=t.get("scopes");var a;if(g.List.isList(i)&&g.Map.isMap(s))o()(a=s.keySeq()).call(a,(e=>{i.contains(e)||(s=s.delete(e))})),e=e.set(r,t.set("scopes",s))}})),a=a.push(e))})),a}},x=(0,m.P1)(y,(e=>e.get("authorized")||(0,g.Map)())),_=(e,t)=>e=>{var n;let{authSelectors:r}=e,o=r.authorized();return g.List.isList(t)?!!l()(n=t.toJS()).call(n,(e=>{var t,n;return-1===c()(t=f()(n=d()(e)).call(n,(e=>!!o.get(e)))).call(t,!1)})).length:null},S=(0,m.P1)(y,(e=>e.get("configs")))},48302:(e,t,n)=>{"use strict";n.r(t),n.d(t,{execute:()=>r});const r=(e,t)=>{let{authSelectors:n,specSelectors:r}=t;return t=>{let{path:o,method:a,operation:i,extras:s}=t,l={authorized:n.authorized()&&n.authorized().toJS(),definitions:r.securityDefinitions()&&r.securityDefinitions().toJS(),specSecurity:r.security()&&r.security().toJS()};return e({path:o,method:a,operation:i,securities:l,...s})}}},70714:(e,t,n)=>{"use strict";n.r(t),n.d(t,{TOGGLE_CONFIGS:()=>o,UPDATE_CONFIGS:()=>r,loaded:()=>s,toggle:()=>i,update:()=>a});const r="configs_update",o="configs_toggle";function a(e,t){return{type:r,payload:{[e]:t}}}function i(e){return{type:o,payload:e}}const s=()=>e=>{let{getConfigs:t,authActions:n}=e;if(t().persistAuthorization){const e=localStorage.getItem("authorized");e&&n.restoreAuthorization({authorized:JSON.parse(e)})}}},92256:(e,t,n)=>{"use strict";n.r(t),n.d(t,{parseYamlConfig:()=>o});var r=n(1272);const o=(e,t)=>{try{return r.ZP.load(e)}catch(e){return t&&t.errActions.newThrownErr(new Error(e)),{}}}},46709:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>u});var r=n(92256),o=n(70714),a=n(22698),i=n(69018),s=n(37743);const l={getLocalConfig:()=>(0,r.parseYamlConfig)('---\nurl: "swagger.yaml"\ndom_id: "#swagger-ui"\nvalidatorUrl: "https://validator.swagger.io/validator"\n')};function u(){return{statePlugins:{spec:{actions:a,selectors:l},configs:{reducers:s.default,actions:o,selectors:i}}}}},37743:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>a});var r=n(43393),o=n(70714);const a={[o.UPDATE_CONFIGS]:(e,t)=>e.merge((0,r.fromJS)(t.payload)),[o.TOGGLE_CONFIGS]:(e,t)=>{const n=t.payload,r=e.get(n);return e.set(n,!r)}}},69018:(e,t,n)=>{"use strict";n.r(t),n.d(t,{get:()=>a});var r=n(58309),o=n.n(r);const a=(e,t)=>e.getIn(o()(t)?t:[t])},22698:(e,t,n)=>{"use strict";n.r(t),n.d(t,{downloadConfig:()=>o,getConfigByUrl:()=>a});var r=n(92256);const o=e=>t=>{const{fn:{fetch:n}}=t;return n(e)},a=(e,t)=>n=>{let{specActions:o}=n;if(e)return o.downloadConfig(e).then(a,a);function a(n){n instanceof Error||n.status>=400?(o.updateLoadingStatus("failedConfig"),o.updateLoadingStatus("failedConfig"),o.updateUrl(""),console.error(n.statusText+" "+e.url),t(null)):t((0,r.parseYamlConfig)(n.text))}}},31970:(e,t,n)=>{"use strict";n.r(t),n.d(t,{setHash:()=>r});const r=e=>e?history.pushState(null,null,`#${e}`):window.location.hash=""},34980:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(41599),o=n(60877),a=n(34584);function i(){return[r.default,{statePlugins:{configs:{wrapActions:{loaded:(e,t)=>function(){e(...arguments);const n=decodeURIComponent(window.location.hash);t.layoutActions.parseDeepLinkHash(n)}}}},wrapComponents:{operation:o.default,OperationTag:a.default}}]}},41599:(e,t,n)=>{"use strict";n.r(t),n.d(t,{clearScrollTo:()=>S,default:()=>A,parseDeepLinkHash:()=>E,readyToScroll:()=>x,scrollTo:()=>w,scrollToElement:()=>_,show:()=>b});var r=n(58309),o=n.n(r),a=n(24278),i=n.n(a),s=n(97606),l=n.n(s),u=n(11882),c=n.n(u),p=n(31970),f=n(45172),h=n.n(f),d=n(90242),m=n(43393),g=n.n(m);const y="layout_scroll_to",v="layout_clear_scroll",b=(e,t)=>{let{getConfigs:n,layoutSelectors:r}=t;return function(){for(var t=arguments.length,a=new Array(t),i=0;i({type:y,payload:o()(e)?e:[e]}),E=e=>t=>{let{layoutActions:n,layoutSelectors:r,getConfigs:o}=t;if(o().deepLinking&&e){var a;let t=i()(e).call(e,1);"!"===t[0]&&(t=i()(t).call(t,1)),"/"===t[0]&&(t=i()(t).call(t,1));const o=l()(a=t.split("/")).call(a,(e=>e||"")),s=r.isShownKeyFromUrlHashArray(o),[u,p="",f=""]=s;if("operations"===u){const e=r.isShownKeyFromUrlHashArray([p]);c()(p).call(p,"_")>-1&&(console.warn("Warning: escaping deep link whitespace with `_` will be unsupported in v4.0, use `%20` instead."),n.show(l()(e).call(e,(e=>e.replace(/_/g," "))),!0)),n.show(e,!0)}(c()(p).call(p,"_")>-1||c()(f).call(f,"_")>-1)&&(console.warn("Warning: escaping deep link whitespace with `_` will be unsupported in v4.0, use `%20` instead."),n.show(l()(s).call(s,(e=>e.replace(/_/g," "))),!0)),n.show(s,!0),n.scrollTo(s)}},x=(e,t)=>n=>{const r=n.layoutSelectors.getScrollToKey();g().is(r,(0,m.fromJS)(e))&&(n.layoutActions.scrollToElement(t),n.layoutActions.clearScrollTo())},_=(e,t)=>n=>{try{t=t||n.fn.getScrollParent(e),h().createScroller(t).to(e)}catch(e){console.error(e)}},S=()=>({type:v});const A={fn:{getScrollParent:function(e,t){const n=document.documentElement;let r=getComputedStyle(e);const o="absolute"===r.position,a=t?/(auto|scroll|hidden)/:/(auto|scroll)/;if("fixed"===r.position)return n;for(let t=e;t=t.parentElement;)if(r=getComputedStyle(t),(!o||"static"!==r.position)&&a.test(r.overflow+r.overflowY+r.overflowX))return t;return n}},statePlugins:{layout:{actions:{scrollToElement:_,scrollTo:w,clearScrollTo:S,readyToScroll:x,parseDeepLinkHash:E},selectors:{getScrollToKey:e=>e.get("scrollToKey"),isShownKeyFromUrlHashArray(e,t){const[n,r]=t;return r?["operations",n,r]:n?["operations-tag",n]:[]},urlHashArrayFromIsShownKey(e,t){let[n,r,o]=t;return"operations"==n?[r,o]:"operations-tag"==n?[r]:[]}},reducers:{[y]:(e,t)=>e.set("scrollToKey",g().fromJS(t.payload)),[v]:e=>e.delete("scrollToKey")},wrapActions:{show:b}}}}},34584:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(61125),o=n.n(r),a=n(67294);const i=(e,t)=>class extends a.Component{constructor(){super(...arguments),o()(this,"onLoad",(e=>{const{tag:n}=this.props,r=["operations-tag",n];t.layoutActions.readyToScroll(r,e)}))}render(){return a.createElement("span",{ref:this.onLoad},a.createElement(e,this.props))}}},60877:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(61125),o=n.n(r),a=n(67294);n(23930);const i=(e,t)=>class extends a.Component{constructor(){super(...arguments),o()(this,"onLoad",(e=>{const{operation:n}=this.props,{tag:r,operationId:o}=n.toObject();let{isShownKey:a}=n.toObject();a=a||["operations",r,o],t.layoutActions.readyToScroll(a,e)}))}render(){return a.createElement("span",{ref:this.onLoad},a.createElement(e,this.props))}}},48011:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>d});var r=n(76986),o=n.n(r),a=n(63460),i=n.n(a),s=n(11882),l=n.n(s),u=n(35627),c=n.n(u),p=n(20573),f=n(43393),h=n(27504);function d(e){let{fn:t}=e;return{statePlugins:{spec:{actions:{download:e=>n=>{let{errActions:r,specSelectors:a,specActions:s,getConfigs:l}=n,{fetch:u}=t;const c=l();function p(t){if(t instanceof Error||t.status>=400)return s.updateLoadingStatus("failed"),r.newThrownErr(o()(new Error((t.message||t.statusText)+" "+e),{source:"fetch"})),void(!t.status&&t instanceof Error&&function(){try{let t;if("URL"in h.Z?t=new(i())(e):(t=document.createElement("a"),t.href=e),"https:"!==t.protocol&&"https:"===h.Z.location.protocol){const e=o()(new Error(`Possible mixed-content issue? The page was loaded over https:// but a ${t.protocol}// URL was specified. Check that you are not attempting to load mixed content.`),{source:"fetch"});return void r.newThrownErr(e)}if(t.origin!==h.Z.location.origin){const e=o()(new Error(`Possible cross-origin (CORS) issue? The URL origin (${t.origin}) does not match the page (${h.Z.location.origin}). Check the server returns the correct 'Access-Control-Allow-*' headers.`),{source:"fetch"});r.newThrownErr(e)}}catch(e){return}}());s.updateLoadingStatus("success"),s.updateSpec(t.text),a.url()!==e&&s.updateUrl(e)}e=e||a.url(),s.updateLoadingStatus("loading"),r.clear({source:"fetch"}),u({url:e,loadSpec:!0,requestInterceptor:c.requestInterceptor||(e=>e),responseInterceptor:c.responseInterceptor||(e=>e),credentials:"same-origin",headers:{Accept:"application/json,*/*"}}).then(p,p)},updateLoadingStatus:e=>{let t=[null,"loading","failed","success","failedConfig"];return-1===l()(t).call(t,e)&&console.error(`Error: ${e} is not one of ${c()(t)}`),{type:"spec_update_loading_status",payload:e}}},reducers:{spec_update_loading_status:(e,t)=>"string"==typeof t.payload?e.set("loadingStatus",t.payload):e},selectors:{loadingStatus:(0,p.P1)((e=>e||(0,f.Map)()),(e=>e.get("loadingStatus")||null))}}}}}},34966:(e,t,n)=>{"use strict";n.r(t),n.d(t,{CLEAR:()=>u,CLEAR_BY:()=>c,NEW_AUTH_ERR:()=>l,NEW_SPEC_ERR:()=>i,NEW_SPEC_ERR_BATCH:()=>s,NEW_THROWN_ERR:()=>o,NEW_THROWN_ERR_BATCH:()=>a,clear:()=>g,clearBy:()=>y,newAuthErr:()=>m,newSpecErr:()=>h,newSpecErrBatch:()=>d,newThrownErr:()=>p,newThrownErrBatch:()=>f});var r=n(7710);const o="err_new_thrown_err",a="err_new_thrown_err_batch",i="err_new_spec_err",s="err_new_spec_err_batch",l="err_new_auth_err",u="err_clear",c="err_clear_by";function p(e){return{type:o,payload:(0,r.serializeError)(e)}}function f(e){return{type:a,payload:e}}function h(e){return{type:i,payload:e}}function d(e){return{type:s,payload:e}}function m(e){return{type:l,payload:e}}function g(){return{type:u,payload:arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}}}function y(){return{type:c,payload:arguments.length>0&&void 0!==arguments[0]?arguments[0]:()=>!0}}},56982:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(14418),o=n.n(r),a=n(97606),i=n.n(a),s=n(54061),l=n.n(s);const u=[n(2392),n(21835)];function c(e){var t;let n={jsSpec:{}},r=l()(u,((e,t)=>{try{let r=t.transform(e,n);return o()(r).call(r,(e=>!!e))}catch(t){return console.error("Transformer error:",t),e}}),e);return i()(t=o()(r).call(r,(e=>!!e))).call(t,(e=>(!e.get("line")&&e.get("path"),e)))}},2392:(e,t,n)=>{"use strict";n.r(t),n.d(t,{transform:()=>p});var r=n(97606),o=n.n(r),a=n(11882),i=n.n(a),s=n(24278),l=n.n(s),u=n(24282),c=n.n(u);function p(e){return o()(e).call(e,(e=>{var t;let n="is not of a type(s)",r=i()(t=e.get("message")).call(t,n);if(r>-1){var o,a;let t=l()(o=e.get("message")).call(o,r+n.length).split(",");return e.set("message",l()(a=e.get("message")).call(a,0,r)+function(e){return c()(e).call(e,((e,t,n,r)=>n===r.length-1&&r.length>1?e+"or "+t:r[n+1]&&r.length>2?e+t+", ":r[n+1]?e+t+" ":e+t),"should be a")}(t))}return e}))}},21835:(e,t,n)=>{"use strict";n.r(t),n.d(t,{transform:()=>r});n(97606),n(11882),n(27361),n(43393);function r(e,t){let{jsSpec:n}=t;return e}},77793:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(93527),o=n(34966),a=n(87667);function i(e){return{statePlugins:{err:{reducers:(0,r.default)(e),actions:o,selectors:a}}}}},93527:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>y});var r=n(76986),o=n.n(r),a=n(97606),i=n.n(a),s=n(39022),l=n.n(s),u=n(14418),c=n.n(u),p=n(2250),f=n.n(p),h=n(34966),d=n(43393),m=n(56982);let g={line:0,level:"error",message:"Unknown error"};function y(){return{[h.NEW_THROWN_ERR]:(e,t)=>{let{payload:n}=t,r=o()(g,n,{type:"thrown"});return e.update("errors",(e=>(e||(0,d.List)()).push((0,d.fromJS)(r)))).update("errors",(e=>(0,m.default)(e)))},[h.NEW_THROWN_ERR_BATCH]:(e,t)=>{let{payload:n}=t;return n=i()(n).call(n,(e=>(0,d.fromJS)(o()(g,e,{type:"thrown"})))),e.update("errors",(e=>{var t;return l()(t=e||(0,d.List)()).call(t,(0,d.fromJS)(n))})).update("errors",(e=>(0,m.default)(e)))},[h.NEW_SPEC_ERR]:(e,t)=>{let{payload:n}=t,r=(0,d.fromJS)(n);return r=r.set("type","spec"),e.update("errors",(e=>(e||(0,d.List)()).push((0,d.fromJS)(r)).sortBy((e=>e.get("line"))))).update("errors",(e=>(0,m.default)(e)))},[h.NEW_SPEC_ERR_BATCH]:(e,t)=>{let{payload:n}=t;return n=i()(n).call(n,(e=>(0,d.fromJS)(o()(g,e,{type:"spec"})))),e.update("errors",(e=>{var t;return l()(t=e||(0,d.List)()).call(t,(0,d.fromJS)(n))})).update("errors",(e=>(0,m.default)(e)))},[h.NEW_AUTH_ERR]:(e,t)=>{let{payload:n}=t,r=(0,d.fromJS)(o()({},n));return r=r.set("type","auth"),e.update("errors",(e=>(e||(0,d.List)()).push((0,d.fromJS)(r)))).update("errors",(e=>(0,m.default)(e)))},[h.CLEAR]:(e,t)=>{var n;let{payload:r}=t;if(!r||!e.get("errors"))return e;let o=c()(n=e.get("errors")).call(n,(e=>{var t;return f()(t=e.keySeq()).call(t,(t=>{const n=e.get(t),o=r[t];return!o||n!==o}))}));return e.merge({errors:o})},[h.CLEAR_BY]:(e,t)=>{var n;let{payload:r}=t;if(!r||"function"!=typeof r)return e;let o=c()(n=e.get("errors")).call(n,(e=>r(e)));return e.merge({errors:o})}}}},87667:(e,t,n)=>{"use strict";n.r(t),n.d(t,{allErrors:()=>a,lastError:()=>i});var r=n(43393),o=n(20573);const a=(0,o.P1)((e=>e),(e=>e.get("errors",(0,r.List)()))),i=(0,o.P1)(a,(e=>e.last()))},49978:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(4309);function o(){return{fn:{opsFilter:r.default}}}},4309:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(14418),o=n.n(r),a=n(11882),i=n.n(a);function s(e,t){return o()(e).call(e,((e,n)=>-1!==i()(n).call(n,t)))}},25474:(e,t,n)=>{"use strict";n.r(t),n.d(t,{SHOW:()=>s,UPDATE_FILTER:()=>a,UPDATE_LAYOUT:()=>o,UPDATE_MODE:()=>i,changeMode:()=>p,show:()=>c,updateFilter:()=>u,updateLayout:()=>l});var r=n(90242);const o="layout_update_layout",a="layout_update_filter",i="layout_update_mode",s="layout_show";function l(e){return{type:o,payload:e}}function u(e){return{type:a,payload:e}}function c(e){let t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];return e=(0,r.AF)(e),{type:s,payload:{thing:e,shown:t}}}function p(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return e=(0,r.AF)(e),{type:i,payload:{thing:e,mode:t}}}},26821:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(5672),o=n(25474),a=n(4400),i=n(28989);function s(){return{statePlugins:{layout:{reducers:r.default,actions:o,selectors:a},spec:{wrapSelectors:i}}}}},5672:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(39022),o=n.n(r),a=n(43393),i=n(25474);const s={[i.UPDATE_LAYOUT]:(e,t)=>e.set("layout",t.payload),[i.UPDATE_FILTER]:(e,t)=>e.set("filter",t.payload),[i.SHOW]:(e,t)=>{const n=t.payload.shown,r=(0,a.fromJS)(t.payload.thing);return e.update("shown",(0,a.fromJS)({}),(e=>e.set(r,n)))},[i.UPDATE_MODE]:(e,t)=>{var n;let r=t.payload.thing,a=t.payload.mode;return e.setIn(o()(n=["modes"]).call(n,r),(a||"")+"")}}},4400:(e,t,n)=>{"use strict";n.r(t),n.d(t,{current:()=>i,currentFilter:()=>s,isShown:()=>l,showSummary:()=>c,whatMode:()=>u});var r=n(20573),o=n(90242),a=n(43393);const i=e=>e.get("layout"),s=e=>e.get("filter"),l=(e,t,n)=>(t=(0,o.AF)(t),e.get("shown",(0,a.fromJS)({})).get((0,a.fromJS)(t),n)),u=function(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";return t=(0,o.AF)(t),e.getIn(["modes",...t],n)},c=(0,r.P1)((e=>e),(e=>!l(e,"editor")))},28989:(e,t,n)=>{"use strict";n.r(t),n.d(t,{taggedOperations:()=>a});var r=n(24278),o=n.n(r);const a=(e,t)=>function(n){for(var r=arguments.length,a=new Array(r>1?r-1:0),i=1;i=0&&(s=o()(s).call(s,0,f)),s}},9150:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>a});var r=n(11189),o=n.n(r);function a(e){let{configs:t}=e;const n={debug:0,info:1,log:2,warn:3,error:4},r=e=>n[e]||-1;let{logLevel:a}=t,i=r(a);function s(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),o=1;o=i&&console[e](...n)}return s.warn=o()(s).call(s,null,"warn"),s.error=o()(s).call(s,null,"error"),s.info=o()(s).call(s,null,"info"),s.debug=o()(s).call(s,null,"debug"),{rootInjects:{log:s}}}},67002:(e,t,n)=>{"use strict";n.r(t),n.d(t,{CLEAR_REQUEST_BODY_VALIDATE_ERROR:()=>f,CLEAR_REQUEST_BODY_VALUE:()=>h,SET_REQUEST_BODY_VALIDATE_ERROR:()=>p,UPDATE_ACTIVE_EXAMPLES_MEMBER:()=>s,UPDATE_REQUEST_BODY_INCLUSION:()=>i,UPDATE_REQUEST_BODY_VALUE:()=>o,UPDATE_REQUEST_BODY_VALUE_RETAIN_FLAG:()=>a,UPDATE_REQUEST_CONTENT_TYPE:()=>l,UPDATE_RESPONSE_CONTENT_TYPE:()=>u,UPDATE_SELECTED_SERVER:()=>r,UPDATE_SERVER_VARIABLE_VALUE:()=>c,clearRequestBodyValidateError:()=>_,clearRequestBodyValue:()=>A,initRequestBodyValidateError:()=>S,setActiveExamplesMember:()=>v,setRequestBodyInclusion:()=>y,setRequestBodyValidateError:()=>x,setRequestBodyValue:()=>m,setRequestContentType:()=>b,setResponseContentType:()=>w,setRetainRequestBodyValueFlag:()=>g,setSelectedServer:()=>d,setServerVariableValue:()=>E});const r="oas3_set_servers",o="oas3_set_request_body_value",a="oas3_set_request_body_retain_flag",i="oas3_set_request_body_inclusion",s="oas3_set_active_examples_member",l="oas3_set_request_content_type",u="oas3_set_response_content_type",c="oas3_set_server_variable_value",p="oas3_set_request_body_validate_error",f="oas3_clear_request_body_validate_error",h="oas3_clear_request_body_value";function d(e,t){return{type:r,payload:{selectedServerUrl:e,namespace:t}}}function m(e){let{value:t,pathMethod:n}=e;return{type:o,payload:{value:t,pathMethod:n}}}const g=e=>{let{value:t,pathMethod:n}=e;return{type:a,payload:{value:t,pathMethod:n}}};function y(e){let{value:t,pathMethod:n,name:r}=e;return{type:i,payload:{value:t,pathMethod:n,name:r}}}function v(e){let{name:t,pathMethod:n,contextType:r,contextName:o}=e;return{type:s,payload:{name:t,pathMethod:n,contextType:r,contextName:o}}}function b(e){let{value:t,pathMethod:n}=e;return{type:l,payload:{value:t,pathMethod:n}}}function w(e){let{value:t,path:n,method:r}=e;return{type:u,payload:{value:t,path:n,method:r}}}function E(e){let{server:t,namespace:n,key:r,val:o}=e;return{type:c,payload:{server:t,namespace:n,key:r,val:o}}}const x=e=>{let{path:t,method:n,validationErrors:r}=e;return{type:p,payload:{path:t,method:n,validationErrors:r}}},_=e=>{let{path:t,method:n}=e;return{type:f,payload:{path:t,method:n}}},S=e=>{let{pathMethod:t}=e;return{type:f,payload:{path:t[0],method:t[1]}}},A=e=>{let{pathMethod:t}=e;return{type:h,payload:{pathMethod:t}}}},73723:(e,t,n)=>{"use strict";n.r(t),n.d(t,{definitionsToAuthorize:()=>f});var r=n(86),o=n.n(r),a=n(14418),i=n.n(a),s=n(24282),l=n.n(s),u=n(20573),c=n(43393),p=n(7779);const f=(h=(0,u.P1)((e=>e),(e=>{let{specSelectors:t}=e;return t.securityDefinitions()}),((e,t)=>{var n;let r=(0,c.List)();return t?(o()(n=t.entrySeq()).call(n,(e=>{let[t,n]=e;const a=n.get("type");var s;if("oauth2"===a&&o()(s=n.get("flows").entrySeq()).call(s,(e=>{let[o,a]=e,s=(0,c.fromJS)({flow:o,authorizationUrl:a.get("authorizationUrl"),tokenUrl:a.get("tokenUrl"),scopes:a.get("scopes"),type:n.get("type"),description:n.get("description")});r=r.push(new c.Map({[t]:i()(s).call(s,(e=>void 0!==e))}))})),"http"!==a&&"apiKey"!==a||(r=r.push(new c.Map({[t]:n}))),"openIdConnect"===a&&n.get("openIdConnectData")){let e=n.get("openIdConnectData"),a=e.get("grant_types_supported")||["authorization_code","implicit"];o()(a).call(a,(o=>{var a;let s=e.get("scopes_supported")&&l()(a=e.get("scopes_supported")).call(a,((e,t)=>e.set(t,"")),new c.Map),u=(0,c.fromJS)({flow:o,authorizationUrl:e.get("authorization_endpoint"),tokenUrl:e.get("token_endpoint"),scopes:s,type:"oauth2",openIdConnectUrl:n.get("openIdConnectUrl")});r=r.push(new c.Map({[t]:i()(u).call(u,(e=>void 0!==e))}))}))}})),r):r})),(e,t)=>function(){const n=t.getSystem().specSelectors.specJson();for(var r=arguments.length,o=new Array(r),a=0;a{"use strict";n.r(t),n.d(t,{default:()=>u});var r=n(23101),o=n.n(r),a=n(97606),i=n.n(a),s=n(67294),l=(n(23930),n(43393));const u=e=>{var t;let{callbacks:n,getComponent:r,specPath:a}=e;const u=r("OperationContainer",!0);if(!n)return s.createElement("span",null,"No callbacks");let c=i()(t=n.entrySeq()).call(t,(t=>{var n;let[r,c]=t;return s.createElement("div",{key:r},s.createElement("h2",null,r),i()(n=c.entrySeq()).call(n,(t=>{var n;let[c,p]=t;return"$$ref"===c?null:s.createElement("div",{key:c},i()(n=p.entrySeq()).call(n,(t=>{let[n,i]=t;if("$$ref"===n)return null;let p=(0,l.fromJS)({operation:i});return s.createElement(u,o()({},e,{op:p,key:n,tag:"",method:n,path:c,specPath:a.push(r,c,n),allowTryItOut:!1}))})))})))}));return s.createElement("div",null,c)}},86775:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>f});var r=n(61125),o=n.n(r),a=n(76986),i=n.n(a),s=n(14418),l=n.n(s),u=n(97606),c=n.n(u),p=n(67294);class f extends p.Component{constructor(e,t){super(e,t),o()(this,"onChange",(e=>{let{onChange:t}=this.props,{value:n,name:r}=e.target,o=i()({},this.state.value);r?o[r]=n:o=n,this.setState({value:o},(()=>t(this.state)))}));let{name:n,schema:r}=this.props,a=this.getValue();this.state={name:n,schema:r,value:a}}getValue(){let{name:e,authorized:t}=this.props;return t&&t.getIn([e,"value"])}render(){var e;let{schema:t,getComponent:n,errSelectors:r,name:o}=this.props;const a=n("Input"),i=n("Row"),s=n("Col"),u=n("authError"),f=n("Markdown",!0),h=n("JumpToPath",!0),d=(t.get("scheme")||"").toLowerCase();let m=this.getValue(),g=l()(e=r.allErrors()).call(e,(e=>e.get("authId")===o));if("basic"===d){var y;let e=m?m.get("username"):null;return p.createElement("div",null,p.createElement("h4",null,p.createElement("code",null,o||t.get("name")),"  (http, Basic)",p.createElement(h,{path:["securityDefinitions",o]})),e&&p.createElement("h6",null,"Authorized"),p.createElement(i,null,p.createElement(f,{source:t.get("description")})),p.createElement(i,null,p.createElement("label",null,"Username:"),e?p.createElement("code",null," ",e," "):p.createElement(s,null,p.createElement(a,{type:"text",required:"required",name:"username","aria-label":"auth-basic-username",onChange:this.onChange,autoFocus:!0}))),p.createElement(i,null,p.createElement("label",null,"Password:"),e?p.createElement("code",null," ****** "):p.createElement(s,null,p.createElement(a,{autoComplete:"new-password",name:"password",type:"password","aria-label":"auth-basic-password",onChange:this.onChange}))),c()(y=g.valueSeq()).call(y,((e,t)=>p.createElement(u,{error:e,key:t}))))}var v;return"bearer"===d?p.createElement("div",null,p.createElement("h4",null,p.createElement("code",null,o||t.get("name")),"  (http, Bearer)",p.createElement(h,{path:["securityDefinitions",o]})),m&&p.createElement("h6",null,"Authorized"),p.createElement(i,null,p.createElement(f,{source:t.get("description")})),p.createElement(i,null,p.createElement("label",null,"Value:"),m?p.createElement("code",null," ****** "):p.createElement(s,null,p.createElement(a,{type:"text","aria-label":"auth-bearer-value",onChange:this.onChange,autoFocus:!0}))),c()(v=g.valueSeq()).call(v,((e,t)=>p.createElement(u,{error:e,key:t})))):p.createElement("div",null,p.createElement("em",null,p.createElement("b",null,o)," HTTP authentication: unsupported scheme ",`'${d}'`))}}},76467:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(33427),o=n(42458),a=n(15757),i=n(56617),s=n(9928),l=n(45327),u=n(86775),c=n(96796);const p={Callbacks:r.default,HttpAuth:u.default,RequestBody:o.default,Servers:i.default,ServersContainer:s.default,RequestBodyEditor:l.default,OperationServers:c.default,operationLink:a.default}},15757:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>u});var r=n(35627),o=n.n(r),a=n(97606),i=n.n(a),s=n(67294);n(23930);class l extends s.Component{render(){const{link:e,name:t,getComponent:n}=this.props,r=n("Markdown",!0);let a=e.get("operationId")||e.get("operationRef"),l=e.get("parameters")&&e.get("parameters").toJS(),u=e.get("description");return s.createElement("div",{className:"operation-link"},s.createElement("div",{className:"description"},s.createElement("b",null,s.createElement("code",null,t)),u?s.createElement(r,{source:u}):null),s.createElement("pre",null,"Operation `",a,"`",s.createElement("br",null),s.createElement("br",null),"Parameters ",function(e,t){var n;if("string"!=typeof t)return"";return i()(n=t.split("\n")).call(n,((t,n)=>n>0?Array(e+1).join(" ")+t:t)).join("\n")}(0,o()(l,null,2))||"{}",s.createElement("br",null)))}}const u=l},96796:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(61125),o=n.n(r),a=n(67294);n(23930);class i extends a.Component{constructor(){super(...arguments),o()(this,"setSelectedServer",(e=>{const{path:t,method:n}=this.props;return this.forceUpdate(),this.props.setSelectedServer(e,`${t}:${n}`)})),o()(this,"setServerVariableValue",(e=>{const{path:t,method:n}=this.props;return this.forceUpdate(),this.props.setServerVariableValue({...e,namespace:`${t}:${n}`})})),o()(this,"getSelectedServer",(()=>{const{path:e,method:t}=this.props;return this.props.getSelectedServer(`${e}:${t}`)})),o()(this,"getServerVariable",((e,t)=>{const{path:n,method:r}=this.props;return this.props.getServerVariable({namespace:`${n}:${r}`,server:e},t)})),o()(this,"getEffectiveServerValue",(e=>{const{path:t,method:n}=this.props;return this.props.getEffectiveServerValue({server:e,namespace:`${t}:${n}`})}))}render(){const{operationServers:e,pathServers:t,getComponent:n}=this.props;if(!e&&!t)return null;const r=n("Servers"),o=e||t,i=e?"operation":"path";return a.createElement("div",{className:"opblock-section operation-servers"},a.createElement("div",{className:"opblock-section-header"},a.createElement("div",{className:"tab-header"},a.createElement("h4",{className:"opblock-title"},"Servers"))),a.createElement("div",{className:"opblock-description-wrapper"},a.createElement("h4",{className:"message"},"These ",i,"-level options override the global server options."),a.createElement(r,{servers:o,currentServer:this.getSelectedServer(),setSelectedServer:this.setSelectedServer,setServerVariableValue:this.setServerVariableValue,getServerVariable:this.getServerVariable,getEffectiveServerValue:this.getEffectiveServerValue})))}}},45327:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(61125),o=n.n(r),a=n(67294),i=n(94184),s=n.n(i),l=n(90242);const u=Function.prototype;class c extends a.PureComponent{constructor(e,t){super(e,t),o()(this,"applyDefaultValue",(e=>{const{onChange:t,defaultValue:n}=e||this.props;return this.setState({value:n}),t(n)})),o()(this,"onChange",(e=>{this.props.onChange((0,l.Pz)(e))})),o()(this,"onDomChange",(e=>{const t=e.target.value;this.setState({value:t},(()=>this.onChange(t)))})),this.state={value:(0,l.Pz)(e.value)||e.defaultValue},e.onChange(e.value)}UNSAFE_componentWillReceiveProps(e){this.props.value!==e.value&&e.value!==this.state.value&&this.setState({value:(0,l.Pz)(e.value)}),!e.value&&e.defaultValue&&this.state.value&&this.applyDefaultValue(e)}render(){let{getComponent:e,errors:t}=this.props,{value:n}=this.state,r=t.size>0;const o=e("TextArea");return a.createElement("div",{className:"body-param"},a.createElement(o,{className:s()("body-param__text",{invalid:r}),title:t.size?t.join(", "):"",value:n,onChange:this.onDomChange}))}}o()(c,"defaultProps",{onChange:u,userHasEditedBody:!1})},42458:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>g,getDefaultRequestBodyValue:()=>m});var r=n(97606),o=n.n(r),a=n(11882),i=n.n(a),s=n(58118),l=n.n(s),u=n(58309),c=n.n(u),p=n(67294),f=(n(23930),n(43393)),h=n(90242),d=n(2518);const m=(e,t,n)=>{const r=e.getIn(["content",t]),o=r.get("schema").toJS(),a=void 0!==r.get("examples"),i=r.get("example"),s=a?r.getIn(["examples",n,"value"]):i,l=(0,h.xi)(o,t,{includeWriteOnly:!0},s);return(0,h.Pz)(l)},g=e=>{let{userHasEditedBody:t,requestBody:n,requestBodyValue:r,requestBodyInclusionSetting:a,requestBodyErrors:s,getComponent:u,getConfigs:g,specSelectors:y,fn:v,contentType:b,isExecute:w,specPath:E,onChange:x,onChangeIncludeEmpty:_,activeExamplesKey:S,updateActiveExamplesKey:A,setRetainRequestBodyValueFlag:C}=e;const k=e=>{x(e.target.files[0])},O=e=>{let t={key:e,shouldDispatchInit:!1,defaultValue:!0};return"no value"===a.get(e,"no value")&&(t.shouldDispatchInit=!0),t},j=u("Markdown",!0),I=u("modelExample"),T=u("RequestBodyEditor"),N=u("highlightCode"),P=u("ExamplesSelectValueRetainer"),R=u("Example"),M=u("ParameterIncludeEmpty"),{showCommonExtensions:D}=g(),L=n&&n.get("description")||null,B=n&&n.get("content")||new f.OrderedMap;b=b||B.keySeq().first()||"";const F=B.get(b,(0,f.OrderedMap)()),U=F.get("schema",(0,f.OrderedMap)()),z=F.get("examples",null),q=null==z?void 0:o()(z).call(z,((e,t)=>{var r;const o=null===(r=e)||void 0===r?void 0:r.get("value",null);return o&&(e=e.set("value",m(n,b,t),o)),e}));if(s=f.List.isList(s)?s:(0,f.List)(),!F.size)return null;const $="object"===F.getIn(["schema","type"]),V="binary"===F.getIn(["schema","format"]),W="base64"===F.getIn(["schema","format"]);if("application/octet-stream"===b||0===i()(b).call(b,"image/")||0===i()(b).call(b,"audio/")||0===i()(b).call(b,"video/")||V||W){const e=u("Input");return w?p.createElement(e,{type:"file",onChange:k}):p.createElement("i",null,"Example values are not available for ",p.createElement("code",null,b)," media types.")}if($&&("application/x-www-form-urlencoded"===b||0===i()(b).call(b,"multipart/"))&&U.get("properties",(0,f.OrderedMap)()).size>0){var H;const e=u("JsonSchemaForm"),t=u("ParameterExt"),n=U.get("properties",(0,f.OrderedMap)());return r=f.Map.isMap(r)?r:(0,f.OrderedMap)(),p.createElement("div",{className:"table-container"},L&&p.createElement(j,{source:L}),p.createElement("table",null,p.createElement("tbody",null,f.Map.isMap(n)&&o()(H=n.entrySeq()).call(H,(n=>{var i,d;let[m,g]=n;if(g.get("readOnly"))return;let y=D?(0,h.po)(g):null;const b=l()(i=U.get("required",(0,f.List)())).call(i,m),E=g.get("type"),S=g.get("format"),A=g.get("description"),C=r.getIn([m,"value"]),k=r.getIn([m,"errors"])||s,I=a.get(m)||!1,T=g.has("default")||g.has("example")||g.hasIn(["items","example"])||g.hasIn(["items","default"]),N=g.has("enum")&&(1===g.get("enum").size||b),P=T||N;let R="";"array"!==E||P||(R=[]),("object"===E||P)&&(R=(0,h.xi)(g,!1,{includeWriteOnly:!0})),"string"!=typeof R&&"object"===E&&(R=(0,h.Pz)(R)),"string"==typeof R&&"array"===E&&(R=JSON.parse(R));const L="string"===E&&("binary"===S||"base64"===S);return p.createElement("tr",{key:m,className:"parameters","data-property-name":m},p.createElement("td",{className:"parameters-col_name"},p.createElement("div",{className:b?"parameter__name required":"parameter__name"},m,b?p.createElement("span",null," *"):null),p.createElement("div",{className:"parameter__type"},E,S&&p.createElement("span",{className:"prop-format"},"($",S,")"),D&&y.size?o()(d=y.entrySeq()).call(d,(e=>{let[n,r]=e;return p.createElement(t,{key:`${n}-${r}`,xKey:n,xVal:r})})):null),p.createElement("div",{className:"parameter__deprecated"},g.get("deprecated")?"deprecated":null)),p.createElement("td",{className:"parameters-col_description"},p.createElement(j,{source:A}),w?p.createElement("div",null,p.createElement(e,{fn:v,dispatchInitialValue:!L,schema:g,description:m,getComponent:u,value:void 0===C?R:C,required:b,errors:k,onChange:e=>{x(e,[m])}}),b?null:p.createElement(M,{onChange:e=>_(m,e),isIncluded:I,isIncludedOptions:O(m),isDisabled:c()(C)?0!==C.length:!(0,h.O2)(C)})):null))})))))}const J=m(n,b,S);let K=null;return(0,d.O)(J)&&(K="json"),p.createElement("div",null,L&&p.createElement(j,{source:L}),q?p.createElement(P,{userHasEditedBody:t,examples:q,currentKey:S,currentUserInputValue:r,onSelect:e=>{A(e)},updateValue:x,defaultToFirstExample:!0,getComponent:u,setRetainRequestBodyValueFlag:C}):null,w?p.createElement("div",null,p.createElement(T,{value:r,errors:s,defaultValue:J,onChange:x,getComponent:u})):p.createElement(I,{getComponent:u,getConfigs:g,specSelectors:y,expandDepth:1,isExecute:w,schema:F.get("schema"),specPath:E.push("content",b),example:p.createElement(N,{className:"body-param__example",getConfigs:g,language:K,value:(0,h.Pz)(r)||J}),includeWriteOnly:!0}),q?p.createElement(R,{example:q.get(S),getComponent:u,getConfigs:g}):null)}},9928:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);class o extends r.Component{render(){const{specSelectors:e,oas3Selectors:t,oas3Actions:n,getComponent:o}=this.props,a=e.servers(),i=o("Servers");return a&&a.size?r.createElement("div",null,r.createElement("span",{className:"servers-title"},"Servers"),r.createElement(i,{servers:a,currentServer:t.selectedServer(),setSelectedServer:n.setSelectedServer,setServerVariableValue:n.setServerVariableValue,getServerVariable:t.serverVariableValue,getEffectiveServerValue:t.serverEffectiveValue})):null}}},56617:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(61125),o=n.n(r),a=n(51679),i=n.n(a),s=n(97606),l=n.n(s),u=n(67294),c=n(43393);n(23930);class p extends u.Component{constructor(){super(...arguments),o()(this,"onServerChange",(e=>{this.setServer(e.target.value)})),o()(this,"onServerVariableValueChange",(e=>{let{setServerVariableValue:t,currentServer:n}=this.props,r=e.target.getAttribute("data-variable"),o=e.target.value;"function"==typeof t&&t({server:n,key:r,val:o})})),o()(this,"setServer",(e=>{let{setSelectedServer:t}=this.props;t(e)}))}componentDidMount(){var e;let{servers:t,currentServer:n}=this.props;n||this.setServer(null===(e=t.first())||void 0===e?void 0:e.get("url"))}UNSAFE_componentWillReceiveProps(e){let{servers:t,setServerVariableValue:n,getServerVariable:r}=e;if(this.props.currentServer!==e.currentServer||this.props.servers!==e.servers){var o;let a=i()(t).call(t,(t=>t.get("url")===e.currentServer)),s=i()(o=this.props.servers).call(o,(e=>e.get("url")===this.props.currentServer))||(0,c.OrderedMap)();if(!a)return this.setServer(t.first().get("url"));let u=s.get("variables")||(0,c.OrderedMap)(),p=(i()(u).call(u,(e=>e.get("default")))||(0,c.OrderedMap)()).get("default"),f=a.get("variables")||(0,c.OrderedMap)(),h=(i()(f).call(f,(e=>e.get("default")))||(0,c.OrderedMap)()).get("default");l()(f).call(f,((t,o)=>{r(e.currentServer,o)&&p===h||n({server:e.currentServer,key:o,val:t.get("default")||""})}))}}render(){var e,t;let{servers:n,currentServer:r,getServerVariable:o,getEffectiveServerValue:a}=this.props,s=(i()(n).call(n,(e=>e.get("url")===r))||(0,c.OrderedMap)()).get("variables")||(0,c.OrderedMap)(),p=0!==s.size;return u.createElement("div",{className:"servers"},u.createElement("label",{htmlFor:"servers"},u.createElement("select",{onChange:this.onServerChange,value:r},l()(e=n.valueSeq()).call(e,(e=>u.createElement("option",{value:e.get("url"),key:e.get("url")},e.get("url"),e.get("description")&&` - ${e.get("description")}`))).toArray())),p?u.createElement("div",null,u.createElement("div",{className:"computed-url"},"Computed URL:",u.createElement("code",null,a(r))),u.createElement("h4",null,"Server variables"),u.createElement("table",null,u.createElement("tbody",null,l()(t=s.entrySeq()).call(t,(e=>{var t;let[n,a]=e;return u.createElement("tr",{key:n},u.createElement("td",null,n),u.createElement("td",null,a.get("enum")?u.createElement("select",{"data-variable":n,onChange:this.onServerVariableValueChange},l()(t=a.get("enum")).call(t,(e=>u.createElement("option",{selected:e===o(r,n),key:e,value:e},e)))):u.createElement("input",{type:"text",value:o(r,n)||"",onChange:this.onServerVariableValueChange,"data-variable":n})))}))))):null)}}},7779:(e,t,n)=>{"use strict";n.r(t),n.d(t,{OAS3ComponentWrapFactory:()=>c,isOAS3:()=>l,isSwagger2:()=>u});var r=n(23101),o=n.n(r),a=n(27043),i=n.n(a),s=n(67294);function l(e){const t=e.get("openapi");return"string"==typeof t&&(i()(t).call(t,"3.0.")&&t.length>4)}function u(e){const t=e.get("swagger");return"string"==typeof t&&i()(t).call(t,"2.0")}function c(e){return(t,n)=>r=>{if(n&&n.specSelectors&&n.specSelectors.specJson){return l(n.specSelectors.specJson())?s.createElement(e,o()({},r,n,{Ori:t})):s.createElement(t,r)}return console.warn("OAS3 wrapper: couldn't get spec"),null}}},97451:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(92044),o=n(73723),a=n(91741),i=n(76467),s=n(37761),l=n(67002),u=n(5065),c=n(62109);function p(){return{components:i.default,wrapComponents:s.default,statePlugins:{spec:{wrapSelectors:r,selectors:a},auth:{wrapSelectors:o},oas3:{actions:l,reducers:c.default,selectors:u}}}}},62109:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(8712),o=n.n(r),a=n(86),i=n.n(a),s=n(24282),l=n.n(s),u=n(43393),c=n(67002);const p={[c.UPDATE_SELECTED_SERVER]:(e,t)=>{let{payload:{selectedServerUrl:n,namespace:r}}=t;const o=r?[r,"selectedServer"]:["selectedServer"];return e.setIn(o,n)},[c.UPDATE_REQUEST_BODY_VALUE]:(e,t)=>{let{payload:{value:n,pathMethod:r}}=t,[a,s]=r;if(!u.Map.isMap(n))return e.setIn(["requestData",a,s,"bodyValue"],n);let l,c=e.getIn(["requestData",a,s,"bodyValue"])||(0,u.Map)();u.Map.isMap(c)||(c=(0,u.Map)());const[...p]=o()(n).call(n);return i()(p).call(p,(e=>{let t=n.getIn([e]);c.has(e)&&u.Map.isMap(t)||(l=c.setIn([e,"value"],t))})),e.setIn(["requestData",a,s,"bodyValue"],l)},[c.UPDATE_REQUEST_BODY_VALUE_RETAIN_FLAG]:(e,t)=>{let{payload:{value:n,pathMethod:r}}=t,[o,a]=r;return e.setIn(["requestData",o,a,"retainBodyValue"],n)},[c.UPDATE_REQUEST_BODY_INCLUSION]:(e,t)=>{let{payload:{value:n,pathMethod:r,name:o}}=t,[a,i]=r;return e.setIn(["requestData",a,i,"bodyInclusion",o],n)},[c.UPDATE_ACTIVE_EXAMPLES_MEMBER]:(e,t)=>{let{payload:{name:n,pathMethod:r,contextType:o,contextName:a}}=t,[i,s]=r;return e.setIn(["examples",i,s,o,a,"activeExample"],n)},[c.UPDATE_REQUEST_CONTENT_TYPE]:(e,t)=>{let{payload:{value:n,pathMethod:r}}=t,[o,a]=r;return e.setIn(["requestData",o,a,"requestContentType"],n)},[c.UPDATE_RESPONSE_CONTENT_TYPE]:(e,t)=>{let{payload:{value:n,path:r,method:o}}=t;return e.setIn(["requestData",r,o,"responseContentType"],n)},[c.UPDATE_SERVER_VARIABLE_VALUE]:(e,t)=>{let{payload:{server:n,namespace:r,key:o,val:a}}=t;const i=r?[r,"serverVariableValues",n,o]:["serverVariableValues",n,o];return e.setIn(i,a)},[c.SET_REQUEST_BODY_VALIDATE_ERROR]:(e,t)=>{let{payload:{path:n,method:r,validationErrors:o}}=t,a=[];if(a.push("Required field is not provided"),o.missingBodyValue)return e.setIn(["requestData",n,r,"errors"],(0,u.fromJS)(a));if(o.missingRequiredKeys&&o.missingRequiredKeys.length>0){const{missingRequiredKeys:t}=o;return e.updateIn(["requestData",n,r,"bodyValue"],(0,u.fromJS)({}),(e=>l()(t).call(t,((e,t)=>e.setIn([t,"errors"],(0,u.fromJS)(a))),e)))}return console.warn("unexpected result: SET_REQUEST_BODY_VALIDATE_ERROR"),e},[c.CLEAR_REQUEST_BODY_VALIDATE_ERROR]:(e,t)=>{let{payload:{path:n,method:r}}=t;const a=e.getIn(["requestData",n,r,"bodyValue"]);if(!u.Map.isMap(a))return e.setIn(["requestData",n,r,"errors"],(0,u.fromJS)([]));const[...i]=o()(a).call(a);return i?e.updateIn(["requestData",n,r,"bodyValue"],(0,u.fromJS)({}),(e=>l()(i).call(i,((e,t)=>e.setIn([t,"errors"],(0,u.fromJS)([]))),e))):e},[c.CLEAR_REQUEST_BODY_VALUE]:(e,t)=>{let{payload:{pathMethod:n}}=t,[r,o]=n;const a=e.getIn(["requestData",r,o,"bodyValue"]);return a?u.Map.isMap(a)?e.setIn(["requestData",r,o,"bodyValue"],(0,u.Map)()):e.setIn(["requestData",r,o,"bodyValue"],""):e}}},5065:(e,t,n)=>{"use strict";n.r(t),n.d(t,{activeExamplesMember:()=>_,hasUserEditedBody:()=>w,requestBodyErrors:()=>x,requestBodyInclusionSetting:()=>E,requestBodyValue:()=>y,requestContentType:()=>S,responseContentType:()=>A,selectDefaultRequestBodyValue:()=>b,selectedServer:()=>g,serverEffectiveValue:()=>O,serverVariableValue:()=>C,serverVariables:()=>k,shouldRetainRequestBodyValue:()=>v,validateBeforeExecute:()=>j,validateShallowRequired:()=>T});var r=n(97606),o=n.n(r),a=n(86),i=n.n(a),s=n(28222),l=n.n(s),u=n(11882),c=n.n(u),p=n(43393),f=n(7779),h=n(42458),d=n(90242);const m=e=>function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o{const o=n.getSystem().specSelectors.specJson();if((0,f.isOAS3)(o)){const o=e(t,...r);return"function"==typeof o?o(n):o}return null}};const g=m(((e,t)=>{const n=t?[t,"selectedServer"]:["selectedServer"];return e.getIn(n)||""})),y=m(((e,t,n)=>e.getIn(["requestData",t,n,"bodyValue"])||null)),v=m(((e,t,n)=>e.getIn(["requestData",t,n,"retainBodyValue"])||!1)),b=(e,t,n)=>e=>{const{oas3Selectors:r,specSelectors:o}=e.getSystem(),a=o.specJson();if((0,f.isOAS3)(a)){const e=r.requestContentType(t,n);if(e)return(0,h.getDefaultRequestBodyValue)(o.specResolvedSubtree(["paths",t,n,"requestBody"]),e,r.activeExamplesMember(t,n,"requestBody","requestBody"))}return null},w=m(((e,t,n)=>e=>{const{oas3Selectors:r,specSelectors:o}=e.getSystem();let a=!1;const i=r.requestContentType(t,n);let s=r.requestBodyValue(t,n);const l=o.specResolvedSubtree(["paths",t,n,"requestBody"]);if(!l)return!1;if(p.Map.isMap(s)&&(s=(0,d.Pz)(s.mapEntries((e=>p.Map.isMap(e[1])?[e[0],e[1].get("value")]:e)).toJS())),p.List.isList(s)&&(s=(0,d.Pz)(s)),i){const e=(0,h.getDefaultRequestBodyValue)(l,i,r.activeExamplesMember(t,n,"requestBody","requestBody"));a=!!s&&s!==e}return a})),E=m(((e,t,n)=>e.getIn(["requestData",t,n,"bodyInclusion"])||(0,p.Map)())),x=m(((e,t,n)=>e.getIn(["requestData",t,n,"errors"])||null)),_=m(((e,t,n,r,o)=>e.getIn(["examples",t,n,r,o,"activeExample"])||null)),S=m(((e,t,n)=>e.getIn(["requestData",t,n,"requestContentType"])||null)),A=m(((e,t,n)=>e.getIn(["requestData",t,n,"responseContentType"])||null)),C=m(((e,t,n)=>{let r;if("string"!=typeof t){const{server:e,namespace:o}=t;r=o?[o,"serverVariableValues",e,n]:["serverVariableValues",e,n]}else{r=["serverVariableValues",t,n]}return e.getIn(r)||null})),k=m(((e,t)=>{let n;if("string"!=typeof t){const{server:e,namespace:r}=t;n=r?[r,"serverVariableValues",e]:["serverVariableValues",e]}else{n=["serverVariableValues",t]}return e.getIn(n)||(0,p.OrderedMap)()})),O=m(((e,t)=>{var n,r;if("string"!=typeof t){const{server:o,namespace:a}=t;r=o,n=a?e.getIn([a,"serverVariableValues",r]):e.getIn(["serverVariableValues",r])}else r=t,n=e.getIn(["serverVariableValues",r]);n=n||(0,p.OrderedMap)();let a=r;return o()(n).call(n,((e,t)=>{a=a.replace(new RegExp(`{${t}}`,"g"),e)})),a})),j=(I=(e,t)=>((e,t)=>(t=t||[],!!e.getIn(["requestData",...t,"bodyValue"])))(e,t),function(){for(var e=arguments.length,t=new Array(e),n=0;n{const n=e.getSystem().specSelectors.specJson();let r=[...t][1]||[];return!n.getIn(["paths",...r,"requestBody","required"])||I(...t)}});var I;const T=(e,t)=>{var n;let{oas3RequiredRequestBodyContentType:r,oas3RequestContentType:o,oas3RequestBodyValue:a}=t,s=[];if(!p.Map.isMap(a))return s;let u=[];return i()(n=l()(r.requestContentType)).call(n,(e=>{if(e===o){let t=r.requestContentType[e];i()(t).call(t,(e=>{c()(u).call(u,e)<0&&u.push(e)}))}})),i()(u).call(u,(e=>{a.getIn([e,"value"])||s.push(e)})),s}},91741:(e,t,n)=>{"use strict";n.r(t),n.d(t,{isSwagger2:()=>p,servers:()=>u});var r=n(20573),o=n(43393),a=n(7779);const i=e=>e||(0,o.Map)(),s=(0,r.P1)(i,(e=>e.get("json",(0,o.Map)()))),l=(0,r.P1)(i,(e=>e.get("resolved",(0,o.Map)()))),u=(c=(0,r.P1)((e=>{let t=l(e);return t.count()<1&&(t=s(e)),t}),(e=>e.getIn(["servers"])||(0,o.Map)())),()=>function(e){const t=e.getSystem().specSelectors.specJson();if((0,a.isOAS3)(t)){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o()=>{const e=t.getSystem().specSelectors.specJson();return(0,a.isSwagger2)(e)}},92044:(e,t,n)=>{"use strict";n.r(t),n.d(t,{basePath:()=>y,consumes:()=>v,definitions:()=>h,hasHost:()=>d,host:()=>g,isOAS3:()=>x,isSwagger2:()=>_,produces:()=>b,schemes:()=>w,securityDefinitions:()=>m,servers:()=>E});var r=n(20573),o=n(33881),a=n(43393),i=n(7779);function s(e){return(t,n)=>function(){const r=n.getSystem().specSelectors.specJson();return(0,i.isOAS3)(r)?e(...arguments):t(...arguments)}}const l=e=>e||(0,a.Map)(),u=s((0,r.P1)((()=>null))),c=(0,r.P1)(l,(e=>e.get("json",(0,a.Map)()))),p=(0,r.P1)(l,(e=>e.get("resolved",(0,a.Map)()))),f=e=>{let t=p(e);return t.count()<1&&(t=c(e)),t},h=s((0,r.P1)(f,(e=>{const t=e.getIn(["components","schemas"]);return a.Map.isMap(t)?t:(0,a.Map)()}))),d=s((e=>f(e).hasIn(["servers",0]))),m=s((0,r.P1)(o.specJsonWithResolvedSubtrees,(e=>e.getIn(["components","securitySchemes"])||null))),g=u,y=u,v=u,b=u,w=u,E=s((0,r.P1)(f,(e=>e.getIn(["servers"])||(0,a.Map)()))),x=(e,t)=>()=>{const e=t.getSystem().specSelectors.specJson();return(0,i.isOAS3)(a.Map.isMap(e)?e:(0,a.Map)())},_=(e,t)=>()=>{const e=t.getSystem().specSelectors.specJson();return(0,i.isSwagger2)(a.Map.isMap(e)?e:(0,a.Map)())}},70356:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=(0,n(7779).OAS3ComponentWrapFactory)((e=>{let{Ori:t,...n}=e;const{schema:o,getComponent:a,errSelectors:i,authorized:s,onAuthChange:l,name:u}=n,c=a("HttpAuth");return"http"===o.get("type")?r.createElement(c,{key:u,schema:o,name:u,errSelectors:i,authorized:s,getComponent:a,onChange:l}):r.createElement(t,n)}))},37761:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>u});var r=n(22460),o=n(70356),a=n(69487),i=n(50058),s=n(53499),l=n(90287);const u={Markdown:r.default,AuthItem:o.default,JsonSchema_string:l.default,VersionStamp:a.default,model:s.default,onlineValidatorBadge:i.default}},90287:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=(0,n(7779).OAS3ComponentWrapFactory)((e=>{let{Ori:t,...n}=e;const{schema:o,getComponent:a,errors:i,onChange:s}=n,l=o&&o.get?o.get("format"):null,u=o&&o.get?o.get("type"):null,c=a("Input");return u&&"string"===u&&l&&("binary"===l||"base64"===l)?r.createElement(c,{type:"file",className:i.length?"invalid":"",title:i.length?i:"",onChange:e=>{s(e.target.files[0])},disabled:t.isDisabled}):r.createElement(t,n)}))},22460:(e,t,n)=>{"use strict";n.r(t),n.d(t,{Markdown:()=>f,default:()=>h});var r=n(81607),o=n.n(r),a=n(67294),i=n(94184),s=n.n(i),l=n(89927),u=n(7779),c=n(94994);const p=new l._("commonmark");p.block.ruler.enable(["table"]),p.set({linkTarget:"_blank"});const f=e=>{let{source:t,className:n="",getConfigs:r}=e;if("string"!=typeof t)return null;if(t){const{useUnsafeMarkdown:e}=r(),i=p.render(t),l=(0,c.s)(i,{useUnsafeMarkdown:e});let u;return"string"==typeof l&&(u=o()(l).call(l)),a.createElement("div",{dangerouslySetInnerHTML:{__html:u},className:s()(n,"renderedMarkdown")})}return null};f.defaultProps={getConfigs:()=>({useUnsafeMarkdown:!1})};const h=(0,u.OAS3ComponentWrapFactory)(f)},53499:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>u});var r=n(23101),o=n.n(r),a=n(67294),i=n(7779),s=n(53795);class l extends a.Component{render(){let{getConfigs:e,schema:t}=this.props,n=["model-box"],r=null;return!0===t.get("deprecated")&&(n.push("deprecated"),r=a.createElement("span",{className:"model-deprecated-warning"},"Deprecated:")),a.createElement("div",{className:n.join(" ")},r,a.createElement(s.Z,o()({},this.props,{getConfigs:e,depth:1,expandDepth:this.props.expandDepth||0})))}}const u=(0,i.OAS3ComponentWrapFactory)(l)},50058:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>a});var r=n(7779),o=n(5623);const a=(0,r.OAS3ComponentWrapFactory)(o.Z)},69487:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=(0,n(7779).OAS3ComponentWrapFactory)((e=>{const{Ori:t}=e;return r.createElement("span",null,r.createElement(t,e),r.createElement("small",{className:"version-stamp"},r.createElement("pre",{className:"version"},"OAS3")))}))},28560:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(87198),o=n.n(r);let a=!1;function i(){return{statePlugins:{spec:{wrapActions:{updateSpec:e=>function(){return a=!0,e(...arguments)},updateJsonSpec:(e,t)=>function(){const n=t.getConfigs().onComplete;return a&&"function"==typeof n&&(o()(n,0),a=!1),e(...arguments)}}}}}}},92135:(e,t,n)=>{"use strict";n.r(t),n.d(t,{requestSnippetGenerator_curl_bash:()=>A,requestSnippetGenerator_curl_cmd:()=>C,requestSnippetGenerator_curl_powershell:()=>S});var r=n(11882),o=n.n(r),a=n(81607),i=n.n(a),s=n(35627),l=n.n(s),u=n(97606),c=n.n(u),p=n(12196),f=n.n(p),h=n(74386),d=n.n(h),m=n(58118),g=n.n(m),y=n(27504),v=n(43393);const b=e=>{var t;const n="_**[]";return o()(e).call(e,n)<0?e:i()(t=e.split(n)[0]).call(t)},w=e=>"-d "===e||/^[_\/-]/g.test(e)?e:"'"+e.replace(/'/g,"'\\''")+"'",E=e=>"-d "===(e=e.replace(/\^/g,"^^").replace(/\\"/g,'\\\\"').replace(/"/g,'""').replace(/\n/g,"^\n"))?e.replace(/-d /g,"-d ^\n"):/^[_\/-]/g.test(e)?e:'"'+e+'"',x=e=>"-d "===e?e:/\n/.test(e)?'@"\n'+e.replace(/"/g,'\\"').replace(/`/g,"``").replace(/\$/,"`$")+'\n"@':/^[_\/-]/g.test(e)?e:"'"+e.replace(/"/g,'""').replace(/'/g,"''")+"'";const _=function(e,t,n){let r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"",o=!1,a="";const i=function(){for(var e=arguments.length,n=new Array(e),r=0;ra+=` ${n}`,p=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1;return a+=f()(" ").call(" ",e)};let h=e.get("headers");if(a+="curl"+r,e.has("curlOptions")&&i(...e.get("curlOptions")),i("-X",e.get("method")),u(),p(),s(`${e.get("url")}`),h&&h.size)for(let t of d()(m=e.get("headers")).call(m)){var m;u(),p();let[e,n]=t;s("-H",`${e}: ${n}`),o=o||/^content-type$/i.test(e)&&/^multipart\/form-data$/i.test(n)}const w=e.get("body");var E;if(w)if(o&&g()(E=["POST","PUT","PATCH"]).call(E,e.get("method")))for(let[e,t]of w.entrySeq()){let n=b(e);u(),p(),s("-F"),t instanceof y.Z.File?i(`${n}=@${t.name}${t.type?`;type=${t.type}`:""}`):i(`${n}=${t}`)}else if(w instanceof y.Z.File)u(),p(),s(`--data-binary '@${w.name}'`);else{u(),p(),s("-d ");let t=w;v.Map.isMap(t)?s(function(e){let t=[];for(let[n,r]of e.get("body").entrySeq()){let e=b(n);r instanceof y.Z.File?t.push(` "${e}": {\n "name": "${r.name}"${r.type?`,\n "type": "${r.type}"`:""}\n }`):t.push(` "${e}": ${l()(r,null,2).replace(/(\r\n|\r|\n)/g,"\n ")}`)}return`{\n${t.join(",\n")}\n}`}(e)):("string"!=typeof t&&(t=l()(t)),s(t))}else w||"POST"!==e.get("method")||(u(),p(),s("-d ''"));return a},S=e=>_(e,x,"`\n",".exe"),A=e=>_(e,w,"\\\n"),C=e=>_(e,E,"^\n")},86575:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(92135),o=n(4669),a=n(84206);const i=()=>({components:{RequestSnippets:a.default},fn:r,statePlugins:{requestSnippets:{selectors:o}}})},84206:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>w});var r=n(14418),o=n.n(r),a=n(25110),i=n.n(a),s=n(86),l=n.n(s),u=n(97606),c=n.n(u),p=n(67294),f=n(27361),h=n.n(f),d=n(23560),m=n.n(d),g=n(74855),y=n(33424);const v={cursor:"pointer",lineHeight:1,display:"inline-flex",backgroundColor:"rgb(250, 250, 250)",paddingBottom:"0",paddingTop:"0",border:"1px solid rgb(51, 51, 51)",borderRadius:"4px 4px 0 0",boxShadow:"none",borderBottom:"none"},b={cursor:"pointer",lineHeight:1,display:"inline-flex",backgroundColor:"rgb(51, 51, 51)",boxShadow:"none",border:"1px solid rgb(51, 51, 51)",paddingBottom:"0",paddingTop:"0",borderRadius:"4px 4px 0 0",marginTop:"-5px",marginRight:"-5px",marginLeft:"-5px",zIndex:"9999",borderBottom:"none"},w=e=>{var t,n;let{request:r,requestSnippetsSelectors:a,getConfigs:s}=e;const u=m()(s)?s():null,f=!1!==h()(u,"syntaxHighlight")&&h()(u,"syntaxHighlight.activated",!0),d=(0,p.useRef)(null),[w,E]=(0,p.useState)(null===(t=a.getSnippetGenerators())||void 0===t?void 0:t.keySeq().first()),[x,_]=(0,p.useState)(null==a?void 0:a.getDefaultExpanded());(0,p.useEffect)((()=>{}),[]),(0,p.useEffect)((()=>{var e;const t=o()(e=i()(d.current.childNodes)).call(e,(e=>{var t;return!!e.nodeType&&(null===(t=e.classList)||void 0===t?void 0:t.contains("curl-command"))}));return l()(t).call(t,(e=>e.addEventListener("mousewheel",j,{passive:!1}))),()=>{l()(t).call(t,(e=>e.removeEventListener("mousewheel",j)))}}),[r]);const S=a.getSnippetGenerators(),A=S.get(w),C=A.get("fn")(r),k=()=>{_(!x)},O=e=>e===w?b:v,j=e=>{const{target:t,deltaY:n}=e,{scrollHeight:r,offsetHeight:o,scrollTop:a}=t;r>o&&(0===a&&n<0||o+a>=r&&n>0)&&e.preventDefault()},I=f?p.createElement(y.d3,{language:A.get("syntax"),className:"curl microlight",style:(0,y.C2)(h()(u,"syntaxHighlight.theme"))},C):p.createElement("textarea",{readOnly:!0,className:"curl",value:C});return p.createElement("div",{className:"request-snippets",ref:d},p.createElement("div",{style:{width:"100%",display:"flex",justifyContent:"flex-start",alignItems:"center",marginBottom:"15px"}},p.createElement("h4",{onClick:()=>k(),style:{cursor:"pointer"}},"Snippets"),p.createElement("button",{onClick:()=>k(),style:{border:"none",background:"none"},title:x?"Collapse operation":"Expand operation"},p.createElement("svg",{className:"arrow",width:"10",height:"10"},p.createElement("use",{href:x?"#large-arrow-down":"#large-arrow",xlinkHref:x?"#large-arrow-down":"#large-arrow"})))),x&&p.createElement("div",{className:"curl-command"},p.createElement("div",{style:{paddingLeft:"15px",paddingRight:"10px",width:"100%",display:"flex"}},c()(n=S.entrySeq()).call(n,(e=>{let[t,n]=e;return p.createElement("div",{style:O(t),className:"btn",key:t,onClick:()=>(e=>{w!==e&&E(e)})(t)},p.createElement("h4",{style:t===w?{color:"white"}:{}},n.get("title")))}))),p.createElement("div",{className:"copy-to-clipboard"},p.createElement(g.CopyToClipboard,{text:C},p.createElement("button",null))),p.createElement("div",null,I)))}},4669:(e,t,n)=>{"use strict";n.r(t),n.d(t,{getActiveLanguage:()=>d,getDefaultExpanded:()=>m,getGenerators:()=>f,getSnippetGenerators:()=>h});var r=n(14418),o=n.n(r),a=n(58118),i=n.n(a),s=n(97606),l=n.n(s),u=n(20573),c=n(43393);const p=e=>e||(0,c.Map)(),f=(0,u.P1)(p,(e=>{const t=e.get("languages"),n=e.get("generators",(0,c.Map)());return!t||t.isEmpty()?n:o()(n).call(n,((e,n)=>i()(t).call(t,n)))})),h=e=>t=>{var n,r;let{fn:a}=t;return o()(n=l()(r=f(e)).call(r,((e,t)=>{const n=(e=>a[`requestSnippetGenerator_${e}`])(t);return"function"!=typeof n?null:e.set("fn",n)}))).call(n,(e=>e))},d=(0,u.P1)(p,(e=>e.get("activeLanguage"))),m=(0,u.P1)(p,(e=>e.get("defaultExpanded")))},36195:(e,t,n)=>{"use strict";n.r(t),n.d(t,{ErrorBoundary:()=>i,default:()=>s});var r=n(67294),o=n(56189),a=n(29403);class i extends r.Component{static getDerivedStateFromError(e){return{hasError:!0,error:e}}constructor(){super(...arguments),this.state={hasError:!1,error:null}}componentDidCatch(e,t){this.props.fn.componentDidCatch(e,t)}render(){const{getComponent:e,targetName:t,children:n}=this.props;if(this.state.hasError){const n=e("Fallback");return r.createElement(n,{name:t})}return n}}i.defaultProps={targetName:"this component",getComponent:()=>a.default,fn:{componentDidCatch:o.componentDidCatch},children:null};const s=i},29403:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=e=>{let{name:t}=e;return r.createElement("div",{className:"fallback"},"😱 ",r.createElement("i",null,"Could not render ","t"===t?"this component":t,", see the console."))}},56189:(e,t,n)=>{"use strict";n.r(t),n.d(t,{componentDidCatch:()=>i,withErrorBoundary:()=>s});var r=n(23101),o=n.n(r),a=n(67294);const i=console.error,s=e=>t=>{const{getComponent:n,fn:r}=e(),i=n("ErrorBoundary"),s=r.getDisplayName(t);class l extends a.Component{render(){return a.createElement(i,{targetName:s,getComponent:n,fn:r},a.createElement(t,o()({},this.props,this.context)))}}var u;return l.displayName=`WithErrorBoundary(${s})`,(u=t).prototype&&u.prototype.isReactComponent&&(l.prototype.mapStateToProps=t.prototype.mapStateToProps),l}},27621:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(47475),o=n.n(r),a=n(7287),i=n.n(a),s=n(36195),l=n(29403),u=n(56189);const c=function(){let{componentList:e=[],fullOverride:t=!1}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return n=>{var r;let{getSystem:a}=n;const c=t?e:["App","BaseLayout","VersionPragmaFilter","InfoContainer","ServersContainer","SchemesContainer","AuthorizeBtnContainer","FilterContainer","Operations","OperationContainer","parameters","responses","OperationServers","Models","ModelWrapper",...e],p=i()(c,o()(r=Array(c.length)).call(r,((e,t)=>{let{fn:n}=t;return n.withErrorBoundary(e)})));return{fn:{componentDidCatch:u.componentDidCatch,withErrorBoundary:(0,u.withErrorBoundary)(a)},components:{ErrorBoundary:s.default,Fallback:l.default},wrapComponents:p}}}},57050:(e,t,n)=>{"use strict";n.r(t),n.d(t,{createXMLExample:()=>z,inferSchema:()=>U,memoizedCreateXMLExample:()=>V,memoizedSampleFromSchema:()=>W,sampleFromSchema:()=>q,sampleFromSchemaGeneric:()=>F});var r=n(11882),o=n.n(r),a=n(86),i=n.n(a),s=n(58309),l=n.n(s),u=n(58118),c=n.n(u),p=n(92039),f=n.n(p),h=n(24278),d=n.n(h),m=n(51679),g=n.n(m),y=n(39022),v=n.n(y),b=n(97606),w=n.n(b),E=n(35627),x=n.n(E),_=n(53479),S=n.n(_),A=n(14419),C=n.n(A),k=n(41609),O=n.n(k),j=n(90242),I=n(60314);const T={string:e=>e.pattern?(e=>{try{return new(C())(e).gen()}catch(e){return"string"}})(e.pattern):"string",string_email:()=>"user@example.com","string_date-time":()=>(new Date).toISOString(),string_date:()=>(new Date).toISOString().substring(0,10),string_uuid:()=>"3fa85f64-5717-4562-b3fc-2c963f66afa6",string_hostname:()=>"example.com",string_ipv4:()=>"198.51.100.42",string_ipv6:()=>"2001:0db8:5b96:0000:0000:426f:8e17:642a",number:()=>0,number_float:()=>0,integer:()=>0,boolean:e=>"boolean"!=typeof e.default||e.default},N=e=>{e=(0,j.mz)(e);let{type:t,format:n}=e,r=T[`${t}_${n}`]||T[t];return(0,j.Wl)(r)?r(e):"Unknown Type: "+e.type},P=e=>(0,j.XV)(e,"$$ref",(e=>"string"==typeof e&&o()(e).call(e,"#")>-1)),R=["maxProperties","minProperties"],M=["minItems","maxItems"],D=["minimum","maximum","exclusiveMinimum","exclusiveMaximum"],L=["minLength","maxLength"],B=function(e,t){var n;let r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};var a;(i()(n=["example","default","enum","xml","type",...R,...M,...D,...L]).call(n,(n=>(n=>{void 0===t[n]&&void 0!==e[n]&&(t[n]=e[n])})(n))),void 0!==e.required&&l()(e.required))&&(void 0!==t.required&&t.required.length||(t.required=[]),i()(a=e.required).call(a,(e=>{var n;c()(n=t.required).call(n,e)||t.required.push(e)})));if(e.properties){t.properties||(t.properties={});let n=(0,j.mz)(e.properties);for(let a in n){var s;if(Object.prototype.hasOwnProperty.call(n,a))if(!n[a]||!n[a].deprecated)if(!n[a]||!n[a].readOnly||r.includeReadOnly)if(!n[a]||!n[a].writeOnly||r.includeWriteOnly)if(!t.properties[a])t.properties[a]=n[a],!e.required&&l()(e.required)&&-1!==o()(s=e.required).call(s,a)&&(t.required?t.required.push(a):t.required=[a])}}return e.items&&(t.items||(t.items={}),t.items=B(e.items,t.items,r)),t},F=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0,r=arguments.length>3&&void 0!==arguments[3]&&arguments[3];e&&(0,j.Wl)(e.toJS)&&(e=e.toJS());let a=void 0!==n||e&&void 0!==e.example||e&&void 0!==e.default;const s=!a&&e&&e.oneOf&&e.oneOf.length>0,u=!a&&e&&e.anyOf&&e.anyOf.length>0;if(!a&&(s||u)){const n=(0,j.mz)(s?e.oneOf[0]:e.anyOf[0]);if(B(n,e,t),!e.xml&&n.xml&&(e.xml=n.xml),void 0!==e.example&&void 0!==n.example)a=!0;else if(n.properties){e.properties||(e.properties={});let r=(0,j.mz)(n.properties);for(let a in r){var p;if(Object.prototype.hasOwnProperty.call(r,a))if(!r[a]||!r[a].deprecated)if(!r[a]||!r[a].readOnly||t.includeReadOnly)if(!r[a]||!r[a].writeOnly||t.includeWriteOnly)if(!e.properties[a])e.properties[a]=r[a],!n.required&&l()(n.required)&&-1!==o()(p=n.required).call(p,a)&&(e.required?e.required.push(a):e.required=[a])}}}const h={};let{xml:m,type:y,example:b,properties:E,additionalProperties:x,items:_}=e||{},{includeReadOnly:S,includeWriteOnly:A}=t;m=m||{};let C,{name:k,prefix:I,namespace:T}=m,L={};if(r&&(k=k||"notagname",C=(I?I+":":"")+k,T)){h[I?"xmlns:"+I:"xmlns"]=T}r&&(L[C]=[]);const U=t=>f()(t).call(t,(t=>Object.prototype.hasOwnProperty.call(e,t)));e&&!y&&(E||x||U(R)?y="object":_||U(M)?y="array":U(D)?(y="number",e.type="number"):a||e.enum||(y="string",e.type="string"));const z=t=>{var n,r,o,a,i;null!==(null===(n=e)||void 0===n?void 0:n.maxItems)&&void 0!==(null===(r=e)||void 0===r?void 0:r.maxItems)&&(t=d()(t).call(t,0,null===(i=e)||void 0===i?void 0:i.maxItems));if(null!==(null===(o=e)||void 0===o?void 0:o.minItems)&&void 0!==(null===(a=e)||void 0===a?void 0:a.minItems)){let n=0;for(;t.length<(null===(s=e)||void 0===s?void 0:s.minItems);){var s;t.push(t[n++%t.length])}}return t},q=(0,j.mz)(E);let $,V=0;const W=()=>e&&null!==e.maxProperties&&void 0!==e.maxProperties&&V>=e.maxProperties,H=t=>!e||null===e.maxProperties||void 0===e.maxProperties||!W()&&(!(t=>{var n;return!(e&&e.required&&e.required.length&&c()(n=e.required).call(n,t))})(t)||e.maxProperties-V-(()=>{if(!e||!e.required)return 0;let t=0;var n,o;return r?i()(n=e.required).call(n,(e=>t+=void 0===L[e]?0:1)):i()(o=e.required).call(o,(e=>{var n;return t+=void 0===(null===(n=L[C])||void 0===n?void 0:g()(n).call(n,(t=>void 0!==t[e])))?0:1})),e.required.length-t})()>0);if($=r?function(n){let o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:void 0;if(e&&q[n]){if(q[n].xml=q[n].xml||{},q[n].xml.attribute){const e=l()(q[n].enum)?q[n].enum[0]:void 0,t=q[n].example,r=q[n].default;return void(h[q[n].xml.name||n]=void 0!==t?t:void 0!==r?r:void 0!==e?e:N(q[n]))}q[n].xml.name=q[n].xml.name||n}else q[n]||!1===x||(q[n]={xml:{name:n}});let a=F(e&&q[n]||void 0,t,o,r);var i;H(n)&&(V++,l()(a)?L[C]=v()(i=L[C]).call(i,a):L[C].push(a))}:(n,o)=>{if(H(n)){if(Object.prototype.hasOwnProperty.call(e,"discriminator")&&e.discriminator&&Object.prototype.hasOwnProperty.call(e.discriminator,"mapping")&&e.discriminator.mapping&&Object.prototype.hasOwnProperty.call(e,"$$ref")&&e.$$ref&&e.discriminator.propertyName===n){for(let t in e.discriminator.mapping)if(-1!==e.$$ref.search(e.discriminator.mapping[t])){L[n]=t;break}}else L[n]=F(q[n],t,o,r);V++}},a){let o;if(o=P(void 0!==n?n:void 0!==b?b:e.default),!r){if("number"==typeof o&&"string"===y)return`${o}`;if("string"!=typeof o||"string"===y)return o;try{return JSON.parse(o)}catch(e){return o}}if(e||(y=l()(o)?"array":typeof o),"array"===y){if(!l()(o)){if("string"==typeof o)return o;o=[o]}const n=e?e.items:void 0;n&&(n.xml=n.xml||m||{},n.xml.name=n.xml.name||m.name);let a=w()(o).call(o,(e=>F(n,t,e,r)));return a=z(a),m.wrapped?(L[C]=a,O()(h)||L[C].push({_attr:h})):L=a,L}if("object"===y){if("string"==typeof o)return o;for(let t in o)Object.prototype.hasOwnProperty.call(o,t)&&(e&&q[t]&&q[t].readOnly&&!S||e&&q[t]&&q[t].writeOnly&&!A||(e&&q[t]&&q[t].xml&&q[t].xml.attribute?h[q[t].xml.name||t]=o[t]:$(t,o[t])));return O()(h)||L[C].push({_attr:h}),L}return L[C]=O()(h)?o:[{_attr:h},o],L}if("object"===y){for(let e in q)Object.prototype.hasOwnProperty.call(q,e)&&(q[e]&&q[e].deprecated||q[e]&&q[e].readOnly&&!S||q[e]&&q[e].writeOnly&&!A||$(e));if(r&&h&&L[C].push({_attr:h}),W())return L;if(!0===x)r?L[C].push({additionalProp:"Anything can be here"}):L.additionalProp1={},V++;else if(x){const n=(0,j.mz)(x),o=F(n,t,void 0,r);if(r&&n.xml&&n.xml.name&&"notagname"!==n.xml.name)L[C].push(o);else{const t=null!==e.minProperties&&void 0!==e.minProperties&&VF(B(_,e,t),t,void 0,r)));else if(l()(_.oneOf)){var G;n=w()(G=_.oneOf).call(G,(e=>F(B(_,e,t),t,void 0,r)))}else{if(!(!r||r&&m.wrapped))return F(_,t,void 0,r);n=[F(_,t,void 0,r)]}return n=z(n),r&&m.wrapped?(L[C]=n,O()(h)||L[C].push({_attr:h}),L):n}let Z;if(e&&l()(e.enum))Z=(0,j.AF)(e.enum)[0];else{if(!e)return;if(Z=N(e),"number"==typeof Z){let t=e.minimum;null!=t&&(e.exclusiveMinimum&&t++,Z=t);let n=e.maximum;null!=n&&(e.exclusiveMaximum&&n--,Z=n)}if("string"==typeof Z&&(null!==e.maxLength&&void 0!==e.maxLength&&(Z=d()(Z).call(Z,0,e.maxLength)),null!==e.minLength&&void 0!==e.minLength)){let t=0;for(;Z.length(e.schema&&(e=e.schema),e.properties&&(e.type="object"),e),z=(e,t,n)=>{const r=F(e,t,n,!0);if(r)return"string"==typeof r?r:S()(r,{declaration:!0,indent:"\t"})},q=(e,t,n)=>F(e,t,n,!1),$=(e,t,n)=>[e,x()(t),x()(n)],V=(0,I.Z)(z,$),W=(0,I.Z)(q,$)},8883:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(57050);function o(){return{fn:r}}},51228:(e,t,n)=>{"use strict";n.r(t),n.d(t,{CLEAR_REQUEST:()=>Q,CLEAR_RESPONSE:()=>Y,CLEAR_VALIDATE_PARAMS:()=>X,LOG_REQUEST:()=>Z,SET_MUTATED_REQUEST:()=>G,SET_REQUEST:()=>K,SET_RESPONSE:()=>J,SET_SCHEME:()=>re,UPDATE_EMPTY_PARAM_INCLUSION:()=>W,UPDATE_JSON:()=>$,UPDATE_OPERATION_META_VALUE:()=>ee,UPDATE_PARAM:()=>V,UPDATE_RESOLVED:()=>te,UPDATE_RESOLVED_SUBTREE:()=>ne,UPDATE_SPEC:()=>z,UPDATE_URL:()=>q,VALIDATE_PARAMS:()=>H,changeConsumesValue:()=>xe,changeParam:()=>me,changeParamByIdentity:()=>ge,changeProducesValue:()=>_e,clearRequest:()=>Te,clearResponse:()=>Ie,clearValidateParams:()=>Ee,execute:()=>je,executeRequest:()=>Oe,invalidateResolvedSubtreeCache:()=>ve,logRequest:()=>ke,parseToJson:()=>ue,requestResolvedSubtree:()=>de,resolveSpec:()=>pe,setMutatedRequest:()=>Ce,setRequest:()=>Ae,setResponse:()=>Se,setScheme:()=>Ne,updateEmptyParamInclusion:()=>we,updateJsonSpec:()=>le,updateResolved:()=>ie,updateResolvedSubtree:()=>ye,updateSpec:()=>ae,updateUrl:()=>se,validateParams:()=>be});var r=n(58309),o=n.n(r),a=n(97606),i=n.n(a),s=n(96718),l=n.n(s),u=n(24282),c=n.n(u),p=n(2250),f=n.n(p),h=n(6226),d=n.n(h),m=n(14418),g=n.n(m),y=n(3665),v=n.n(y),b=n(11882),w=n.n(b),E=n(86),x=n.n(E),_=n(28222),S=n.n(_),A=n(76986),C=n.n(A),k=n(70586),O=n.n(k),j=n(1272),I=n(43393),T=n(84564),N=n.n(T),P=n(7710),R=n(47037),M=n.n(R),D=n(23279),L=n.n(D),B=n(36968),F=n.n(B),U=n(90242);const z="spec_update_spec",q="spec_update_url",$="spec_update_json",V="spec_update_param",W="spec_update_empty_param_inclusion",H="spec_validate_param",J="spec_set_response",K="spec_set_request",G="spec_set_mutated_request",Z="spec_log_request",Y="spec_clear_response",Q="spec_clear_request",X="spec_clear_validate_param",ee="spec_update_operation_meta_value",te="spec_update_resolved",ne="spec_update_resolved_subtree",re="set_scheme",oe=e=>M()(e)?e:"";function ae(e){const t=oe(e).replace(/\t/g," ");if("string"==typeof e)return{type:z,payload:t}}function ie(e){return{type:te,payload:e}}function se(e){return{type:q,payload:e}}function le(e){return{type:$,payload:e}}const ue=e=>t=>{let{specActions:n,specSelectors:r,errActions:o}=t,{specStr:a}=r,i=null;try{e=e||a(),o.clear({source:"parser"}),i=j.ZP.load(e,{schema:j.A8})}catch(e){return console.error(e),o.newSpecErr({source:"parser",level:"error",message:e.reason,line:e.mark&&e.mark.line?e.mark.line+1:void 0})}return i&&"object"==typeof i?n.updateJsonSpec(i):{}};let ce=!1;const pe=(e,t)=>n=>{let{specActions:r,specSelectors:a,errActions:s,fn:{fetch:u,resolve:c,AST:p={}},getConfigs:f}=n;ce||(console.warn("specActions.resolveSpec is deprecated since v3.10.0 and will be removed in v4.0.0; use requestResolvedSubtree instead!"),ce=!0);const{modelPropertyMacro:h,parameterMacro:d,requestInterceptor:m,responseInterceptor:g}=f();void 0===e&&(e=a.specJson()),void 0===t&&(t=a.url());let y=p.getLineNumberForPath?p.getLineNumberForPath:()=>{},v=a.specStr();return c({fetch:u,spec:e,baseDoc:t,modelPropertyMacro:h,parameterMacro:d,requestInterceptor:m,responseInterceptor:g}).then((e=>{let{spec:t,errors:n}=e;if(s.clear({type:"thrown"}),o()(n)&&n.length>0){let e=i()(n).call(n,(e=>(console.error(e),e.line=e.fullPath?y(v,e.fullPath):null,e.path=e.fullPath?e.fullPath.join("."):null,e.level="error",e.type="thrown",e.source="resolver",l()(e,"message",{enumerable:!0,value:e.message}),e)));s.newThrownErrBatch(e)}return r.updateResolved(t)}))};let fe=[];const he=L()((async()=>{const e=fe.system;if(!e)return void console.error("debResolveSubtrees: don't have a system to operate on, aborting.");const{errActions:t,errSelectors:n,fn:{resolveSubtree:r,fetch:a,AST:s={}},specSelectors:u,specActions:p}=e;if(!r)return void console.error("Error: Swagger-Client did not provide a `resolveSubtree` method, doing nothing.");let h=s.getLineNumberForPath?s.getLineNumberForPath:()=>{};const m=u.specStr(),{modelPropertyMacro:y,parameterMacro:b,requestInterceptor:w,responseInterceptor:E}=e.getConfigs();try{var x=await c()(fe).call(fe,(async(e,s)=>{const{resultMap:c,specWithCurrentSubtrees:p}=await e,{errors:x,spec:_}=await r(p,s,{baseDoc:u.url(),modelPropertyMacro:y,parameterMacro:b,requestInterceptor:w,responseInterceptor:E});if(n.allErrors().size&&t.clearBy((e=>{var t;return"thrown"!==e.get("type")||"resolver"!==e.get("source")||!f()(t=e.get("fullPath")).call(t,((e,t)=>e===s[t]||void 0===s[t]))})),o()(x)&&x.length>0){let e=i()(x).call(x,(e=>(e.line=e.fullPath?h(m,e.fullPath):null,e.path=e.fullPath?e.fullPath.join("."):null,e.level="error",e.type="thrown",e.source="resolver",l()(e,"message",{enumerable:!0,value:e.message}),e)));t.newThrownErrBatch(e)}var S,A;_&&u.isOAS3()&&"components"===s[0]&&"securitySchemes"===s[1]&&await d().all(i()(S=g()(A=v()(_)).call(A,(e=>"openIdConnect"===e.type))).call(S,(async e=>{const t={url:e.openIdConnectUrl,requestInterceptor:w,responseInterceptor:E};try{const n=await a(t);n instanceof Error||n.status>=400?console.error(n.statusText+" "+t.url):e.openIdConnectData=JSON.parse(n.text)}catch(e){console.error(e)}})));return F()(c,s,_),F()(p,s,_),{resultMap:c,specWithCurrentSubtrees:p}}),d().resolve({resultMap:(u.specResolvedSubtree([])||(0,I.Map)()).toJS(),specWithCurrentSubtrees:u.specJson().toJS()}));delete fe.system,fe=[]}catch(e){console.error(e)}p.updateResolvedSubtree([],x.resultMap)}),35),de=e=>t=>{var n;w()(n=i()(fe).call(fe,(e=>e.join("@@")))).call(n,e.join("@@"))>-1||(fe.push(e),fe.system=t,he())};function me(e,t,n,r,o){return{type:V,payload:{path:e,value:r,paramName:t,paramIn:n,isXml:o}}}function ge(e,t,n,r){return{type:V,payload:{path:e,param:t,value:n,isXml:r}}}const ye=(e,t)=>({type:ne,payload:{path:e,value:t}}),ve=()=>({type:ne,payload:{path:[],value:(0,I.Map)()}}),be=(e,t)=>({type:H,payload:{pathMethod:e,isOAS3:t}}),we=(e,t,n,r)=>({type:W,payload:{pathMethod:e,paramName:t,paramIn:n,includeEmptyValue:r}});function Ee(e){return{type:X,payload:{pathMethod:e}}}function xe(e,t){return{type:ee,payload:{path:e,value:t,key:"consumes_value"}}}function _e(e,t){return{type:ee,payload:{path:e,value:t,key:"produces_value"}}}const Se=(e,t,n)=>({payload:{path:e,method:t,res:n},type:J}),Ae=(e,t,n)=>({payload:{path:e,method:t,req:n},type:K}),Ce=(e,t,n)=>({payload:{path:e,method:t,req:n},type:G}),ke=e=>({payload:e,type:Z}),Oe=e=>t=>{let{fn:n,specActions:r,specSelectors:a,getConfigs:s,oas3Selectors:l}=t,{pathName:u,method:c,operation:p}=e,{requestInterceptor:f,responseInterceptor:h}=s(),d=p.toJS();var m,y;p&&p.get("parameters")&&x()(m=g()(y=p.get("parameters")).call(y,(e=>e&&!0===e.get("allowEmptyValue")))).call(m,(t=>{if(a.parameterInclusionSettingFor([u,c],t.get("name"),t.get("in"))){e.parameters=e.parameters||{};const n=(0,U.cz)(t,e.parameters);(!n||n&&0===n.size)&&(e.parameters[t.get("name")]="")}}));if(e.contextUrl=N()(a.url()).toString(),d&&d.operationId?e.operationId=d.operationId:d&&u&&c&&(e.operationId=n.opId(d,u,c)),a.isOAS3()){const t=`${u}:${c}`;e.server=l.selectedServer(t)||l.selectedServer();const n=l.serverVariables({server:e.server,namespace:t}).toJS(),r=l.serverVariables({server:e.server}).toJS();e.serverVariables=S()(n).length?n:r,e.requestContentType=l.requestContentType(u,c),e.responseContentType=l.responseContentType(u,c)||"*/*";const a=l.requestBodyValue(u,c),s=l.requestBodyInclusionSetting(u,c);var v;if(a&&a.toJS)e.requestBody=g()(v=i()(a).call(a,(e=>I.Map.isMap(e)?e.get("value"):e))).call(v,((e,t)=>(o()(e)?0!==e.length:!(0,U.O2)(e))||s.get(t))).toJS();else e.requestBody=a}let b=C()({},e);b=n.buildRequest(b),r.setRequest(e.pathName,e.method,b);e.requestInterceptor=async t=>{let n=await f.apply(void 0,[t]),o=C()({},n);return r.setMutatedRequest(e.pathName,e.method,o),n},e.responseInterceptor=h;const w=O()();return n.execute(e).then((t=>{t.duration=O()()-w,r.setResponse(e.pathName,e.method,t)})).catch((t=>{"Failed to fetch"===t.message&&(t.name="",t.message='**Failed to fetch.** \n**Possible Reasons:** \n - CORS \n - Network Failure \n - URL scheme must be "http" or "https" for CORS request.'),r.setResponse(e.pathName,e.method,{error:!0,err:(0,P.serializeError)(t)})}))},je=function(){let{path:e,method:t,...n}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return r=>{let{fn:{fetch:o},specSelectors:a,specActions:i}=r,s=a.specJsonWithResolvedSubtrees().toJS(),l=a.operationScheme(e,t),{requestContentType:u,responseContentType:c}=a.contentTypeValues([e,t]).toJS(),p=/xml/i.test(u),f=a.parameterValues([e,t],p).toJS();return i.executeRequest({...n,fetch:o,spec:s,pathName:e,method:t,parameters:f,requestContentType:u,scheme:l,responseContentType:c})}};function Ie(e,t){return{type:Y,payload:{path:e,method:t}}}function Te(e,t){return{type:Q,payload:{path:e,method:t}}}function Ne(e,t,n){return{type:re,payload:{scheme:e,path:t,method:n}}}},37038:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(20032),o=n(51228),a=n(33881),i=n(77508);function s(){return{statePlugins:{spec:{wrapActions:i,reducers:r.default,actions:o,selectors:a}}}}},20032:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>d});var r=n(24282),o=n.n(r),a=n(97606),i=n.n(a),s=n(76986),l=n.n(s),u=n(43393),c=n(90242),p=n(27504),f=n(33881),h=n(51228);const d={[h.UPDATE_SPEC]:(e,t)=>"string"==typeof t.payload?e.set("spec",t.payload):e,[h.UPDATE_URL]:(e,t)=>e.set("url",t.payload+""),[h.UPDATE_JSON]:(e,t)=>e.set("json",(0,c.oG)(t.payload)),[h.UPDATE_RESOLVED]:(e,t)=>e.setIn(["resolved"],(0,c.oG)(t.payload)),[h.UPDATE_RESOLVED_SUBTREE]:(e,t)=>{const{value:n,path:r}=t.payload;return e.setIn(["resolvedSubtrees",...r],(0,c.oG)(n))},[h.UPDATE_PARAM]:(e,t)=>{let{payload:n}=t,{path:r,paramName:o,paramIn:a,param:i,value:s,isXml:l}=n,u=i?(0,c.V9)(i):`${a}.${o}`;const p=l?"value_xml":"value";return e.setIn(["meta","paths",...r,"parameters",u,p],s)},[h.UPDATE_EMPTY_PARAM_INCLUSION]:(e,t)=>{let{payload:n}=t,{pathMethod:r,paramName:o,paramIn:a,includeEmptyValue:i}=n;if(!o||!a)return console.warn("Warning: UPDATE_EMPTY_PARAM_INCLUSION could not generate a paramKey."),e;const s=`${a}.${o}`;return e.setIn(["meta","paths",...r,"parameter_inclusions",s],i)},[h.VALIDATE_PARAMS]:(e,t)=>{let{payload:{pathMethod:n,isOAS3:r}}=t;const a=(0,f.specJsonWithResolvedSubtrees)(e).getIn(["paths",...n]),i=(0,f.parameterValues)(e,n).toJS();return e.updateIn(["meta","paths",...n,"parameters"],(0,u.fromJS)({}),(t=>{var s;return o()(s=a.get("parameters",(0,u.List)())).call(s,((t,o)=>{const a=(0,c.cz)(o,i),s=(0,f.parameterInclusionSettingFor)(e,n,o.get("name"),o.get("in")),l=(0,c.Ik)(o,a,{bypassRequiredCheck:s,isOAS3:r});return t.setIn([(0,c.V9)(o),"errors"],(0,u.fromJS)(l))}),t)}))},[h.CLEAR_VALIDATE_PARAMS]:(e,t)=>{let{payload:{pathMethod:n}}=t;return e.updateIn(["meta","paths",...n,"parameters"],(0,u.fromJS)([]),(e=>i()(e).call(e,(e=>e.set("errors",(0,u.fromJS)([]))))))},[h.SET_RESPONSE]:(e,t)=>{let n,{payload:{res:r,path:o,method:a}}=t;n=r.error?l()({error:!0,name:r.err.name,message:r.err.message,statusCode:r.err.statusCode},r.err.response):r,n.headers=n.headers||{};let i=e.setIn(["responses",o,a],(0,c.oG)(n));return p.Z.Blob&&r.data instanceof p.Z.Blob&&(i=i.setIn(["responses",o,a,"text"],r.data)),i},[h.SET_REQUEST]:(e,t)=>{let{payload:{req:n,path:r,method:o}}=t;return e.setIn(["requests",r,o],(0,c.oG)(n))},[h.SET_MUTATED_REQUEST]:(e,t)=>{let{payload:{req:n,path:r,method:o}}=t;return e.setIn(["mutatedRequests",r,o],(0,c.oG)(n))},[h.UPDATE_OPERATION_META_VALUE]:(e,t)=>{let{payload:{path:n,value:r,key:o}}=t,a=["paths",...n],i=["meta","paths",...n];return e.getIn(["json",...a])||e.getIn(["resolved",...a])||e.getIn(["resolvedSubtrees",...a])?e.setIn([...i,o],(0,u.fromJS)(r)):e},[h.CLEAR_RESPONSE]:(e,t)=>{let{payload:{path:n,method:r}}=t;return e.deleteIn(["responses",n,r])},[h.CLEAR_REQUEST]:(e,t)=>{let{payload:{path:n,method:r}}=t;return e.deleteIn(["requests",n,r])},[h.SET_SCHEME]:(e,t)=>{let{payload:{scheme:n,path:r,method:o}}=t;return r&&o?e.setIn(["scheme",r,o],n):r||o?void 0:e.setIn(["scheme","_defaultScheme"],n)}}},33881:(e,t,n)=>{"use strict";n.r(t),n.d(t,{allowTryItOutFor:()=>pe,basePath:()=>Y,canExecuteScheme:()=>Ce,consumes:()=>W,consumesOptionsFor:()=>Se,contentTypeValues:()=>Ee,currentProducesFor:()=>xe,definitions:()=>Z,externalDocs:()=>U,findDefinition:()=>G,getOAS3RequiredRequestBodyContentType:()=>je,getParameter:()=>ge,hasHost:()=>ye,host:()=>Q,info:()=>F,isMediaTypeSchemaPropertiesEqual:()=>Ie,isOAS3:()=>B,lastError:()=>O,mutatedRequestFor:()=>ce,mutatedRequests:()=>se,operationScheme:()=>Ae,operationWithMeta:()=>me,operations:()=>V,operationsWithRootInherited:()=>ee,operationsWithTags:()=>re,parameterInclusionSettingFor:()=>he,parameterValues:()=>ve,parameterWithMeta:()=>de,parameterWithMetaByIdentity:()=>fe,parametersIncludeIn:()=>be,parametersIncludeType:()=>we,paths:()=>$,produces:()=>H,producesOptionsFor:()=>_e,requestFor:()=>ue,requests:()=>ie,responseFor:()=>le,responses:()=>ae,schemes:()=>X,security:()=>J,securityDefinitions:()=>K,semver:()=>q,spec:()=>L,specJson:()=>N,specJsonWithResolvedSubtrees:()=>D,specResolved:()=>P,specResolvedSubtree:()=>R,specSource:()=>T,specStr:()=>I,tagDetails:()=>ne,taggedOperations:()=>oe,tags:()=>te,url:()=>j,validateBeforeExecute:()=>Oe,validationErrors:()=>ke,version:()=>z});var r=n(24278),o=n.n(r),a=n(86),i=n.n(a),s=n(11882),l=n.n(s),u=n(97606),c=n.n(u),p=n(14418),f=n.n(p),h=n(51679),d=n.n(h),m=n(24282),g=n.n(m),y=n(2578),v=n.n(y),b=n(92039),w=n.n(b),E=n(58309),x=n.n(E),_=n(20573),S=n(90242),A=n(43393);const C=["get","put","post","delete","options","head","patch","trace"],k=e=>e||(0,A.Map)(),O=(0,_.P1)(k,(e=>e.get("lastError"))),j=(0,_.P1)(k,(e=>e.get("url"))),I=(0,_.P1)(k,(e=>e.get("spec")||"")),T=(0,_.P1)(k,(e=>e.get("specSource")||"not-editor")),N=(0,_.P1)(k,(e=>e.get("json",(0,A.Map)()))),P=(0,_.P1)(k,(e=>e.get("resolved",(0,A.Map)()))),R=(e,t)=>e.getIn(["resolvedSubtrees",...t],void 0),M=(e,t)=>A.Map.isMap(e)&&A.Map.isMap(t)?t.get("$$ref")?t:(0,A.OrderedMap)().mergeWith(M,e,t):t,D=(0,_.P1)(k,(e=>(0,A.OrderedMap)().mergeWith(M,e.get("json"),e.get("resolvedSubtrees")))),L=e=>N(e),B=(0,_.P1)(L,(()=>!1)),F=(0,_.P1)(L,(e=>Te(e&&e.get("info")))),U=(0,_.P1)(L,(e=>Te(e&&e.get("externalDocs")))),z=(0,_.P1)(F,(e=>e&&e.get("version"))),q=(0,_.P1)(z,(e=>{var t;return o()(t=/v?([0-9]*)\.([0-9]*)\.([0-9]*)/i.exec(e)).call(t,1)})),$=(0,_.P1)(D,(e=>e.get("paths"))),V=(0,_.P1)($,(e=>{if(!e||e.size<1)return(0,A.List)();let t=(0,A.List)();return e&&i()(e)?(i()(e).call(e,((e,n)=>{if(!e||!i()(e))return{};i()(e).call(e,((e,r)=>{l()(C).call(C,r)<0||(t=t.push((0,A.fromJS)({path:n,method:r,operation:e,id:`${r}-${n}`})))}))})),t):(0,A.List)()})),W=(0,_.P1)(L,(e=>(0,A.Set)(e.get("consumes")))),H=(0,_.P1)(L,(e=>(0,A.Set)(e.get("produces")))),J=(0,_.P1)(L,(e=>e.get("security",(0,A.List)()))),K=(0,_.P1)(L,(e=>e.get("securityDefinitions"))),G=(e,t)=>{const n=e.getIn(["resolvedSubtrees","definitions",t],null),r=e.getIn(["json","definitions",t],null);return n||r||null},Z=(0,_.P1)(L,(e=>{const t=e.get("definitions");return A.Map.isMap(t)?t:(0,A.Map)()})),Y=(0,_.P1)(L,(e=>e.get("basePath"))),Q=(0,_.P1)(L,(e=>e.get("host"))),X=(0,_.P1)(L,(e=>e.get("schemes",(0,A.Map)()))),ee=(0,_.P1)(V,W,H,((e,t,n)=>c()(e).call(e,(e=>e.update("operation",(e=>{if(e){if(!A.Map.isMap(e))return;return e.withMutations((e=>(e.get("consumes")||e.update("consumes",(e=>(0,A.Set)(e).merge(t))),e.get("produces")||e.update("produces",(e=>(0,A.Set)(e).merge(n))),e)))}return(0,A.Map)()})))))),te=(0,_.P1)(L,(e=>{const t=e.get("tags",(0,A.List)());return A.List.isList(t)?f()(t).call(t,(e=>A.Map.isMap(e))):(0,A.List)()})),ne=(e,t)=>{var n;let r=te(e)||(0,A.List)();return d()(n=f()(r).call(r,A.Map.isMap)).call(n,(e=>e.get("name")===t),(0,A.Map)())},re=(0,_.P1)(ee,te,((e,t)=>g()(e).call(e,((e,t)=>{let n=(0,A.Set)(t.getIn(["operation","tags"]));return n.count()<1?e.update("default",(0,A.List)(),(e=>e.push(t))):g()(n).call(n,((e,n)=>e.update(n,(0,A.List)(),(e=>e.push(t)))),e)}),g()(t).call(t,((e,t)=>e.set(t.get("name"),(0,A.List)())),(0,A.OrderedMap)())))),oe=e=>t=>{var n;let{getConfigs:r}=t,{tagsSorter:o,operationsSorter:a}=r();return c()(n=re(e).sortBy(((e,t)=>t),((e,t)=>{let n="function"==typeof o?o:S.wh.tagsSorter[o];return n?n(e,t):null}))).call(n,((t,n)=>{let r="function"==typeof a?a:S.wh.operationsSorter[a],o=r?v()(t).call(t,r):t;return(0,A.Map)({tagDetails:ne(e,n),operations:o})}))},ae=(0,_.P1)(k,(e=>e.get("responses",(0,A.Map)()))),ie=(0,_.P1)(k,(e=>e.get("requests",(0,A.Map)()))),se=(0,_.P1)(k,(e=>e.get("mutatedRequests",(0,A.Map)()))),le=(e,t,n)=>ae(e).getIn([t,n],null),ue=(e,t,n)=>ie(e).getIn([t,n],null),ce=(e,t,n)=>se(e).getIn([t,n],null),pe=()=>!0,fe=(e,t,n)=>{const r=D(e).getIn(["paths",...t,"parameters"],(0,A.OrderedMap)()),o=e.getIn(["meta","paths",...t,"parameters"],(0,A.OrderedMap)()),a=c()(r).call(r,(e=>{const t=o.get(`${n.get("in")}.${n.get("name")}`),r=o.get(`${n.get("in")}.${n.get("name")}.hash-${n.hashCode()}`);return(0,A.OrderedMap)().merge(e,t,r)}));return d()(a).call(a,(e=>e.get("in")===n.get("in")&&e.get("name")===n.get("name")),(0,A.OrderedMap)())},he=(e,t,n,r)=>{const o=`${r}.${n}`;return e.getIn(["meta","paths",...t,"parameter_inclusions",o],!1)},de=(e,t,n,r)=>{const o=D(e).getIn(["paths",...t,"parameters"],(0,A.OrderedMap)()),a=d()(o).call(o,(e=>e.get("in")===r&&e.get("name")===n),(0,A.OrderedMap)());return fe(e,t,a)},me=(e,t,n)=>{var r;const o=D(e).getIn(["paths",t,n],(0,A.OrderedMap)()),a=e.getIn(["meta","paths",t,n],(0,A.OrderedMap)()),i=c()(r=o.get("parameters",(0,A.List)())).call(r,(r=>fe(e,[t,n],r)));return(0,A.OrderedMap)().merge(o,a).set("parameters",i)};function ge(e,t,n,r){t=t||[];let o=e.getIn(["meta","paths",...t,"parameters"],(0,A.fromJS)([]));return d()(o).call(o,(e=>A.Map.isMap(e)&&e.get("name")===n&&e.get("in")===r))||(0,A.Map)()}const ye=(0,_.P1)(L,(e=>{const t=e.get("host");return"string"==typeof t&&t.length>0&&"/"!==t[0]}));function ve(e,t,n){t=t||[];let r=me(e,...t).get("parameters",(0,A.List)());return g()(r).call(r,((e,t)=>{let r=n&&"body"===t.get("in")?t.get("value_xml"):t.get("value");return e.set((0,S.V9)(t,{allowHashes:!1}),r)}),(0,A.fromJS)({}))}function be(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";if(A.List.isList(e))return w()(e).call(e,(e=>A.Map.isMap(e)&&e.get("in")===t))}function we(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";if(A.List.isList(e))return w()(e).call(e,(e=>A.Map.isMap(e)&&e.get("type")===t))}function Ee(e,t){t=t||[];let n=D(e).getIn(["paths",...t],(0,A.fromJS)({})),r=e.getIn(["meta","paths",...t],(0,A.fromJS)({})),o=xe(e,t);const a=n.get("parameters")||new A.List,i=r.get("consumes_value")?r.get("consumes_value"):we(a,"file")?"multipart/form-data":we(a,"formData")?"application/x-www-form-urlencoded":void 0;return(0,A.fromJS)({requestContentType:i,responseContentType:o})}function xe(e,t){t=t||[];const n=D(e).getIn(["paths",...t],null);if(null===n)return;const r=e.getIn(["meta","paths",...t,"produces_value"],null),o=n.getIn(["produces",0],null);return r||o||"application/json"}function _e(e,t){t=t||[];const n=D(e),r=n.getIn(["paths",...t],null);if(null===r)return;const[o]=t,a=r.get("produces",null),i=n.getIn(["paths",o,"produces"],null),s=n.getIn(["produces"],null);return a||i||s}function Se(e,t){t=t||[];const n=D(e),r=n.getIn(["paths",...t],null);if(null===r)return;const[o]=t,a=r.get("consumes",null),i=n.getIn(["paths",o,"consumes"],null),s=n.getIn(["consumes"],null);return a||i||s}const Ae=(e,t,n)=>{let r=e.get("url").match(/^([a-z][a-z0-9+\-.]*):/),o=x()(r)?r[1]:null;return e.getIn(["scheme",t,n])||e.getIn(["scheme","_defaultScheme"])||o||""},Ce=(e,t,n)=>{var r;return l()(r=["http","https"]).call(r,Ae(e,t,n))>-1},ke=(e,t)=>{t=t||[];let n=e.getIn(["meta","paths",...t,"parameters"],(0,A.fromJS)([]));const r=[];return i()(n).call(n,(e=>{let t=e.get("errors");t&&t.count()&&i()(t).call(t,(e=>r.push(e)))})),r},Oe=(e,t)=>0===ke(e,t).length,je=(e,t)=>{var n;let r={requestBody:!1,requestContentType:{}},o=e.getIn(["resolvedSubtrees","paths",...t,"requestBody"],(0,A.fromJS)([]));return o.size<1||(o.getIn(["required"])&&(r.requestBody=o.getIn(["required"])),i()(n=o.getIn(["content"]).entrySeq()).call(n,(e=>{const t=e[0];if(e[1].getIn(["schema","required"])){const n=e[1].getIn(["schema","required"]).toJS();r.requestContentType[t]=n}}))),r},Ie=(e,t,n,r)=>{if((n||r)&&n===r)return!0;let o=e.getIn(["resolvedSubtrees","paths",...t,"requestBody","content"],(0,A.fromJS)([]));if(o.size<2||!n||!r)return!1;let a=o.getIn([n,"schema","properties"],(0,A.fromJS)([])),i=o.getIn([r,"schema","properties"],(0,A.fromJS)([]));return!!a.equals(i)};function Te(e){return A.Map.isMap(e)?e:new A.Map}},77508:(e,t,n)=>{"use strict";n.r(t),n.d(t,{executeRequest:()=>p,updateJsonSpec:()=>c,updateSpec:()=>u,validateParams:()=>f});var r=n(28222),o=n.n(r),a=n(86),i=n.n(a),s=n(27361),l=n.n(s);const u=(e,t)=>{let{specActions:n}=t;return function(){e(...arguments),n.parseToJson(...arguments)}},c=(e,t)=>{let{specActions:n}=t;return function(){for(var t=arguments.length,r=new Array(t),a=0;a{l()(u,[e]).$ref&&n.requestResolvedSubtree(["paths",e])})),n.requestResolvedSubtree(["components","securitySchemes"])}},p=(e,t)=>{let{specActions:n}=t;return t=>(n.logRequest(t),e(t))},f=(e,t)=>{let{specSelectors:n}=t;return t=>e(t,n.isOAS3())}},34852:(e,t,n)=>{"use strict";n.r(t),n.d(t,{loaded:()=>r});const r=(e,t)=>function(){e(...arguments);const n=t.getConfigs().withCredentials;void 0!==n&&(t.fn.fetch.withCredentials="string"==typeof n?"true"===n:!!n)}},74370:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>on});var r={};n.r(r),n.d(r,{JsonPatchError:()=>G,_areEquals:()=>ae,applyOperation:()=>ee,applyPatch:()=>te,applyReducer:()=>ne,deepClone:()=>Z,getValueByPointer:()=>X,validate:()=>oe,validator:()=>re});var o={};n.r(o),n.d(o,{compare:()=>he,generate:()=>pe,observe:()=>ce,unobserve:()=>ue});var a={};n.r(a),n.d(a,{cookie:()=>Ft,header:()=>Bt,path:()=>Mt,query:()=>Dt});var i=n(58826),s=n.n(i);const l="application/json, application/yaml";function u(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const{requestInterceptor:n,responseInterceptor:r}=t,o=e.withCredentials?"include":"same-origin";return t=>e({url:t,loadSpec:!0,requestInterceptor:n,responseInterceptor:r,headers:{Accept:l},credentials:o}).then((e=>e.body))}n(31905);var c=n(80129),p=n.n(c),f=n(1272);const h="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:window,{FormData:d,Blob:m,File:g}=h,y=e=>":/?#[]@!$&'()*+,;=".indexOf(e)>-1,v=e=>/^[a-z0-9\-._~]+$/i.test(e);function b(e){let{escape:t}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0;return"number"==typeof e&&(e=e.toString()),"string"==typeof e&&e.length&&t?n?JSON.parse(e):[...e].map((e=>{if(v(e))return e;if(y(e)&&"unsafe"===t)return e;const n=new TextEncoder;return Array.from(n.encode(e)).map((e=>`0${e.toString(16).toUpperCase()}`.slice(-2))).map((e=>`%${e}`)).join("")})).join(""):e}function w(e){const{value:t}=e;return Array.isArray(t)?function(e){let{key:t,value:n,style:r,explode:o,escape:a}=e;const i=e=>b(e,{escape:a});if("simple"===r)return n.map((e=>i(e))).join(",");if("label"===r)return`.${n.map((e=>i(e))).join(".")}`;if("matrix"===r)return n.map((e=>i(e))).reduce(((e,n)=>!e||o?`${e||""};${t}=${n}`:`${e},${n}`),"");if("form"===r){const e=o?`&${t}=`:",";return n.map((e=>i(e))).join(e)}if("spaceDelimited"===r){const e=o?`${t}=`:"";return n.map((e=>i(e))).join(` ${e}`)}if("pipeDelimited"===r){const e=o?`${t}=`:"";return n.map((e=>i(e))).join(`|${e}`)}return}(e):"object"==typeof t?function(e){let{key:t,value:n,style:r,explode:o,escape:a}=e;const i=e=>b(e,{escape:a}),s=Object.keys(n);if("simple"===r)return s.reduce(((e,t)=>{const r=i(n[t]);return`${e?`${e},`:""}${t}${o?"=":","}${r}`}),"");if("label"===r)return s.reduce(((e,t)=>{const r=i(n[t]);return`${e?`${e}.`:"."}${t}${o?"=":"."}${r}`}),"");if("matrix"===r&&o)return s.reduce(((e,t)=>`${e?`${e};`:";"}${t}=${i(n[t])}`),"");if("matrix"===r)return s.reduce(((e,r)=>{const o=i(n[r]);return`${e?`${e},`:`;${t}=`}${r},${o}`}),"");if("form"===r)return s.reduce(((e,t)=>{const r=i(n[t]);return`${e?`${e}${o?"&":","}`:""}${t}${o?"=":","}${r}`}),"");return}(e):function(e){let{key:t,value:n,style:r,escape:o}=e;const a=e=>b(e,{escape:o});if("simple"===r)return a(n);if("label"===r)return`.${a(n)}`;if("matrix"===r)return`;${t}=${a(n)}`;if("form"===r)return a(n);if("deepObject"===r)return a(n,{},!0);return}(e)}const E=(e,t)=>{t.body=e},x={serializeRes:A,mergeInQueryOrForm:R};async function _(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};"object"==typeof e&&(t=e,e=t.url),t.headers=t.headers||{},x.mergeInQueryOrForm(t),t.headers&&Object.keys(t.headers).forEach((e=>{const n=t.headers[e];"string"==typeof n&&(t.headers[e]=n.replace(/\n+/g," "))})),t.requestInterceptor&&(t=await t.requestInterceptor(t)||t);const n=t.headers["content-type"]||t.headers["Content-Type"];let r;/multipart\/form-data/i.test(n)&&t.body instanceof d&&(delete t.headers["content-type"],delete t.headers["Content-Type"]);try{r=await(t.userFetch||fetch)(t.url,t),r=await x.serializeRes(r,e,t),t.responseInterceptor&&(r=await t.responseInterceptor(r)||r)}catch(e){if(!r)throw e;const t=new Error(r.statusText||`response status is ${r.status}`);throw t.status=r.status,t.statusCode=r.status,t.responseError=e,t}if(!r.ok){const e=new Error(r.statusText||`response status is ${r.status}`);throw e.status=r.status,e.statusCode=r.status,e.response=r,e}return r}const S=function(){return/(json|xml|yaml|text)\b/.test(arguments.length>0&&void 0!==arguments[0]?arguments[0]:"")};function A(e,t){let{loadSpec:n=!1}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const r={ok:e.ok,url:e.url||t,status:e.status,statusText:e.statusText,headers:C(e.headers)},o=r.headers["content-type"],a=n||S(o);return(a?e.text:e.blob||e.buffer).call(e).then((e=>{if(r.text=e,r.data=e,a)try{const t=function(e,t){return t&&(0===t.indexOf("application/json")||t.indexOf("+json")>0)?JSON.parse(e):f.ZP.load(e)}(e,o);r.body=t,r.obj=t}catch(e){r.parseError=e}return r}))}function C(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return"function"!=typeof e.entries?{}:Array.from(e.entries()).reduce(((e,t)=>{let[n,r]=t;return e[n]=function(e){return e.includes(", ")?e.split(", "):e}(r),e}),{})}function k(e,t){return t||"undefined"==typeof navigator||(t=navigator),t&&"ReactNative"===t.product?!(!e||"object"!=typeof e||"string"!=typeof e.uri):void 0!==g&&e instanceof g||(void 0!==m&&e instanceof m||(!!ArrayBuffer.isView(e)||null!==e&&"object"==typeof e&&"function"==typeof e.pipe))}function O(e,t){return Array.isArray(e)&&e.some((e=>k(e,t)))}const j={form:",",spaceDelimited:"%20",pipeDelimited:"|"},I={csv:",",ssv:"%20",tsv:"%09",pipes:"|"};function T(e,t){let n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];const{collectionFormat:r,allowEmptyValue:o,serializationOption:a,encoding:i}=t,s="object"!=typeof t||Array.isArray(t)?t:t.value,l=n?e=>e.toString():e=>encodeURIComponent(e),u=l(e);if(void 0===s&&o)return[[u,""]];if(k(s)||O(s))return[[u,s]];if(a)return N(e,s,n,a);if(i){if([typeof i.style,typeof i.explode,typeof i.allowReserved].some((e=>"undefined"!==e))){const{style:t,explode:r,allowReserved:o}=i;return N(e,s,n,{style:t,explode:r,allowReserved:o})}if(i.contentType){if("application/json"===i.contentType){return[[u,l("string"==typeof s?s:JSON.stringify(s))]]}return[[u,l(s.toString())]]}return"object"!=typeof s?[[u,l(s)]]:Array.isArray(s)&&s.every((e=>"object"!=typeof e))?[[u,s.map(l).join(",")]]:[[u,l(JSON.stringify(s))]]}return"object"!=typeof s?[[u,l(s)]]:Array.isArray(s)?"multi"===r?[[u,s.map(l)]]:[[u,s.map(l).join(I[r||"csv"])]]:[[u,""]]}function N(e,t,n,r){const o=r.style||"form",a=void 0===r.explode?"form"===o:r.explode,i=!n&&(r&&r.allowReserved?"unsafe":"reserved"),s=e=>b(e,{escape:i}),l=n?e=>e:e=>b(e,{escape:i});return"object"!=typeof t?[[l(e),s(t)]]:Array.isArray(t)?a?[[l(e),t.map(s)]]:[[l(e),t.map(s).join(j[o])]]:"deepObject"===o?Object.keys(t).map((n=>[l(`${e}[${n}]`),s(t[n])])):a?Object.keys(t).map((e=>[l(e),s(t[e])])):[[l(e),Object.keys(t).map((e=>[`${l(e)},${s(t[e])}`])).join(",")]]}function P(e){const t=Object.keys(e).reduce(((t,n)=>{for(const[r,o]of T(n,e[n]))t[r]=o;return t}),{});return p().stringify(t,{encode:!1,indices:!1})||""}function R(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};const{url:t="",query:n,form:r}=e;if(r){const t=Object.keys(r).some((e=>{const{value:t}=r[e];return k(t)||O(t)})),n=e.headers["content-type"]||e.headers["Content-Type"];if(t||/multipart\/form-data/i.test(n)){const t=(o=e.form,Object.entries(o).reduce(((e,t)=>{let[n,r]=t;for(const[t,o]of T(n,r,!0))if(Array.isArray(o))for(const n of o)if(ArrayBuffer.isView(n)){const r=new m([n]);e.append(t,r)}else e.append(t,n);else if(ArrayBuffer.isView(o)){const n=new m([o]);e.append(t,n)}else e.append(t,o);return e}),new d));E(t,e)}else e.body=P(r);delete e.form}var o;if(n){const[r,o]=t.split("?");let a="";if(o){const e=p().parse(o);Object.keys(n).forEach((t=>delete e[t])),a=p().stringify(e,{encode:!0})}const i=function(){for(var e=arguments.length,t=new Array(e),n=0;ne)).join("&");return r?`?${r}`:""}(a,P(n));e.url=r+i,delete e.query}return e}const M=e=>{const{baseDoc:t,url:n}=e;return t||n||""},D=e=>{const{fetch:t,http:n}=e;return t||n||_};var L,B=(L=function(e,t){return L=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])},L(e,t)},function(e,t){function n(){this.constructor=e}L(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),F=Object.prototype.hasOwnProperty;function U(e,t){return F.call(e,t)}function z(e){if(Array.isArray(e)){for(var t=new Array(e.length),n=0;n=48&&t<=57))return!1;n++}return!0}function V(e){return-1===e.indexOf("/")&&-1===e.indexOf("~")?e:e.replace(/~/g,"~0").replace(/\//g,"~1")}function W(e){return e.replace(/~1/g,"/").replace(/~0/g,"~")}function H(e){if(void 0===e)return!0;if(e)if(Array.isArray(e)){for(var t=0,n=e.length;t0&&"constructor"==s[u-1]))throw new TypeError("JSON-Patch: modifying `__proto__` or `constructor/prototype` prop is banned for security reasons, if this was on purpose, please set `banPrototypeModifications` flag false and pass it to this function. More info in fast-json-patch README");if(n&&void 0===p&&(void 0===l[f]?p=s.slice(0,u).join("/"):u==c-1&&(p=t.path),void 0!==p&&h(t,0,e,p)),u++,Array.isArray(l)){if("-"===f)f=l.length;else{if(n&&!$(f))throw new G("Expected an unsigned base-10 integer value, making the new referenced value the array element with the zero-based index","OPERATION_PATH_ILLEGAL_ARRAY_INDEX",a,t,e);$(f)&&(f=~~f)}if(u>=c){if(n&&"add"===t.op&&f>l.length)throw new G("The specified index MUST NOT be greater than the number of elements in the array","OPERATION_VALUE_OUT_OF_BOUNDS",a,t,e);if(!1===(i=Q[t.op].call(t,l,f,e)).test)throw new G("Test operation failed","TEST_OPERATION_FAILED",a,t,e);return i}}else if(u>=c){if(!1===(i=Y[t.op].call(t,l,f,e)).test)throw new G("Test operation failed","TEST_OPERATION_FAILED",a,t,e);return i}if(l=l[f],n&&u0)throw new G('Operation `path` property must start with "/"',"OPERATION_PATH_INVALID",t,e,n);if(("move"===e.op||"copy"===e.op)&&"string"!=typeof e.from)throw new G("Operation `from` property is not present (applicable in `move` and `copy` operations)","OPERATION_FROM_REQUIRED",t,e,n);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&void 0===e.value)throw new G("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_REQUIRED",t,e,n);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&H(e.value))throw new G("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_CANNOT_CONTAIN_UNDEFINED",t,e,n);if(n)if("add"==e.op){var o=e.path.split("/").length,a=r.split("/").length;if(o!==a+1&&o!==a)throw new G("Cannot perform an `add` operation at the desired path","OPERATION_PATH_CANNOT_ADD",t,e,n)}else if("replace"===e.op||"remove"===e.op||"_get"===e.op){if(e.path!==r)throw new G("Cannot perform the operation at a path that does not exist","OPERATION_PATH_UNRESOLVABLE",t,e,n)}else if("move"===e.op||"copy"===e.op){var i=oe([{op:"_get",path:e.from,value:void 0}],n);if(i&&"OPERATION_PATH_UNRESOLVABLE"===i.name)throw new G("Cannot perform the operation from a path that does not exist","OPERATION_FROM_UNRESOLVABLE",t,e,n)}}function oe(e,t,n){try{if(!Array.isArray(e))throw new G("Patch sequence must be an array","SEQUENCE_NOT_AN_ARRAY");if(t)te(q(t),q(e),n||!0);else{n=n||re;for(var r=0;r0&&(e.patches=[],e.callback&&e.callback(r)),r}function fe(e,t,n,r,o){if(t!==e){"function"==typeof t.toJSON&&(t=t.toJSON());for(var a=z(t),i=z(e),s=!1,l=i.length-1;l>=0;l--){var u=e[p=i[l]];if(!U(t,p)||void 0===t[p]&&void 0!==u&&!1===Array.isArray(t))Array.isArray(e)===Array.isArray(t)?(o&&n.push({op:"test",path:r+"/"+V(p),value:q(u)}),n.push({op:"remove",path:r+"/"+V(p)}),s=!0):(o&&n.push({op:"test",path:r,value:e}),n.push({op:"replace",path:r,value:t}),!0);else{var c=t[p];"object"==typeof u&&null!=u&&"object"==typeof c&&null!=c&&Array.isArray(u)===Array.isArray(c)?fe(u,c,n,r+"/"+V(p),o):u!==c&&(!0,o&&n.push({op:"test",path:r+"/"+V(p),value:q(u)}),n.push({op:"replace",path:r+"/"+V(p),value:q(c)}))}}if(s||a.length!=i.length)for(l=0;lvoid 0!==t&&e?e[t]:e),e)},applyPatch:function(e,t,n){if(n=n||{},"merge"===(t=s()(s()({},t),{},{path:t.path&&ye(t.path)})).op){const n=Te(e,t.path);Object.assign(n,t.value),te(e,[ve(t.path,n)])}else if("mergeDeep"===t.op){const n=Te(e,t.path),r=me()(n,t.value);e=te(e,[ve(t.path,r)]).newDocument}else if("add"===t.op&&""===t.path&&Ae(t.value)){te(e,Object.keys(t.value).reduce(((e,n)=>(e.push({op:"add",path:`/${ye(n)}`,value:t.value[n]}),e)),[]))}else if("replace"===t.op&&""===t.path){let{value:r}=t;n.allowMetaPatches&&t.meta&&je(t)&&(Array.isArray(t.value)||Ae(t.value))&&(r=s()(s()({},r),t.meta)),e=r}else if(te(e,[t]),n.allowMetaPatches&&t.meta&&je(t)&&(Array.isArray(t.value)||Ae(t.value))){const n=Te(e,t.path),r=s()(s()({},n),t.meta);te(e,[ve(t.path,r)])}return e},parentPathMatch:function(e,t){if(!Array.isArray(t))return!1;for(let n=0,r=t.length;n(e+"").replace(/~/g,"~0").replace(/\//g,"~1"))).join("/")}`:e}function ve(e,t,n){return{op:"replace",path:e,value:t,meta:n}}function be(e,t,n){return Se(_e(e.filter(je).map((e=>t(e.value,n,e.path)))||[]))}function we(e,t,n){return n=n||[],Array.isArray(e)?e.map(((e,r)=>we(e,t,n.concat(r)))):Ae(e)?Object.keys(e).map((r=>we(e[r],t,n.concat(r)))):t(e,n[n.length-1],n)}function Ee(e,t,n){let r=[];if((n=n||[]).length>0){const o=t(e,n[n.length-1],n);o&&(r=r.concat(o))}if(Array.isArray(e)){const o=e.map(((e,r)=>Ee(e,t,n.concat(r))));o&&(r=r.concat(o))}else if(Ae(e)){const o=Object.keys(e).map((r=>Ee(e[r],t,n.concat(r))));o&&(r=r.concat(o))}return r=_e(r),r}function xe(e){return Array.isArray(e)?e:[e]}function _e(e){return[].concat(...e.map((e=>Array.isArray(e)?_e(e):e)))}function Se(e){return e.filter((e=>void 0!==e))}function Ae(e){return e&&"object"==typeof e}function Ce(e){return e&&"function"==typeof e}function ke(e){if(Ie(e)){const{op:t}=e;return"add"===t||"remove"===t||"replace"===t}return!1}function Oe(e){return ke(e)||Ie(e)&&"mutation"===e.type}function je(e){return Oe(e)&&("add"===e.op||"replace"===e.op||"merge"===e.op||"mergeDeep"===e.op)}function Ie(e){return e&&"object"==typeof e}function Te(e,t){try{return X(e,t)}catch(e){return console.error(e),{}}}var Ne=n(8575);function Pe(e,t){function n(){Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=(new Error).stack;for(var e=arguments.length,n=new Array(e),r=0;r-1&&-1===Le.indexOf(n)||Be.indexOf(r)>-1||Fe.some((e=>r.indexOf(e)>-1))}function ze(e,t){const[n,r]=e.split("#"),o=Ne.resolve(n||"",t||"");return r?`${o}#${r}`:o}const qe=/^([a-z]+:\/\/|\/\/)/i,$e=Pe("JSONRefError",(function(e,t,n){this.originalError=n,Object.assign(this,t||{})})),Ve={},We=new WeakMap,He=[e=>"paths"===e[0]&&"responses"===e[3]&&"examples"===e[5],e=>"paths"===e[0]&&"responses"===e[3]&&"content"===e[5]&&"example"===e[7],e=>"paths"===e[0]&&"responses"===e[3]&&"content"===e[5]&&"examples"===e[7]&&"value"===e[9],e=>"paths"===e[0]&&"requestBody"===e[3]&&"content"===e[4]&&"example"===e[6],e=>"paths"===e[0]&&"requestBody"===e[3]&&"content"===e[4]&&"examples"===e[6]&&"value"===e[8],e=>"paths"===e[0]&&"parameters"===e[2]&&"example"===e[4],e=>"paths"===e[0]&&"parameters"===e[3]&&"example"===e[5],e=>"paths"===e[0]&&"parameters"===e[2]&&"examples"===e[4]&&"value"===e[6],e=>"paths"===e[0]&&"parameters"===e[3]&&"examples"===e[5]&&"value"===e[7],e=>"paths"===e[0]&&"parameters"===e[2]&&"content"===e[4]&&"example"===e[6],e=>"paths"===e[0]&&"parameters"===e[2]&&"content"===e[4]&&"examples"===e[6]&&"value"===e[8],e=>"paths"===e[0]&&"parameters"===e[3]&&"content"===e[4]&&"example"===e[7],e=>"paths"===e[0]&&"parameters"===e[3]&&"content"===e[5]&&"examples"===e[7]&&"value"===e[9]],Je={key:"$ref",plugin:(e,t,n,r)=>{const o=r.getInstance(),a=n.slice(0,-1);if(Ue(a)||(e=>He.some((t=>t(e))))(a))return;const{baseDoc:i}=r.getContext(n);if("string"!=typeof e)return new $e("$ref: must be a string (JSON-Ref)",{$ref:e,baseDoc:i,fullPath:n});const s=Qe(e),l=s[0],u=s[1]||"";let c,p,f;try{c=i||l?Ze(l,i):null}catch(t){return Ye(t,{pointer:u,$ref:e,basePath:c,fullPath:n})}if(function(e,t,n,r){let o=We.get(r);o||(o={},We.set(r,o));const a=function(e){if(0===e.length)return"";return`/${e.map(ot).join("/")}`}(n),i=`${t||""}#${e}`,s=a.replace(/allOf\/\d+\/?/g,""),l=r.contextTree.get([]).baseDoc;if(t===l&&it(s,e))return!0;let u="";const c=n.some((e=>(u=`${u}/${ot(e)}`,o[u]&&o[u].some((e=>it(e,i)||it(i,e))))));if(c)return!0;return void(o[s]=(o[s]||[]).concat(i))}(u,c,a,r)&&!o.useCircularStructures){const t=ze(e,c);return e===t?null:ge.replace(n,t)}if(null==c?(f=nt(u),p=r.get(f),void 0===p&&(p=new $e(`Could not resolve reference: ${e}`,{pointer:u,$ref:e,baseDoc:i,fullPath:n}))):(p=Xe(c,u),p=null!=p.__value?p.__value:p.catch((t=>{throw Ye(t,{pointer:u,$ref:e,baseDoc:i,fullPath:n})}))),p instanceof Error)return[ge.remove(n),p];const h=ze(e,c),d=ge.replace(a,p,{$$ref:h});if(c&&c!==i)return[d,ge.context(a,{baseDoc:c})];try{if(!function(e,t){const n=[e];return t.path.reduce(((e,t)=>(n.push(e[t]),e[t])),e),r(t.value);function r(e){return ge.isObject(e)&&(n.indexOf(e)>=0||Object.keys(e).some((t=>r(e[t]))))}}(r.state,d)||o.useCircularStructures)return d}catch(e){return null}}},Ke=Object.assign(Je,{docCache:Ve,absoluteify:Ze,clearCache:function(e){void 0!==e?delete Ve[e]:Object.keys(Ve).forEach((e=>{delete Ve[e]}))},JSONRefError:$e,wrapError:Ye,getDoc:et,split:Qe,extractFromDoc:Xe,fetchJSON:function(e){return fetch(e,{headers:{Accept:l},loadSpec:!0}).then((e=>e.text())).then((e=>f.ZP.load(e)))},extract:tt,jsonPointerToArray:nt,unescapeJsonPointerToken:rt}),Ge=Ke;function Ze(e,t){if(!qe.test(e)){if(!t)throw new $e(`Tried to resolve a relative URL, without having a basePath. path: '${e}' basePath: '${t}'`);return Ne.resolve(t,e)}return e}function Ye(e,t){let n;return n=e&&e.response&&e.response.body?`${e.response.body.code} ${e.response.body.message}`:e.message,new $e(`Could not resolve reference: ${n}`,t,e)}function Qe(e){return(e+"").split("#")}function Xe(e,t){const n=Ve[e];if(n&&!ge.isPromise(n))try{const e=tt(t,n);return Object.assign(Promise.resolve(e),{__value:e})}catch(e){return Promise.reject(e)}return et(e).then((e=>tt(t,e)))}function et(e){const t=Ve[e];return t?ge.isPromise(t)?t:Promise.resolve(t):(Ve[e]=Ke.fetchJSON(e).then((t=>(Ve[e]=t,t))),Ve[e])}function tt(e,t){const n=nt(e);if(n.length<1)return t;const r=ge.getIn(t,n);if(void 0===r)throw new $e(`Could not resolve pointer: ${e} does not exist in document`,{pointer:e});return r}function nt(e){if("string"!=typeof e)throw new TypeError("Expected a string, got a "+typeof e);return"/"===e[0]&&(e=e.substr(1)),""===e?[]:e.split("/").map(rt)}function rt(e){if("string"!=typeof e)return e;return new URLSearchParams(`=${e.replace(/~1/g,"/").replace(/~0/g,"~")}`).get("")}function ot(e){return new URLSearchParams([["",e.replace(/~/g,"~0").replace(/\//g,"~1")]]).toString().slice(1)}const at=e=>!e||"/"===e||"#"===e;function it(e,t){if(at(t))return!0;const n=e.charAt(t.length),r=t.slice(-1);return 0===e.indexOf(t)&&(!n||"/"===n||"#"===n)&&"#"!==r}const st={key:"allOf",plugin:(e,t,n,r,o)=>{if(o.meta&&o.meta.$$ref)return;const a=n.slice(0,-1);if(Ue(a))return;if(!Array.isArray(e)){const e=new TypeError("allOf must be an array");return e.fullPath=n,e}let i=!1,l=o.value;if(a.forEach((e=>{l&&(l=l[e])})),l=s()({},l),0===Object.keys(l).length)return;delete l.allOf;const u=[];return u.push(r.replace(a,{})),e.forEach(((e,t)=>{if(!r.isObject(e)){if(i)return null;i=!0;const e=new TypeError("Elements in allOf must be objects");return e.fullPath=n,u.push(e)}u.push(r.mergeDeep(a,e));const o=function(e,t){let{specmap:n,getBaseUrlForNodePath:r=(e=>n.getContext([...t,...e]).baseDoc),targetKeys:o=["$ref","$$ref"]}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const a=[];return Me()(e).forEach((function(){if(o.includes(this.key)&&"string"==typeof this.node){const e=this.path,o=t.concat(this.path),i=ze(this.node,r(e));a.push(n.replace(o,i))}})),a}(e,n.slice(0,-1),{getBaseUrlForNodePath:e=>r.getContext([...n,t,...e]).baseDoc,specmap:r});u.push(...o)})),l.example&&u.push(r.remove([].concat(a,"example"))),u.push(r.mergeDeep(a,l)),l.$$ref||u.push(r.remove([].concat(a,"$$ref"))),u}},lt={key:"parameters",plugin:(e,t,n,r)=>{if(Array.isArray(e)&&e.length){const t=Object.assign([],e),o=n.slice(0,-1),a=s()({},ge.getIn(r.spec,o));for(let o=0;o{const o=s()({},e);for(const t in e)try{o[t].default=r.modelPropertyMacro(o[t])}catch(e){const t=new Error(e);return t.fullPath=n,t}return ge.replace(n,o)}};class ct{constructor(e){this.root=pt(e||{})}set(e,t){const n=this.getParent(e,!0);if(!n)return void ft(this.root,t,null);const r=e[e.length-1],{children:o}=n;o[r]?ft(o[r],t,n):o[r]=pt(t,n)}get(e){if((e=e||[]).length<1)return this.root.value;let t,n,r=this.root;for(let o=0;o{if(!e)return e;const{children:r}=e;return!r[n]&&t&&(r[n]=pt(null,e)),r[n]}),this.root)}}function pt(e,t){return ft({children:{}},e,t)}function ft(e,t,n){return e.value=t||{},e.protoValue=n?s()(s()({},n.protoValue),e.value):e.value,Object.keys(e.children).forEach((t=>{const n=e.children[t];e.children[t]=ft(n,n.value,e)})),e}const ht=()=>{};class dt{static getPluginName(e){return e.pluginName}static getPatchesOfType(e,t){return e.filter(t)}constructor(e){Object.assign(this,{spec:"",debugLevel:"info",plugins:[],pluginHistory:{},errors:[],mutations:[],promisedPatches:[],state:{},patches:[],context:{},contextTree:new ct,showDebug:!1,allPatches:[],pluginProp:"specMap",libMethods:Object.assign(Object.create(this),ge,{getInstance:()=>this}),allowMetaPatches:!1},e),this.get=this._get.bind(this),this.getContext=this._getContext.bind(this),this.hasRun=this._hasRun.bind(this),this.wrappedPlugins=this.plugins.map(this.wrapPlugin.bind(this)).filter(ge.isFunction),this.patches.push(ge.add([],this.spec)),this.patches.push(ge.context([],this.context)),this.updatePatches(this.patches)}debug(e){if(this.debugLevel===e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r1?t-1:0),r=1;r!Array.isArray(e)||e.every(((e,n)=>e===t[n]));return function*(r,o){const a={};for(const e of r.filter(ge.isAdditiveMutation))yield*i(e.value,e.path,e);function*i(r,s,l){if(ge.isObject(r)){const u=s.length-1,c=s[u],p=s.indexOf("properties"),f="properties"===c&&u===p,h=o.allowMetaPatches&&a[r.$$ref];for(const u of Object.keys(r)){const c=r[u],p=s.concat(u),d=ge.isObject(c),m=r.$$ref;if(h||d&&(o.allowMetaPatches&&m&&(a[m]=!0),yield*i(c,p,l)),!f&&u===e.key){const r=t(n,s);n&&!r||(yield e.plugin(c,u,p,o,l))}}}else e.key===s[s.length-1]&&(yield e.plugin(r,e.key,s,o))}}}(e)),Object.assign(r.bind(o),{pluginName:e.name||t,isGenerator:ge.isGenerator(r)})}nextPlugin(){return this.wrappedPlugins.find((e=>this.getMutationsForPlugin(e).length>0))}nextPromisedPatch(){if(this.promisedPatches.length>0)return Promise.race(this.promisedPatches.map((e=>e.value)))}getPluginHistory(e){const t=this.constructor.getPluginName(e);return this.pluginHistory[t]||[]}getPluginRunCount(e){return this.getPluginHistory(e).length}getPluginHistoryTip(e){const t=this.getPluginHistory(e);return t&&t[t.length-1]||{}}getPluginMutationIndex(e){const t=this.getPluginHistoryTip(e).mutationIndex;return"number"!=typeof t?-1:t}updatePluginHistory(e,t){const n=this.constructor.getPluginName(e);this.pluginHistory[n]=this.pluginHistory[n]||[],this.pluginHistory[n].push(t)}updatePatches(e){ge.normalizeArray(e).forEach((e=>{if(e instanceof Error)this.errors.push(e);else try{if(!ge.isObject(e))return void this.debug("updatePatches","Got a non-object patch",e);if(this.showDebug&&this.allPatches.push(e),ge.isPromise(e.value))return this.promisedPatches.push(e),void this.promisedPatchThen(e);if(ge.isContextPatch(e))return void this.setContext(e.path,e.value);if(ge.isMutation(e))return void this.updateMutations(e)}catch(e){console.error(e),this.errors.push(e)}}))}updateMutations(e){"object"==typeof e.value&&!Array.isArray(e.value)&&this.allowMetaPatches&&(e.value=s()({},e.value));const t=ge.applyPatch(this.state,e,{allowMetaPatches:this.allowMetaPatches});t&&(this.mutations.push(e),this.state=t)}removePromisedPatch(e){const t=this.promisedPatches.indexOf(e);t<0?this.debug("Tried to remove a promisedPatch that isn't there!"):this.promisedPatches.splice(t,1)}promisedPatchThen(e){return e.value=e.value.then((t=>{const n=s()(s()({},e),{},{value:t});this.removePromisedPatch(e),this.updatePatches(n)})).catch((t=>{this.removePromisedPatch(e),this.updatePatches(t)})),e.value}getMutations(e,t){return e=e||0,"number"!=typeof t&&(t=this.mutations.length),this.mutations.slice(e,t)}getCurrentMutations(){return this.getMutationsForPlugin(this.getCurrentPlugin())}getMutationsForPlugin(e){const t=this.getPluginMutationIndex(e);return this.getMutations(t+1)}getCurrentPlugin(){return this.currentPlugin}getLib(){return this.libMethods}_get(e){return ge.getIn(this.state,e)}_getContext(e){return this.contextTree.get(e)}setContext(e,t){return this.contextTree.set(e,t)}_hasRun(e){return this.getPluginRunCount(this.getCurrentPlugin())>(e||0)}dispatch(){const e=this,t=this.nextPlugin();if(!t){const e=this.nextPromisedPatch();if(e)return e.then((()=>this.dispatch())).catch((()=>this.dispatch()));const t={spec:this.state,errors:this.errors};return this.showDebug&&(t.patches=this.allPatches),Promise.resolve(t)}if(e.pluginCount=e.pluginCount||{},e.pluginCount[t]=(e.pluginCount[t]||0)+1,e.pluginCount[t]>100)return Promise.resolve({spec:e.state,errors:e.errors.concat(new Error("We've reached a hard limit of 100 plugin runs"))});if(t!==this.currentPlugin&&this.promisedPatches.length){const e=this.promisedPatches.map((e=>e.value));return Promise.all(e.map((e=>e.then(ht,ht)))).then((()=>this.dispatch()))}return function(){e.currentPlugin=t;const r=e.getCurrentMutations(),o=e.mutations.length-1;try{if(t.isGenerator)for(const o of t(r,e.getLib()))n(o);else{n(t(r,e.getLib()))}}catch(e){console.error(e),n([Object.assign(Object.create(e),{plugin:t})])}finally{e.updatePluginHistory(t,{mutationIndex:o})}return e.dispatch()}();function n(n){n&&(n=ge.fullyNormalizeArray(n),e.updatePatches(n,t))}}}const mt={refs:Ge,allOf:st,parameters:lt,properties:ut};var gt=n(32454);function yt(e){const{spec:t}=e,{paths:n}=t,r={};if(!n||t.$$normalized)return e;for(const e in n){const o=n[e];if(null==o||!["object","function"].includes(typeof o))continue;const a=o.parameters;for(const n in o){const i=o[n];if(null==i||!["object","function"].includes(typeof i))continue;const s=(0,gt.Z)(i,e,n);if(s){r[s]?r[s].push(i):r[s]=[i];const e=r[s];if(e.length>1)e.forEach(((e,t)=>{e.__originalOperationId=e.__originalOperationId||e.operationId,e.operationId=`${s}${t+1}`}));else if(void 0!==i.operationId){const t=e[0];t.__originalOperationId=t.__originalOperationId||i.operationId,t.operationId=s}}if("parameters"!==n){const e=[],n={};for(const r in t)"produces"!==r&&"consumes"!==r&&"security"!==r||(n[r]=t[r],e.push(n));if(a&&(n.parameters=a,e.push(n)),e.length)for(const t of e)for(const e in t)if(i[e]){if("parameters"===e)for(const n of t[e]){i[e].some((e=>e.name&&e.name===n.name||e.$ref&&e.$ref===n.$ref||e.$$ref&&e.$$ref===n.$$ref||e===n))||i[e].push(n)}}else i[e]=t[e]}}}return t.$$normalized=!0,e}async function vt(e){const{spec:t,mode:n,allowMetaPatches:r=!0,pathDiscriminator:o,modelPropertyMacro:a,parameterMacro:i,requestInterceptor:s,responseInterceptor:l,skipNormalization:c,useCircularStructures:p}=e,f=M(e),h=D(e);return function(e){f&&(mt.refs.docCache[f]=e);mt.refs.fetchJSON=u(h,{requestInterceptor:s,responseInterceptor:l});const t=[mt.refs];"function"==typeof i&&t.push(mt.parameters);"function"==typeof a&&t.push(mt.properties);"strict"!==n&&t.push(mt.allOf);return(d={spec:e,context:{baseDoc:f},plugins:t,allowMetaPatches:r,pathDiscriminator:o,parameterMacro:i,modelPropertyMacro:a,useCircularStructures:p},new dt(d).dispatch()).then(c?async e=>e:yt);var d}(t)}const bt={name:"generic",match:()=>!0,normalize(e){let{spec:t}=e;const{spec:n}=yt({spec:t});return n},resolve:async e=>vt(e)};const wt=e=>{try{const{openapi:t}=e;return"string"==typeof t&&/^3\.0\.([0123])(?:-rc[012])?$/.test(t)}catch{return!1}},Et=e=>wt(e)||(e=>{try{const{openapi:t}=e;return"string"==typeof t&&/^3\.1\.(?:[1-9]\d*|0)$/.test(t)}catch{return!1}})(e),xt={name:"openapi-2",match(e){let{spec:t}=e;return(e=>{try{const{swagger:t}=e;return"2.0"===t}catch{return!1}})(t)},normalize(e){let{spec:t}=e;const{spec:n}=yt({spec:t});return n},resolve:async e=>async function(e){return vt(e)}(e)};const _t={name:"openapi-3-0",match(e){let{spec:t}=e;return wt(t)},normalize(e){let{spec:t}=e;const{spec:n}=yt({spec:t});return n},resolve:async e=>async function(e){return vt(e)}(e)},St=(At={strategies:[_t,xt,bt]},async e=>(async e=>{const{spec:t,requestInterceptor:n,responseInterceptor:r}=e,o=M(e),a=D(e),i=t||await u(a,{requestInterceptor:n,responseInterceptor:r})(o),l=s()(s()({},e),{},{spec:i});return e.strategies.find((e=>e.match(l))).resolve(l)})(s()(s()({},At),e)));var At,Ct=n(88436),kt=n.n(Ct),Ot=n(27361),jt=n.n(Ot),It=n(76489);function Tt(e){return"[object Object]"===Object.prototype.toString.call(e)}function Nt(e){var t,n;return!1!==Tt(e)&&(void 0===(t=e.constructor)||!1!==Tt(n=t.prototype)&&!1!==n.hasOwnProperty("isPrototypeOf"))}const Pt={body:function(e){let{req:t,value:n}=e;t.body=n},header:function(e){let{req:t,parameter:n,value:r}=e;t.headers=t.headers||{},void 0!==r&&(t.headers[n.name]=r)},query:function(e){let{req:t,value:n,parameter:r}=e;t.query=t.query||{},!1===n&&"boolean"===r.type&&(n="false");0===n&&["number","integer"].indexOf(r.type)>-1&&(n="0");if(n)t.query[r.name]={collectionFormat:r.collectionFormat,value:n};else if(r.allowEmptyValue&&void 0!==n){const e=r.name;t.query[e]=t.query[e]||{},t.query[e].allowEmptyValue=!0}},path:function(e){let{req:t,value:n,parameter:r}=e;t.url=t.url.split(`{${r.name}}`).join(encodeURIComponent(n))},formData:function(e){let{req:t,value:n,parameter:r}=e;(n||r.allowEmptyValue)&&(t.form=t.form||{},t.form[r.name]={value:n,allowEmptyValue:r.allowEmptyValue,collectionFormat:r.collectionFormat})}};function Rt(e,t){return t.includes("application/json")?"string"==typeof e?e:JSON.stringify(e):e.toString()}function Mt(e){let{req:t,value:n,parameter:r}=e;const{name:o,style:a,explode:i,content:s}=r;if(s){const e=Object.keys(s)[0];return void(t.url=t.url.split(`{${o}}`).join(b(Rt(n,e),{escape:!0})))}const l=w({key:r.name,value:n,style:a||"simple",explode:i||!1,escape:!0});t.url=t.url.split(`{${o}}`).join(l)}function Dt(e){let{req:t,value:n,parameter:r}=e;if(t.query=t.query||{},r.content){const e=Rt(n,Object.keys(r.content)[0]);if(e)t.query[r.name]=e;else if(r.allowEmptyValue&&void 0!==n){const e=r.name;t.query[e]=t.query[e]||{},t.query[e].allowEmptyValue=!0}}else if(!1===n&&(n="false"),0===n&&(n="0"),n){const{style:e,explode:o,allowReserved:a}=r;t.query[r.name]={value:n,serializationOption:{style:e,explode:o,allowReserved:a}}}else if(r.allowEmptyValue&&void 0!==n){const e=r.name;t.query[e]=t.query[e]||{},t.query[e].allowEmptyValue=!0}}const Lt=["accept","authorization","content-type"];function Bt(e){let{req:t,parameter:n,value:r}=e;if(t.headers=t.headers||{},!(Lt.indexOf(n.name.toLowerCase())>-1))if(n.content){const e=Object.keys(n.content)[0];t.headers[n.name]=Rt(r,e)}else void 0!==r&&(t.headers[n.name]=w({key:n.name,value:r,style:n.style||"simple",explode:void 0!==n.explode&&n.explode,escape:!1}))}function Ft(e){let{req:t,parameter:n,value:r}=e;t.headers=t.headers||{};const o=typeof r;if(n.content){const e=Object.keys(n.content)[0];t.headers.Cookie=`${n.name}=${Rt(r,e)}`}else if("undefined"!==o){const e="object"===o&&!Array.isArray(r)&&n.explode?"":`${n.name}=`;t.headers.Cookie=e+w({key:n.name,value:r,escape:!1,style:n.style||"form",explode:void 0!==n.explode&&n.explode})}}const Ut="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:window,{btoa:zt}=Ut,qt=zt;function $t(e,t){const{operation:n,requestBody:r,securities:o,spec:a,attachContentTypeForEmptyPayload:i}=e;let{requestContentType:l}=e;t=function(e){let{request:t,securities:n={},operation:r={},spec:o}=e;const a=s()({},t),{authorized:i={}}=n,l=r.security||o.security||[],u=i&&!!Object.keys(i).length,c=jt()(o,["components","securitySchemes"])||{};if(a.headers=a.headers||{},a.query=a.query||{},!Object.keys(n).length||!u||!l||Array.isArray(r.security)&&!r.security.length)return t;return l.forEach((e=>{Object.keys(e).forEach((e=>{const t=i[e],n=c[e];if(!t)return;const r=t.value||t,{type:o}=n;if(t)if("apiKey"===o)"query"===n.in&&(a.query[n.name]=r),"header"===n.in&&(a.headers[n.name]=r),"cookie"===n.in&&(a.cookies[n.name]=r);else if("http"===o){if(/^basic$/i.test(n.scheme)){const e=r.username||"",t=r.password||"",n=qt(`${e}:${t}`);a.headers.Authorization=`Basic ${n}`}/^bearer$/i.test(n.scheme)&&(a.headers.Authorization=`Bearer ${r}`)}else if("oauth2"===o||"openIdConnect"===o){const e=t.token||{},r=e[n["x-tokenName"]||"access_token"];let o=e.token_type;o&&"bearer"!==o.toLowerCase()||(o="Bearer"),a.headers.Authorization=`${o} ${r}`}}))})),a}({request:t,securities:o,operation:n,spec:a});const u=n.requestBody||{},c=Object.keys(u.content||{}),p=l&&c.indexOf(l)>-1;if(r||i){if(l&&p)t.headers["Content-Type"]=l;else if(!l){const e=c[0];e&&(t.headers["Content-Type"]=e,l=e)}}else l&&p&&(t.headers["Content-Type"]=l);if(!e.responseContentType&&n.responses){const e=Object.entries(n.responses).filter((e=>{let[t,n]=e;const r=parseInt(t,10);return r>=200&&r<300&&Nt(n.content)})).reduce(((e,t)=>{let[,n]=t;return e.concat(Object.keys(n.content))}),[]);e.length>0&&(t.headers.accept=e.join(", "))}if(r)if(l){if(c.indexOf(l)>-1)if("application/x-www-form-urlencoded"===l||"multipart/form-data"===l)if("object"==typeof r){const e=(u.content[l]||{}).encoding||{};t.form={},Object.keys(r).forEach((n=>{t.form[n]={value:r[n],encoding:e[n]||{}}}))}else t.form=r;else t.body=r}else t.body=r;return t}function Vt(e,t){const{spec:n,operation:r,securities:o,requestContentType:a,responseContentType:i,attachContentTypeForEmptyPayload:l}=e;if(t=function(e){let{request:t,securities:n={},operation:r={},spec:o}=e;const a=s()({},t),{authorized:i={},specSecurity:l=[]}=n,u=r.security||l,c=i&&!!Object.keys(i).length,p=o.securityDefinitions;if(a.headers=a.headers||{},a.query=a.query||{},!Object.keys(n).length||!c||!u||Array.isArray(r.security)&&!r.security.length)return t;return u.forEach((e=>{Object.keys(e).forEach((e=>{const t=i[e];if(!t)return;const{token:n}=t,r=t.value||t,o=p[e],{type:s}=o,l=o["x-tokenName"]||"access_token",u=n&&n[l];let c=n&&n.token_type;if(t)if("apiKey"===s){const e="query"===o.in?"query":"headers";a[e]=a[e]||{},a[e][o.name]=r}else if("basic"===s)if(r.header)a.headers.authorization=r.header;else{const e=r.username||"",t=r.password||"";r.base64=qt(`${e}:${t}`),a.headers.authorization=`Basic ${r.base64}`}else"oauth2"===s&&u&&(c=c&&"bearer"!==c.toLowerCase()?c:"Bearer",a.headers.authorization=`${c} ${u}`)}))})),a}({request:t,securities:o,operation:r,spec:n}),t.body||t.form||l)a?t.headers["Content-Type"]=a:Array.isArray(r.consumes)?[t.headers["Content-Type"]]=r.consumes:Array.isArray(n.consumes)?[t.headers["Content-Type"]]=n.consumes:r.parameters&&r.parameters.filter((e=>"file"===e.type)).length?t.headers["Content-Type"]="multipart/form-data":r.parameters&&r.parameters.filter((e=>"formData"===e.in)).length&&(t.headers["Content-Type"]="application/x-www-form-urlencoded");else if(a){const e=r.parameters&&r.parameters.filter((e=>"body"===e.in)).length>0,n=r.parameters&&r.parameters.filter((e=>"formData"===e.in)).length>0;(e||n)&&(t.headers["Content-Type"]=a)}return!i&&Array.isArray(r.produces)&&r.produces.length>0&&(t.headers.accept=r.produces.join(", ")),t}function Wt(e,t){return`${t.toLowerCase()}-${e}`}const Ht=["http","fetch","spec","operationId","pathName","method","parameters","securities"],Jt=e=>Array.isArray(e)?e:[],Kt=Pe("OperationNotFoundError",(function(e,t,n){this.originalError=n,Object.assign(this,t||{})})),Gt=(e,t)=>t.filter((t=>t.name===e)),Zt=e=>{const t={};e.forEach((e=>{t[e.in]||(t[e.in]={}),t[e.in][e.name]=e}));const n=[];return Object.keys(t).forEach((e=>{Object.keys(t[e]).forEach((r=>{n.push(t[e][r])}))})),n},Yt={buildRequest:Xt};function Qt(e){let{http:t,fetch:n,spec:r,operationId:o,pathName:a,method:i,parameters:l,securities:u}=e,c=kt()(e,Ht);const p=t||n||_;a&&i&&!o&&(o=Wt(a,i));const f=Yt.buildRequest(s()({spec:r,operationId:o,parameters:l,securities:u,http:p},c));return f.body&&(Nt(f.body)||Array.isArray(f.body))&&(f.body=JSON.stringify(f.body)),p(f)}function Xt(e){const{spec:t,operationId:n,responseContentType:r,scheme:o,requestInterceptor:i,responseInterceptor:l,contextUrl:u,userFetch:c,server:p,serverVariables:f,http:h,signal:d}=e;let{parameters:m,parameterBuilders:g}=e;const y=Et(t);g||(g=y?a:Pt);let v={url:"",credentials:h&&h.withCredentials?"include":"same-origin",headers:{},cookies:{}};d&&(v.signal=d),i&&(v.requestInterceptor=i),l&&(v.responseInterceptor=l),c&&(v.userFetch=c);const b=function(e,t){return e&&e.paths?function(e,t){return function(e,t,n){if(!e||"object"!=typeof e||!e.paths||"object"!=typeof e.paths)return null;const{paths:r}=e;for(const o in r)for(const a in r[o]){if("PARAMETERS"===a.toUpperCase())continue;const i=r[o][a];if(!i||"object"!=typeof i)continue;const s={spec:e,pathName:o,method:a.toUpperCase(),operation:i},l=t(s);if(n&&l)return s}}(e,t,!0)||null}(e,(e=>{let{pathName:n,method:r,operation:o}=e;if(!o||"object"!=typeof o)return!1;const a=o.operationId;return[(0,gt.Z)(o,n,r),Wt(n,r),a].some((e=>e&&e===t))})):null}(t,n);if(!b)throw new Kt(`Operation ${n} not found`);const{operation:w={},method:E,pathName:x}=b;if(v.url+=function(e){const t=Et(e.spec);return t?function(e){let{spec:t,pathName:n,method:r,server:o,contextUrl:a,serverVariables:i={}}=e;const s=jt()(t,["paths",n,(r||"").toLowerCase(),"servers"])||jt()(t,["paths",n,"servers"])||jt()(t,["servers"]);let l="",u=null;if(o&&s&&s.length){const e=s.map((e=>e.url));e.indexOf(o)>-1&&(l=o,u=s[e.indexOf(o)])}!l&&s&&s.length&&(l=s[0].url,[u]=s);if(l.indexOf("{")>-1){(function(e){const t=[],n=/{([^}]+)}/g;let r;for(;r=n.exec(e);)t.push(r[1]);return t})(l).forEach((e=>{if(u.variables&&u.variables[e]){const t=u.variables[e],n=i[e]||t.default,r=new RegExp(`{${e}}`,"g");l=l.replace(r,n)}}))}return function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";const n=e&&t?Ne.parse(Ne.resolve(t,e)):Ne.parse(e),r=Ne.parse(t),o=en(n.protocol)||en(r.protocol)||"",a=n.host||r.host,i=n.pathname||"";let s;s=o&&a?`${o}://${a+i}`:i;return"/"===s[s.length-1]?s.slice(0,-1):s}(l,a)}(e):function(e){let{spec:t,scheme:n,contextUrl:r=""}=e;const o=Ne.parse(r),a=Array.isArray(t.schemes)?t.schemes[0]:null,i=n||a||en(o.protocol)||"http",s=t.host||o.host||"",l=t.basePath||"";let u;u=i&&s?`${i}://${s+l}`:l;return"/"===u[u.length-1]?u.slice(0,-1):u}(e)}({spec:t,scheme:o,contextUrl:u,server:p,serverVariables:f,pathName:x,method:E}),!n)return delete v.cookies,v;v.url+=x,v.method=`${E}`.toUpperCase(),m=m||{};const _=t.paths[x]||{};r&&(v.headers.accept=r);const S=Zt([].concat(Jt(w.parameters)).concat(Jt(_.parameters)));S.forEach((e=>{const n=g[e.in];let r;if("body"===e.in&&e.schema&&e.schema.properties&&(r=m),r=e&&e.name&&m[e.name],void 0===r?r=e&&e.name&&m[`${e.in}.${e.name}`]:Gt(e.name,S).length>1&&console.warn(`Parameter '${e.name}' is ambiguous because the defined spec has more than one parameter with the name: '${e.name}' and the passed-in parameter values did not define an 'in' value.`),null!==r){if(void 0!==e.default&&void 0===r&&(r=e.default),void 0===r&&e.required&&!e.allowEmptyValue)throw new Error(`Required parameter ${e.name} is not provided`);if(y&&e.schema&&"object"===e.schema.type&&"string"==typeof r)try{r=JSON.parse(r)}catch(e){throw new Error("Could not parse object parameter value string as JSON")}n&&n({req:v,parameter:e,value:r,operation:w,spec:t})}}));const A=s()(s()({},e),{},{operation:w});if(v=y?$t(A,v):Vt(A,v),v.cookies&&Object.keys(v.cookies).length){const e=Object.keys(v.cookies).reduce(((e,t)=>{const n=v.cookies[t];return e+(e?"&":"")+It.serialize(t,n)}),"");v.headers.Cookie=e}return v.cookies&&delete v.cookies,R(v),v}const en=e=>e?e.replace(/\W/g,""):null;const tn=async function(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const{returnEntireTree:r,baseDoc:o,requestInterceptor:a,responseInterceptor:i,parameterMacro:l,modelPropertyMacro:u,useCircularStructures:c,strategies:p}=n,f={spec:e,pathDiscriminator:t,baseDoc:o,requestInterceptor:a,responseInterceptor:i,parameterMacro:l,modelPropertyMacro:u,useCircularStructures:c,strategies:p},h=p.find((e=>e.match(f))).normalize(f),d=await St(s()(s()({},f),{},{spec:h,allowMetaPatches:!0,skipNormalization:!0}));return!r&&Array.isArray(t)&&t.length&&(d.spec=jt()(d.spec,t)||null),d},nn=(e=>async function(t,n){let r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const o=s()(s()({},e),r);return tn(t,n,o)})({strategies:[_t,xt,bt]});var rn=n(34852);function on(e){let{configs:t,getConfigs:n}=e;return{fn:{fetch:(r=_,o=t.preFetch,a=t.postFetch,a=a||(e=>e),o=o||(e=>e),e=>("string"==typeof e&&(e={url:e}),x.mergeInQueryOrForm(e),e=o(e),a(r(e)))),buildRequest:Xt,execute:Qt,resolve:St,resolveSubtree:function(e,t,r){if(void 0===r){const e=n();r={modelPropertyMacro:e.modelPropertyMacro,parameterMacro:e.parameterMacro,requestInterceptor:e.requestInterceptor,responseInterceptor:e.responseInterceptor}}for(var o=arguments.length,a=new Array(o>3?o-3:0),i=3;i{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(90242);function o(){return{fn:{shallowEqualKeys:r.be}}}},48347:(e,t,n)=>{"use strict";n.r(t),n.d(t,{getDisplayName:()=>r});const r=e=>e.displayName||e.name||"Component"},73420:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>u});var r=n(35627),o=n.n(r),a=n(90242),i=n(11092),s=n(48347),l=n(60314);const u=e=>{let{getComponents:t,getStore:n,getSystem:r}=e;const u=(c=(0,i.getComponent)(r,n,t),(0,a.HP)(c,(function(){for(var e=arguments.length,t=new Array(e),n=0;n(0,l.Z)(e,(function(){for(var e=arguments.length,t=new Array(e),n=0;n{"use strict";n.r(t),n.d(t,{getComponent:()=>Q,render:()=>Y,withMappedContainer:()=>Z});var r=n(23101),o=n.n(r),a=n(28222),i=n.n(a),s=n(67294),l=n(73935),u=n(97779),c=n(61688),p=n(52798);let f=function(e){e()};const h=()=>f,d=(0,s.createContext)(null);let m=null;var g=n(87462),y=n(63366),v=n(8679),b=n.n(v),w=n(59864);const E=["initMapStateToProps","initMapDispatchToProps","initMergeProps"];function x(e,t,n,r,{areStatesEqual:o,areOwnPropsEqual:a,areStatePropsEqual:i}){let s,l,u,c,p,f=!1;function h(f,h){const d=!a(h,l),m=!o(f,s,h,l);return s=f,l=h,d&&m?(u=e(s,l),t.dependsOnOwnProps&&(c=t(r,l)),p=n(u,c,l),p):d?(e.dependsOnOwnProps&&(u=e(s,l)),t.dependsOnOwnProps&&(c=t(r,l)),p=n(u,c,l),p):m?function(){const t=e(s,l),r=!i(t,u);return u=t,r&&(p=n(u,c,l)),p}():p}return function(o,a){return f?h(o,a):(s=o,l=a,u=e(s,l),c=t(r,l),p=n(u,c,l),f=!0,p)}}function _(e){return function(t){const n=e(t);function r(){return n}return r.dependsOnOwnProps=!1,r}}function S(e){return e.dependsOnOwnProps?Boolean(e.dependsOnOwnProps):1!==e.length}function A(e,t){return function(t,{displayName:n}){const r=function(e,t){return r.dependsOnOwnProps?r.mapToProps(e,t):r.mapToProps(e,void 0)};return r.dependsOnOwnProps=!0,r.mapToProps=function(t,n){r.mapToProps=e,r.dependsOnOwnProps=S(e);let o=r(t,n);return"function"==typeof o&&(r.mapToProps=o,r.dependsOnOwnProps=S(o),o=r(t,n)),o},r}}function C(e,t){return(n,r)=>{throw new Error(`Invalid value of type ${typeof e} for ${t} argument when connecting component ${r.wrappedComponentName}.`)}}function k(e,t,n){return(0,g.Z)({},n,e,t)}const O={notify(){},get:()=>[]};function j(e,t){let n,r=O;function o(){i.onStateChange&&i.onStateChange()}function a(){n||(n=t?t.addNestedSub(o):e.subscribe(o),r=function(){const e=h();let t=null,n=null;return{clear(){t=null,n=null},notify(){e((()=>{let e=t;for(;e;)e.callback(),e=e.next}))},get(){let e=[],n=t;for(;n;)e.push(n),n=n.next;return e},subscribe(e){let r=!0,o=n={callback:e,next:null,prev:n};return o.prev?o.prev.next=o:t=o,function(){r&&null!==t&&(r=!1,o.next?o.next.prev=o.prev:n=o.prev,o.prev?o.prev.next=o.next:t=o.next)}}}}())}const i={addNestedSub:function(e){return a(),r.subscribe(e)},notifyNestedSubs:function(){r.notify()},handleChangeWrapper:o,isSubscribed:function(){return Boolean(n)},trySubscribe:a,tryUnsubscribe:function(){n&&(n(),n=void 0,r.clear(),r=O)},getListeners:()=>r};return i}const I=!("undefined"==typeof window||void 0===window.document||void 0===window.document.createElement)?s.useLayoutEffect:s.useEffect;function T(e,t){return e===t?0!==e||0!==t||1/e==1/t:e!=e&&t!=t}function N(e,t){if(T(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;const n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(let r=0;r{throw new Error("uSES not initialized!")};const M=[null,null];function D(e,t,n,r,o,a){e.current=r,n.current=!1,o.current&&(o.current=null,a())}function L(e,t){return e===t}const B=function(e,t,n,{pure:r,areStatesEqual:o=L,areOwnPropsEqual:a=N,areStatePropsEqual:i=N,areMergedPropsEqual:l=N,forwardRef:u=!1,context:c=d}={}){const p=c,f=function(e){return e?"function"==typeof e?A(e):C(e,"mapStateToProps"):_((()=>({})))}(e),h=function(e){return e&&"object"==typeof e?_((t=>function(e,t){const n={};for(const r in e){const o=e[r];"function"==typeof o&&(n[r]=(...e)=>t(o(...e)))}return n}(e,t))):e?"function"==typeof e?A(e):C(e,"mapDispatchToProps"):_((e=>({dispatch:e})))}(t),m=function(e){return e?"function"==typeof e?function(e){return function(t,{displayName:n,areMergedPropsEqual:r}){let o,a=!1;return function(t,n,i){const s=e(t,n,i);return a?r(s,o)||(o=s):(a=!0,o=s),o}}}(e):C(e,"mergeProps"):()=>k}(n),v=Boolean(e);return e=>{const t=e.displayName||e.name||"Component",n=`Connect(${t})`,r={shouldHandleStateChanges:v,displayName:n,wrappedComponentName:t,WrappedComponent:e,initMapStateToProps:f,initMapDispatchToProps:h,initMergeProps:m,areStatesEqual:o,areStatePropsEqual:i,areOwnPropsEqual:a,areMergedPropsEqual:l};function c(t){const[n,o,a]=(0,s.useMemo)((()=>{const{reactReduxForwardedRef:e}=t,n=(0,y.Z)(t,P);return[t.context,e,n]}),[t]),i=(0,s.useMemo)((()=>n&&n.Consumer&&(0,w.isContextConsumer)(s.createElement(n.Consumer,null))?n:p),[n,p]),l=(0,s.useContext)(i),u=Boolean(t.store)&&Boolean(t.store.getState)&&Boolean(t.store.dispatch),c=Boolean(l)&&Boolean(l.store);const f=u?t.store:l.store,h=c?l.getServerState:f.getState,d=(0,s.useMemo)((()=>function(e,t){let{initMapStateToProps:n,initMapDispatchToProps:r,initMergeProps:o}=t,a=(0,y.Z)(t,E);return x(n(e,a),r(e,a),o(e,a),e,a)}(f.dispatch,r)),[f]),[m,b]=(0,s.useMemo)((()=>{if(!v)return M;const e=j(f,u?void 0:l.subscription),t=e.notifyNestedSubs.bind(e);return[e,t]}),[f,u,l]),_=(0,s.useMemo)((()=>u?l:(0,g.Z)({},l,{subscription:m})),[u,l,m]),S=(0,s.useRef)(),A=(0,s.useRef)(a),C=(0,s.useRef)(),k=(0,s.useRef)(!1),O=((0,s.useRef)(!1),(0,s.useRef)(!1)),T=(0,s.useRef)();I((()=>(O.current=!0,()=>{O.current=!1})),[]);const N=(0,s.useMemo)((()=>()=>C.current&&a===A.current?C.current:d(f.getState(),a)),[f,a]),L=(0,s.useMemo)((()=>e=>m?function(e,t,n,r,o,a,i,s,l,u,c){if(!e)return()=>{};let p=!1,f=null;const h=()=>{if(p||!s.current)return;const e=t.getState();let n,h;try{n=r(e,o.current)}catch(e){h=e,f=e}h||(f=null),n===a.current?i.current||u():(a.current=n,l.current=n,i.current=!0,c())};return n.onStateChange=h,n.trySubscribe(),h(),()=>{if(p=!0,n.tryUnsubscribe(),n.onStateChange=null,f)throw f}}(v,f,m,d,A,S,k,O,C,b,e):()=>{}),[m]);var B,F,U;let z;B=D,F=[A,S,k,a,C,b],I((()=>B(...F)),U);try{z=R(L,N,h?()=>d(h(),a):N)}catch(e){throw T.current&&(e.message+=`\nThe error may be correlated with this previous error:\n${T.current.stack}\n\n`),e}I((()=>{T.current=void 0,C.current=void 0,S.current=z}));const q=(0,s.useMemo)((()=>s.createElement(e,(0,g.Z)({},z,{ref:o}))),[o,e,z]);return(0,s.useMemo)((()=>v?s.createElement(i.Provider,{value:_},q):q),[i,q,_])}const d=s.memo(c);if(d.WrappedComponent=e,d.displayName=c.displayName=n,u){const t=s.forwardRef((function(e,t){return s.createElement(d,(0,g.Z)({},e,{reactReduxForwardedRef:t}))}));return t.displayName=n,t.WrappedComponent=e,b()(t,e)}return b()(d,e)}};const F=function({store:e,context:t,children:n,serverState:r}){const o=(0,s.useMemo)((()=>{const t=j(e);return{store:e,subscription:t,getServerState:r?()=>r:void 0}}),[e,r]),a=(0,s.useMemo)((()=>e.getState()),[e]);I((()=>{const{subscription:t}=o;return t.onStateChange=t.notifyNestedSubs,t.trySubscribe(),a!==e.getState()&&t.notifyNestedSubs(),()=>{t.tryUnsubscribe(),t.onStateChange=void 0}}),[o,a]);const i=t||d;return s.createElement(i.Provider,{value:o},n)};var U,z;U=p.useSyncExternalStoreWithSelector,m=U,(e=>{R=e})(c.useSyncExternalStore),z=l.unstable_batchedUpdates,f=z;var q=n(57557),$=n.n(q),V=n(6557),W=n.n(V);const H=e=>t=>{const{fn:n}=e();class r extends s.Component{render(){return s.createElement(t,o()({},e(),this.props,this.context))}}return r.displayName=`WithSystem(${n.getDisplayName(t)})`,r},J=(e,t)=>n=>{const{fn:r}=e();class a extends s.Component{render(){return s.createElement(F,{store:t},s.createElement(n,o()({},this.props,this.context)))}}return a.displayName=`WithRoot(${r.getDisplayName(n)})`,a},K=(e,t,n)=>(0,u.qC)(n?J(e,n):W(),B(((n,r)=>{var o;const a={...r,...e()},i=(null===(o=t.prototype)||void 0===o?void 0:o.mapStateToProps)||(e=>({state:e}));return i(n,a)})),H(e))(t),G=(e,t,n,r)=>{for(const o in t){const a=t[o];"function"==typeof a&&a(n[o],r[o],e())}},Z=(e,t,n)=>(t,r)=>{const{fn:o}=e(),a=n(t,"root");class l extends s.Component{constructor(t,n){super(t,n),G(e,r,t,{})}UNSAFE_componentWillReceiveProps(t){G(e,r,t,this.props)}render(){const e=$()(this.props,r?i()(r):[]);return s.createElement(a,e)}}return l.displayName=`WithMappedContainer(${o.getDisplayName(a)})`,l},Y=(e,t,n,r)=>o=>{const a=n(e,t,r)("App","root");l.render(s.createElement(a,null),o)},Q=(e,t,n)=>function(r,o){let a=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if("string"!=typeof r)throw new TypeError("Need a string, to fetch a component. Was given a "+typeof r);const i=n(r);return i?o?"root"===o?K(e,i,t()):K(e,i):i:(a.failSilently||e().log.warn("Could not find component:",r),null)}},33424:(e,t,n)=>{"use strict";n.d(t,{d3:()=>D,C2:()=>ee});var r=n(28222),o=n.n(r),a=n(58118),i=n.n(a),s=n(63366);function l(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0;return function(e){if(0===e.length||1===e.length)return e;var t,n,r=e.join(".");return m[r]||(m[r]=0===(n=(t=e).length)||1===n?t:2===n?[t[0],t[1],"".concat(t[0],".").concat(t[1]),"".concat(t[1],".").concat(t[0])]:3===n?[t[0],t[1],t[2],"".concat(t[0],".").concat(t[1]),"".concat(t[0],".").concat(t[2]),"".concat(t[1],".").concat(t[0]),"".concat(t[1],".").concat(t[2]),"".concat(t[2],".").concat(t[0]),"".concat(t[2],".").concat(t[1]),"".concat(t[0],".").concat(t[1],".").concat(t[2]),"".concat(t[0],".").concat(t[2],".").concat(t[1]),"".concat(t[1],".").concat(t[0],".").concat(t[2]),"".concat(t[1],".").concat(t[2],".").concat(t[0]),"".concat(t[2],".").concat(t[0],".").concat(t[1]),"".concat(t[2],".").concat(t[1],".").concat(t[0])]:n>=4?[t[0],t[1],t[2],t[3],"".concat(t[0],".").concat(t[1]),"".concat(t[0],".").concat(t[2]),"".concat(t[0],".").concat(t[3]),"".concat(t[1],".").concat(t[0]),"".concat(t[1],".").concat(t[2]),"".concat(t[1],".").concat(t[3]),"".concat(t[2],".").concat(t[0]),"".concat(t[2],".").concat(t[1]),"".concat(t[2],".").concat(t[3]),"".concat(t[3],".").concat(t[0]),"".concat(t[3],".").concat(t[1]),"".concat(t[3],".").concat(t[2]),"".concat(t[0],".").concat(t[1],".").concat(t[2]),"".concat(t[0],".").concat(t[1],".").concat(t[3]),"".concat(t[0],".").concat(t[2],".").concat(t[1]),"".concat(t[0],".").concat(t[2],".").concat(t[3]),"".concat(t[0],".").concat(t[3],".").concat(t[1]),"".concat(t[0],".").concat(t[3],".").concat(t[2]),"".concat(t[1],".").concat(t[0],".").concat(t[2]),"".concat(t[1],".").concat(t[0],".").concat(t[3]),"".concat(t[1],".").concat(t[2],".").concat(t[0]),"".concat(t[1],".").concat(t[2],".").concat(t[3]),"".concat(t[1],".").concat(t[3],".").concat(t[0]),"".concat(t[1],".").concat(t[3],".").concat(t[2]),"".concat(t[2],".").concat(t[0],".").concat(t[1]),"".concat(t[2],".").concat(t[0],".").concat(t[3]),"".concat(t[2],".").concat(t[1],".").concat(t[0]),"".concat(t[2],".").concat(t[1],".").concat(t[3]),"".concat(t[2],".").concat(t[3],".").concat(t[0]),"".concat(t[2],".").concat(t[3],".").concat(t[1]),"".concat(t[3],".").concat(t[0],".").concat(t[1]),"".concat(t[3],".").concat(t[0],".").concat(t[2]),"".concat(t[3],".").concat(t[1],".").concat(t[0]),"".concat(t[3],".").concat(t[1],".").concat(t[2]),"".concat(t[3],".").concat(t[2],".").concat(t[0]),"".concat(t[3],".").concat(t[2],".").concat(t[1]),"".concat(t[0],".").concat(t[1],".").concat(t[2],".").concat(t[3]),"".concat(t[0],".").concat(t[1],".").concat(t[3],".").concat(t[2]),"".concat(t[0],".").concat(t[2],".").concat(t[1],".").concat(t[3]),"".concat(t[0],".").concat(t[2],".").concat(t[3],".").concat(t[1]),"".concat(t[0],".").concat(t[3],".").concat(t[1],".").concat(t[2]),"".concat(t[0],".").concat(t[3],".").concat(t[2],".").concat(t[1]),"".concat(t[1],".").concat(t[0],".").concat(t[2],".").concat(t[3]),"".concat(t[1],".").concat(t[0],".").concat(t[3],".").concat(t[2]),"".concat(t[1],".").concat(t[2],".").concat(t[0],".").concat(t[3]),"".concat(t[1],".").concat(t[2],".").concat(t[3],".").concat(t[0]),"".concat(t[1],".").concat(t[3],".").concat(t[0],".").concat(t[2]),"".concat(t[1],".").concat(t[3],".").concat(t[2],".").concat(t[0]),"".concat(t[2],".").concat(t[0],".").concat(t[1],".").concat(t[3]),"".concat(t[2],".").concat(t[0],".").concat(t[3],".").concat(t[1]),"".concat(t[2],".").concat(t[1],".").concat(t[0],".").concat(t[3]),"".concat(t[2],".").concat(t[1],".").concat(t[3],".").concat(t[0]),"".concat(t[2],".").concat(t[3],".").concat(t[0],".").concat(t[1]),"".concat(t[2],".").concat(t[3],".").concat(t[1],".").concat(t[0]),"".concat(t[3],".").concat(t[0],".").concat(t[1],".").concat(t[2]),"".concat(t[3],".").concat(t[0],".").concat(t[2],".").concat(t[1]),"".concat(t[3],".").concat(t[1],".").concat(t[0],".").concat(t[2]),"".concat(t[3],".").concat(t[1],".").concat(t[2],".").concat(t[0]),"".concat(t[3],".").concat(t[2],".").concat(t[0],".").concat(t[1]),"".concat(t[3],".").concat(t[2],".").concat(t[1],".").concat(t[0])]:void 0),m[r]}(e.filter((function(e){return"token"!==e}))).reduce((function(e,t){return d(d({},e),n[t])}),t)}function y(e){return e.join(" ")}function v(e){var t=e.node,n=e.stylesheet,r=e.style,o=void 0===r?{}:r,a=e.useInlineStyles,i=e.key,s=t.properties,l=t.type,u=t.tagName,c=t.value;if("text"===l)return c;if(u){var h,m=function(e,t){var n=0;return function(r){return n+=1,r.map((function(r,o){return v({node:r,stylesheet:e,useInlineStyles:t,key:"code-segment-".concat(n,"-").concat(o)})}))}}(n,a);if(a){var b=Object.keys(n).reduce((function(e,t){return t.split(".").forEach((function(t){e.includes(t)||e.push(t)})),e}),[]),w=s.className&&s.className.includes("token")?["token"]:[],E=s.className&&w.concat(s.className.filter((function(e){return!b.includes(e)})));h=d(d({},s),{},{className:y(E)||void 0,style:g(s.className,Object.assign({},s.style,o),n)})}else h=d(d({},s),{},{className:y(s.className)});var x=m(t.children);return p.createElement(u,(0,f.Z)({key:i},h),x)}}const b=function(e,t){return-1!==e.listLanguages().indexOf(t)};var w=["language","children","style","customStyle","codeTagProps","useInlineStyles","showLineNumbers","showInlineLineNumbers","startingLineNumber","lineNumberContainerStyle","lineNumberStyle","wrapLines","wrapLongLines","lineProps","renderer","PreTag","CodeTag","code","astGenerator"];function E(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function x(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:[],n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],r=0;r2&&void 0!==arguments[2]?arguments[2]:[];return t||u.length>0?function(e,t){return k({children:e,lineNumber:t,lineNumberStyle:s,largestLineNumber:i,showInlineLineNumbers:o,lineProps:n,className:arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],showLineNumbers:r,wrapLongLines:l})}(e,a,u):function(e,t){if(r&&t&&o){var n=C(s,t,i);e.unshift(A(t,n))}return e}(e,a)}for(var m=function(){var e=c[h],t=e.children[0].value;if(t.match(_)){var n=t.split("\n");n.forEach((function(t,o){var i=r&&p.length+a,s={type:"text",value:"".concat(t,"\n")};if(0===o){var l=d(c.slice(f+1,h).concat(k({children:[s],className:e.properties.className})),i);p.push(l)}else if(o===n.length-1){var u=c[h+1]&&c[h+1].children&&c[h+1].children[0],m={type:"text",value:"".concat(t)};if(u){var g=k({children:[m],className:e.properties.className});c.splice(h+1,0,g)}else{var y=d([m],i,e.properties.className);p.push(y)}}else{var v=d([s],i,e.properties.className);p.push(v)}})),f=h}h++};h=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}(e,w);$=$||N;var W=d?p.createElement(S,{containerStyle:E,codeStyle:u.style||{},numberStyle:A,startingLineNumber:v,codeString:q}):null,H=o.hljs||o['pre[class*="language-"]']||{backgroundColor:"#fff"},J=T($)?"hljs":"prismjs",K=f?Object.assign({},V,{style:Object.assign({},H,i)}):Object.assign({},V,{className:V.className?"".concat(J," ").concat(V.className):J,style:Object.assign({},i)});if(u.style=x(x({},u.style),{},O?{whiteSpace:"pre-wrap"}:{whiteSpace:"pre"}),!$)return p.createElement(B,K,W,p.createElement(U,u,q));(void 0===C&&D||O)&&(C=!0),D=D||I;var G=[{type:"text",value:q}],Z=function(e){var t=e.astGenerator,n=e.language,r=e.code,o=e.defaultCodeValue;if(T(t)){var a=b(t,n);return"text"===n?{value:o,language:"text"}:a?t.highlight(n,r):t.highlightAuto(r)}try{return n&&"text"!==n?{value:t.highlight(r,n)}:{value:o}}catch(e){return{value:o}}}({astGenerator:$,language:t,code:q,defaultCodeValue:G});null===Z.language&&(Z.value=G);var Y=j(Z,C,M,d,g,v,Z.value.length+v,A,O);return p.createElement(B,K,p.createElement(U,u,!g&&W,D({rows:Y,stylesheet:o,useInlineStyles:f})))});M.registerLanguage=R.registerLanguage;const D=M;var L=n(96344);const B=n.n(L)();var F=n(82026);const U=n.n(F)();var z=n(42157);const q=n.n(z)();var $=n(61519);const V=n.n($)();var W=n(54587);const H=n.n(W)();var J=n(30786);const K=n.n(J)();var G=n(66336);const Z=n.n(G)(),Y={hljs:{display:"block",overflowX:"auto",padding:"0.5em",background:"#333",color:"white"},"hljs-name":{fontWeight:"bold"},"hljs-strong":{fontWeight:"bold"},"hljs-code":{fontStyle:"italic",color:"#888"},"hljs-emphasis":{fontStyle:"italic"},"hljs-tag":{color:"#62c8f3"},"hljs-variable":{color:"#ade5fc"},"hljs-template-variable":{color:"#ade5fc"},"hljs-selector-id":{color:"#ade5fc"},"hljs-selector-class":{color:"#ade5fc"},"hljs-string":{color:"#a2fca2"},"hljs-bullet":{color:"#d36363"},"hljs-type":{color:"#ffa"},"hljs-title":{color:"#ffa"},"hljs-section":{color:"#ffa"},"hljs-attribute":{color:"#ffa"},"hljs-quote":{color:"#ffa"},"hljs-built_in":{color:"#ffa"},"hljs-builtin-name":{color:"#ffa"},"hljs-number":{color:"#d36363"},"hljs-symbol":{color:"#d36363"},"hljs-keyword":{color:"#fcc28c"},"hljs-selector-tag":{color:"#fcc28c"},"hljs-literal":{color:"#fcc28c"},"hljs-comment":{color:"#888"},"hljs-deletion":{color:"#333",backgroundColor:"#fc9b9b"},"hljs-regexp":{color:"#c6b4f0"},"hljs-link":{color:"#c6b4f0"},"hljs-meta":{color:"#fc9b9b"},"hljs-addition":{backgroundColor:"#a2fca2",color:"#333"}};D.registerLanguage("json",U),D.registerLanguage("js",B),D.registerLanguage("xml",q),D.registerLanguage("yaml",H),D.registerLanguage("http",K),D.registerLanguage("bash",V),D.registerLanguage("powershell",Z),D.registerLanguage("javascript",B);const Q={agate:Y,arta:{hljs:{display:"block",overflowX:"auto",padding:"0.5em",background:"#222",color:"#aaa"},"hljs-subst":{color:"#aaa"},"hljs-section":{color:"#fff",fontWeight:"bold"},"hljs-comment":{color:"#444"},"hljs-quote":{color:"#444"},"hljs-meta":{color:"#444"},"hljs-string":{color:"#ffcc33"},"hljs-symbol":{color:"#ffcc33"},"hljs-bullet":{color:"#ffcc33"},"hljs-regexp":{color:"#ffcc33"},"hljs-number":{color:"#00cc66"},"hljs-addition":{color:"#00cc66"},"hljs-built_in":{color:"#32aaee"},"hljs-builtin-name":{color:"#32aaee"},"hljs-literal":{color:"#32aaee"},"hljs-type":{color:"#32aaee"},"hljs-template-variable":{color:"#32aaee"},"hljs-attribute":{color:"#32aaee"},"hljs-link":{color:"#32aaee"},"hljs-keyword":{color:"#6644aa"},"hljs-selector-tag":{color:"#6644aa"},"hljs-name":{color:"#6644aa"},"hljs-selector-id":{color:"#6644aa"},"hljs-selector-class":{color:"#6644aa"},"hljs-title":{color:"#bb1166"},"hljs-variable":{color:"#bb1166"},"hljs-deletion":{color:"#bb1166"},"hljs-template-tag":{color:"#bb1166"},"hljs-doctag":{fontWeight:"bold"},"hljs-strong":{fontWeight:"bold"},"hljs-emphasis":{fontStyle:"italic"}},monokai:{hljs:{display:"block",overflowX:"auto",padding:"0.5em",background:"#272822",color:"#ddd"},"hljs-tag":{color:"#f92672"},"hljs-keyword":{color:"#f92672",fontWeight:"bold"},"hljs-selector-tag":{color:"#f92672",fontWeight:"bold"},"hljs-literal":{color:"#f92672",fontWeight:"bold"},"hljs-strong":{color:"#f92672"},"hljs-name":{color:"#f92672"},"hljs-code":{color:"#66d9ef"},"hljs-class .hljs-title":{color:"white"},"hljs-attribute":{color:"#bf79db"},"hljs-symbol":{color:"#bf79db"},"hljs-regexp":{color:"#bf79db"},"hljs-link":{color:"#bf79db"},"hljs-string":{color:"#a6e22e"},"hljs-bullet":{color:"#a6e22e"},"hljs-subst":{color:"#a6e22e"},"hljs-title":{color:"#a6e22e",fontWeight:"bold"},"hljs-section":{color:"#a6e22e",fontWeight:"bold"},"hljs-emphasis":{color:"#a6e22e"},"hljs-type":{color:"#a6e22e",fontWeight:"bold"},"hljs-built_in":{color:"#a6e22e"},"hljs-builtin-name":{color:"#a6e22e"},"hljs-selector-attr":{color:"#a6e22e"},"hljs-selector-pseudo":{color:"#a6e22e"},"hljs-addition":{color:"#a6e22e"},"hljs-variable":{color:"#a6e22e"},"hljs-template-tag":{color:"#a6e22e"},"hljs-template-variable":{color:"#a6e22e"},"hljs-comment":{color:"#75715e"},"hljs-quote":{color:"#75715e"},"hljs-deletion":{color:"#75715e"},"hljs-meta":{color:"#75715e"},"hljs-doctag":{fontWeight:"bold"},"hljs-selector-id":{fontWeight:"bold"}},nord:{hljs:{display:"block",overflowX:"auto",padding:"0.5em",background:"#2E3440",color:"#D8DEE9"},"hljs-subst":{color:"#D8DEE9"},"hljs-selector-tag":{color:"#81A1C1"},"hljs-selector-id":{color:"#8FBCBB",fontWeight:"bold"},"hljs-selector-class":{color:"#8FBCBB"},"hljs-selector-attr":{color:"#8FBCBB"},"hljs-selector-pseudo":{color:"#88C0D0"},"hljs-addition":{backgroundColor:"rgba(163, 190, 140, 0.5)"},"hljs-deletion":{backgroundColor:"rgba(191, 97, 106, 0.5)"},"hljs-built_in":{color:"#8FBCBB"},"hljs-type":{color:"#8FBCBB"},"hljs-class":{color:"#8FBCBB"},"hljs-function":{color:"#88C0D0"},"hljs-function > .hljs-title":{color:"#88C0D0"},"hljs-keyword":{color:"#81A1C1"},"hljs-literal":{color:"#81A1C1"},"hljs-symbol":{color:"#81A1C1"},"hljs-number":{color:"#B48EAD"},"hljs-regexp":{color:"#EBCB8B"},"hljs-string":{color:"#A3BE8C"},"hljs-title":{color:"#8FBCBB"},"hljs-params":{color:"#D8DEE9"},"hljs-bullet":{color:"#81A1C1"},"hljs-code":{color:"#8FBCBB"},"hljs-emphasis":{fontStyle:"italic"},"hljs-formula":{color:"#8FBCBB"},"hljs-strong":{fontWeight:"bold"},"hljs-link:hover":{textDecoration:"underline"},"hljs-quote":{color:"#4C566A"},"hljs-comment":{color:"#4C566A"},"hljs-doctag":{color:"#8FBCBB"},"hljs-meta":{color:"#5E81AC"},"hljs-meta-keyword":{color:"#5E81AC"},"hljs-meta-string":{color:"#A3BE8C"},"hljs-attr":{color:"#8FBCBB"},"hljs-attribute":{color:"#D8DEE9"},"hljs-builtin-name":{color:"#81A1C1"},"hljs-name":{color:"#81A1C1"},"hljs-section":{color:"#88C0D0"},"hljs-tag":{color:"#81A1C1"},"hljs-variable":{color:"#D8DEE9"},"hljs-template-variable":{color:"#D8DEE9"},"hljs-template-tag":{color:"#5E81AC"},"abnf .hljs-attribute":{color:"#88C0D0"},"abnf .hljs-symbol":{color:"#EBCB8B"},"apache .hljs-attribute":{color:"#88C0D0"},"apache .hljs-section":{color:"#81A1C1"},"arduino .hljs-built_in":{color:"#88C0D0"},"aspectj .hljs-meta":{color:"#D08770"},"aspectj > .hljs-title":{color:"#88C0D0"},"bnf .hljs-attribute":{color:"#8FBCBB"},"clojure .hljs-name":{color:"#88C0D0"},"clojure .hljs-symbol":{color:"#EBCB8B"},"coq .hljs-built_in":{color:"#88C0D0"},"cpp .hljs-meta-string":{color:"#8FBCBB"},"css .hljs-built_in":{color:"#88C0D0"},"css .hljs-keyword":{color:"#D08770"},"diff .hljs-meta":{color:"#8FBCBB"},"ebnf .hljs-attribute":{color:"#8FBCBB"},"glsl .hljs-built_in":{color:"#88C0D0"},"groovy .hljs-meta:not(:first-child)":{color:"#D08770"},"haxe .hljs-meta":{color:"#D08770"},"java .hljs-meta":{color:"#D08770"},"ldif .hljs-attribute":{color:"#8FBCBB"},"lisp .hljs-name":{color:"#88C0D0"},"lua .hljs-built_in":{color:"#88C0D0"},"moonscript .hljs-built_in":{color:"#88C0D0"},"nginx .hljs-attribute":{color:"#88C0D0"},"nginx .hljs-section":{color:"#5E81AC"},"pf .hljs-built_in":{color:"#88C0D0"},"processing .hljs-built_in":{color:"#88C0D0"},"scss .hljs-keyword":{color:"#81A1C1"},"stylus .hljs-keyword":{color:"#81A1C1"},"swift .hljs-meta":{color:"#D08770"},"vim .hljs-built_in":{color:"#88C0D0",fontStyle:"italic"},"yaml .hljs-meta":{color:"#D08770"}},obsidian:{hljs:{display:"block",overflowX:"auto",padding:"0.5em",background:"#282b2e",color:"#e0e2e4"},"hljs-keyword":{color:"#93c763",fontWeight:"bold"},"hljs-selector-tag":{color:"#93c763",fontWeight:"bold"},"hljs-literal":{color:"#93c763",fontWeight:"bold"},"hljs-selector-id":{color:"#93c763"},"hljs-number":{color:"#ffcd22"},"hljs-attribute":{color:"#668bb0"},"hljs-code":{color:"white"},"hljs-class .hljs-title":{color:"white"},"hljs-section":{color:"white",fontWeight:"bold"},"hljs-regexp":{color:"#d39745"},"hljs-link":{color:"#d39745"},"hljs-meta":{color:"#557182"},"hljs-tag":{color:"#8cbbad"},"hljs-name":{color:"#8cbbad",fontWeight:"bold"},"hljs-bullet":{color:"#8cbbad"},"hljs-subst":{color:"#8cbbad"},"hljs-emphasis":{color:"#8cbbad"},"hljs-type":{color:"#8cbbad",fontWeight:"bold"},"hljs-built_in":{color:"#8cbbad"},"hljs-selector-attr":{color:"#8cbbad"},"hljs-selector-pseudo":{color:"#8cbbad"},"hljs-addition":{color:"#8cbbad"},"hljs-variable":{color:"#8cbbad"},"hljs-template-tag":{color:"#8cbbad"},"hljs-template-variable":{color:"#8cbbad"},"hljs-string":{color:"#ec7600"},"hljs-symbol":{color:"#ec7600"},"hljs-comment":{color:"#818e96"},"hljs-quote":{color:"#818e96"},"hljs-deletion":{color:"#818e96"},"hljs-selector-class":{color:"#A082BD"},"hljs-doctag":{fontWeight:"bold"},"hljs-title":{fontWeight:"bold"},"hljs-strong":{fontWeight:"bold"}},"tomorrow-night":{"hljs-comment":{color:"#969896"},"hljs-quote":{color:"#969896"},"hljs-variable":{color:"#cc6666"},"hljs-template-variable":{color:"#cc6666"},"hljs-tag":{color:"#cc6666"},"hljs-name":{color:"#cc6666"},"hljs-selector-id":{color:"#cc6666"},"hljs-selector-class":{color:"#cc6666"},"hljs-regexp":{color:"#cc6666"},"hljs-deletion":{color:"#cc6666"},"hljs-number":{color:"#de935f"},"hljs-built_in":{color:"#de935f"},"hljs-builtin-name":{color:"#de935f"},"hljs-literal":{color:"#de935f"},"hljs-type":{color:"#de935f"},"hljs-params":{color:"#de935f"},"hljs-meta":{color:"#de935f"},"hljs-link":{color:"#de935f"},"hljs-attribute":{color:"#f0c674"},"hljs-string":{color:"#b5bd68"},"hljs-symbol":{color:"#b5bd68"},"hljs-bullet":{color:"#b5bd68"},"hljs-addition":{color:"#b5bd68"},"hljs-title":{color:"#81a2be"},"hljs-section":{color:"#81a2be"},"hljs-keyword":{color:"#b294bb"},"hljs-selector-tag":{color:"#b294bb"},hljs:{display:"block",overflowX:"auto",background:"#1d1f21",color:"#c5c8c6",padding:"0.5em"},"hljs-emphasis":{fontStyle:"italic"},"hljs-strong":{fontWeight:"bold"}}},X=o()(Q),ee=e=>i()(X).call(X,e)?Q[e]:(console.warn(`Request style '${e}' is not available, returning default instead`),Y)},90242:(e,t,n)=>{"use strict";n.d(t,{AF:()=>he,Ay:()=>be,D$:()=>ut,DR:()=>Se,GZ:()=>Qe,HP:()=>ve,Ik:()=>qe,J6:()=>ot,Kn:()=>me,LQ:()=>de,Nm:()=>et,O2:()=>mt,Pz:()=>lt,Q2:()=>we,QG:()=>nt,UG:()=>Ge,Uj:()=>ft,V9:()=>ct,Wl:()=>ge,XV:()=>st,Xb:()=>ht,Zl:()=>Ae,_5:()=>Ee,be:()=>Xe,cz:()=>pt,gp:()=>_e,hW:()=>tt,iQ:()=>xe,kJ:()=>ye,mz:()=>pe,nX:()=>at,oG:()=>fe,oJ:()=>rt,po:()=>it,r3:()=>Ze,wh:()=>Ye,xi:()=>Ke});var r=n(58309),o=n.n(r),a=n(97606),i=n.n(a),s=n(74386),l=n.n(s),u=n(86),c=n.n(u),p=n(14418),f=n.n(p),h=n(28222),d=n.n(h),m=(n(11189),n(24282)),g=n.n(m),y=n(76986),v=n.n(y),b=n(2578),w=n.n(b),E=n(24278),x=n.n(E),_=(n(39022),n(92039)),S=n.n(_),A=(n(58118),n(35627)),C=n.n(A),k=n(11882),O=n.n(k),j=n(51679),I=n.n(j),T=n(27043),N=n.n(T),P=n(81607),R=n.n(P),M=n(43393),D=n.n(M),L=n(17967),B=n(68929),F=n.n(B),U=n(11700),z=n.n(U),q=n(88306),$=n.n(q),V=n(13311),W=n.n(V),H=n(59704),J=n.n(H),K=n(77813),G=n.n(K),Z=n(23560),Y=n.n(Z),Q=n(57050),X=n(27504),ee=n(8269),te=n.n(ee),ne=n(19069),re=n(92282),oe=n.n(re),ae=n(89072),ie=n.n(ae),se=n(1272),le=n(48764).Buffer;const ue="default",ce=e=>D().Iterable.isIterable(e);function pe(e){return me(e)?ce(e)?e.toJS():e:{}}function fe(e){var t,n;if(ce(e))return e;if(e instanceof X.Z.File)return e;if(!me(e))return e;if(o()(e))return i()(n=D().Seq(e)).call(n,fe).toList();if(Y()(l()(e))){var r;const t=function(e){if(!Y()(l()(e)))return e;const t={},n="_**[]",r={};for(let o of l()(e).call(e))if(t[o[0]]||r[o[0]]&&r[o[0]].containsMultiple){if(!r[o[0]]){r[o[0]]={containsMultiple:!0,length:1},t[`${o[0]}${n}${r[o[0]].length}`]=t[o[0]],delete t[o[0]]}r[o[0]].length+=1,t[`${o[0]}${n}${r[o[0]].length}`]=o[1]}else t[o[0]]=o[1];return t}(e);return i()(r=D().OrderedMap(t)).call(r,fe)}return i()(t=D().OrderedMap(e)).call(t,fe)}function he(e){return o()(e)?e:[e]}function de(e){return"function"==typeof e}function me(e){return!!e&&"object"==typeof e}function ge(e){return"function"==typeof e}function ye(e){return o()(e)}const ve=$();function be(e,t){var n;return g()(n=d()(e)).call(n,((n,r)=>(n[r]=t(e[r],r),n)),{})}function we(e,t){var n;return g()(n=d()(e)).call(n,((n,r)=>{let o=t(e[r],r);return o&&"object"==typeof o&&v()(n,o),n}),{})}function Ee(e){return t=>{let{dispatch:n,getState:r}=t;return t=>n=>"function"==typeof n?n(e()):t(n)}}function xe(e){var t;let n=e.keySeq();return n.contains(ue)?ue:w()(t=f()(n).call(n,(e=>"2"===(e+"")[0]))).call(t).first()}function _e(e,t){if(!D().Iterable.isIterable(e))return D().List();let n=e.getIn(o()(t)?t:[t]);return D().List.isList(n)?n:D().List()}function Se(e){let t,n=[/filename\*=[^']+'\w*'"([^"]+)";?/i,/filename\*=[^']+'\w*'([^;]+);?/i,/filename="([^;]*);?"/i,/filename=([^;]*);?/i];if(S()(n).call(n,(n=>(t=n.exec(e),null!==t))),null!==t&&t.length>1)try{return decodeURIComponent(t[1])}catch(e){console.error(e)}return null}function Ae(e){return t=e.replace(/\.[^./]*$/,""),z()(F()(t));var t}const Ce=(e,t)=>{if(e>t)return`Value must be less than ${t}`},ke=(e,t)=>{if(e{if(!/^-?\d+(\.?\d+)?$/.test(e))return"Value must be a number"},je=e=>{if(!/^-?\d+$/.test(e))return"Value must be an integer"},Ie=e=>{if(e&&!(e instanceof X.Z.File))return"Value must be a file"},Te=e=>{if("true"!==e&&"false"!==e&&!0!==e&&!1!==e)return"Value must be a boolean"},Ne=e=>{if(e&&"string"!=typeof e)return"Value must be a string"},Pe=e=>{if(isNaN(Date.parse(e)))return"Value must be a DateTime"},Re=e=>{if(e=e.toString().toLowerCase(),!/^[{(]?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[)}]?$/.test(e))return"Value must be a Guid"},Me=(e,t)=>{if(e.length>t)return`Value must be no longer than ${t} character${1!==t?"s":""}`},De=(e,t)=>{if(e&&("true"===t||!0===t)){const t=(0,M.fromJS)(e),n=t.toSet();if(e.length>n.size){let e=(0,M.Set)();if(c()(t).call(t,((n,r)=>{f()(t).call(t,(e=>ge(e.equals)?e.equals(n):e===n)).size>1&&(e=e.add(r))})),0!==e.size)return i()(e).call(e,(e=>({index:e,error:"No duplicates allowed."}))).toArray()}}},Le=(e,t)=>{if(!e&&t>=1||e&&e.length{if(e&&e.length>t)return`Array must not contain more then ${t} item${1===t?"":"s"}`},Fe=(e,t)=>{if(e.length{if(!new RegExp(t).test(e))return"Value must follow pattern "+t};function ze(e,t,n,r,a){if(!t)return[];let s=[],l=t.get("nullable"),u=t.get("required"),p=t.get("maximum"),f=t.get("minimum"),h=t.get("type"),d=t.get("format"),m=t.get("maxLength"),g=t.get("minLength"),y=t.get("uniqueItems"),v=t.get("maxItems"),b=t.get("minItems"),w=t.get("pattern");const E=n||!0===u,x=null!=e;if(l&&null===e||!h||!(E||x&&"array"===h||!(!E&&!x)))return[];let _="string"===h&&e,A="array"===h&&o()(e)&&e.length,C="array"===h&&D().List.isList(e)&&e.count();const k=[_,A,C,"array"===h&&"string"==typeof e&&e,"file"===h&&e instanceof X.Z.File,"boolean"===h&&(e||!1===e),"number"===h&&(e||0===e),"integer"===h&&(e||0===e),"object"===h&&"object"==typeof e&&null!==e,"object"===h&&"string"==typeof e&&e],O=S()(k).call(k,(e=>!!e));if(E&&!O&&!r)return s.push("Required field is not provided"),s;if("object"===h&&(null===a||"application/json"===a)){let n=e;if("string"==typeof e)try{n=JSON.parse(e)}catch(e){return s.push("Parameter string value must be valid JSON"),s}var j;if(t&&t.has("required")&&ge(u.isList)&&u.isList()&&c()(u).call(u,(e=>{void 0===n[e]&&s.push({propKey:e,error:"Required property not found"})})),t&&t.has("properties"))c()(j=t.get("properties")).call(j,((e,t)=>{const o=ze(n[t],e,!1,r,a);s.push(...i()(o).call(o,(e=>({propKey:t,error:e}))))}))}if(w){let t=Ue(e,w);t&&s.push(t)}if(b&&"array"===h){let t=Le(e,b);t&&s.push(t)}if(v&&"array"===h){let t=Be(e,v);t&&s.push({needRemove:!0,error:t})}if(y&&"array"===h){let t=De(e,y);t&&s.push(...t)}if(m||0===m){let t=Me(e,m);t&&s.push(t)}if(g){let t=Fe(e,g);t&&s.push(t)}if(p||0===p){let t=Ce(e,p);t&&s.push(t)}if(f||0===f){let t=ke(e,f);t&&s.push(t)}if("string"===h){let t;if(t="date-time"===d?Pe(e):"uuid"===d?Re(e):Ne(e),!t)return s;s.push(t)}else if("boolean"===h){let t=Te(e);if(!t)return s;s.push(t)}else if("number"===h){let t=Oe(e);if(!t)return s;s.push(t)}else if("integer"===h){let t=je(e);if(!t)return s;s.push(t)}else if("array"===h){if(!A&&!C)return s;e&&c()(e).call(e,((e,n)=>{const o=ze(e,t.get("items"),!1,r,a);s.push(...i()(o).call(o,(e=>({index:n,error:e}))))}))}else if("file"===h){let t=Ie(e);if(!t)return s;s.push(t)}return s}const qe=function(e,t){let{isOAS3:n=!1,bypassRequiredCheck:r=!1}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},o=e.get("required"),{schema:a,parameterContentMediaType:i}=(0,ne.Z)(e,{isOAS3:n});return ze(t,a,o,r,i)},$e=(e,t,n)=>{if(e&&!e.xml&&(e.xml={}),e&&!e.xml.name){if(!e.$$ref&&(e.type||e.items||e.properties||e.additionalProperties))return'\n\x3c!-- XML example cannot be generated; root element name is undefined --\x3e';if(e.$$ref){let t=e.$$ref.match(/\S*\/(\S+)$/);e.xml.name=t[1]}}return(0,Q.memoizedCreateXMLExample)(e,t,n)},Ve=[{when:/json/,shouldStringifyTypes:["string"]}],We=["object"],He=(e,t,n,r)=>{const o=(0,Q.memoizedSampleFromSchema)(e,t,r),a=typeof o,i=g()(Ve).call(Ve,((e,t)=>t.when.test(n)?[...e,...t.shouldStringifyTypes]:e),We);return J()(i,(e=>e===a))?C()(o,null,2):o},Je=(e,t,n,r)=>{const o=He(e,t,n,r);let a;try{a=se.ZP.dump(se.ZP.load(o),{lineWidth:-1},{schema:se.A8}),"\n"===a[a.length-1]&&(a=x()(a).call(a,0,a.length-1))}catch(e){return console.error(e),"error: could not generate yaml example"}return a.replace(/\t/g," ")},Ke=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:void 0;return e&&ge(e.toJS)&&(e=e.toJS()),r&&ge(r.toJS)&&(r=r.toJS()),/xml/.test(t)?$e(e,n,r):/(yaml|yml)/.test(t)?Je(e,n,t,r):He(e,n,t,r)},Ge=()=>{let e={},t=X.Z.location.search;if(!t)return{};if(""!=t){let n=t.substr(1).split("&");for(let t in n)Object.prototype.hasOwnProperty.call(n,t)&&(t=n[t].split("="),e[decodeURIComponent(t[0])]=t[1]&&decodeURIComponent(t[1])||"")}return e},Ze=e=>{let t;return t=e instanceof le?e:le.from(e.toString(),"utf-8"),t.toString("base64")},Ye={operationsSorter:{alpha:(e,t)=>e.get("path").localeCompare(t.get("path")),method:(e,t)=>e.get("method").localeCompare(t.get("method"))},tagsSorter:{alpha:(e,t)=>e.localeCompare(t)}},Qe=e=>{let t=[];for(let n in e){let r=e[n];void 0!==r&&""!==r&&t.push([n,"=",encodeURIComponent(r).replace(/%20/g,"+")].join(""))}return t.join("&")},Xe=(e,t,n)=>!!W()(n,(n=>G()(e[n],t[n])));function et(e){return"string"!=typeof e||""===e?"":(0,L.N)(e)}function tt(e){return!(!e||O()(e).call(e,"localhost")>=0||O()(e).call(e,"127.0.0.1")>=0||"none"===e)}function nt(e){if(!D().OrderedMap.isOrderedMap(e))return null;if(!e.size)return null;const t=I()(e).call(e,((e,t)=>N()(t).call(t,"2")&&d()(e.get("content")||{}).length>0)),n=e.get("default")||D().OrderedMap(),r=(n.get("content")||D().OrderedMap()).keySeq().toJS().length?n:null;return t||r}const rt=e=>"string"==typeof e||e instanceof String?R()(e).call(e).replace(/\s/g,"%20"):"",ot=e=>te()(rt(e).replace(/%20/g,"_")),at=e=>f()(e).call(e,((e,t)=>/^x-/.test(t))),it=e=>f()(e).call(e,((e,t)=>/^pattern|maxLength|minLength|maximum|minimum/.test(t)));function st(e,t){var n;let r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:()=>!0;if("object"!=typeof e||o()(e)||null===e||!t)return e;const a=v()({},e);return c()(n=d()(a)).call(n,(e=>{e===t&&r(a[e],e)?delete a[e]:a[e]=st(a[e],t,r)})),a}function lt(e){if("string"==typeof e)return e;if(e&&e.toJS&&(e=e.toJS()),"object"==typeof e&&null!==e)try{return C()(e,null,2)}catch(t){return String(e)}return null==e?"":e.toString()}function ut(e){return"number"==typeof e?e.toString():e}function ct(e){let{returnAll:t=!1,allowHashes:n=!0}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(!D().Map.isMap(e))throw new Error("paramToIdentifier: received a non-Im.Map parameter as input");const r=e.get("name"),o=e.get("in");let a=[];return e&&e.hashCode&&o&&r&&n&&a.push(`${o}.${r}.hash-${e.hashCode()}`),o&&r&&a.push(`${o}.${r}`),a.push(r),t?a:a[0]||""}function pt(e,t){var n;const r=ct(e,{returnAll:!0});return f()(n=i()(r).call(r,(e=>t[e]))).call(n,(e=>void 0!==e))[0]}function ft(){return dt(oe()(32).toString("base64"))}function ht(e){return dt(ie()("sha256").update(e).digest("base64"))}function dt(e){return e.replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}const mt=e=>!e||!(!ce(e)||!e.isEmpty())},2518:(e,t,n)=>{"use strict";function r(e){return function(e){try{return!!JSON.parse(e)}catch(e){return null}}(e)?"json":null}n.d(t,{O:()=>r})},27504:(e,t,n)=>{"use strict";n.d(t,{Z:()=>r});const r=function(){var e={location:{},history:{},open:()=>{},close:()=>{},File:function(){}};if("undefined"==typeof window)return e;try{e=window;for(var t of["File","Blob","FormData"])t in window&&(e[t]=window[t])}catch(e){console.error(e)}return e}()},19069:(e,t,n)=>{"use strict";n.d(t,{Z:()=>c});var r=n(14418),o=n.n(r),a=n(58118),i=n.n(a),s=n(43393),l=n.n(s);const u=l().Set.of("type","format","items","default","maximum","exclusiveMaximum","minimum","exclusiveMinimum","maxLength","minLength","pattern","maxItems","minItems","uniqueItems","enum","multipleOf");function c(e){let{isOAS3:t}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(!l().Map.isMap(e))return{schema:l().Map(),parameterContentMediaType:null};if(!t)return"body"===e.get("in")?{schema:e.get("schema",l().Map()),parameterContentMediaType:null}:{schema:o()(e).call(e,((e,t)=>i()(u).call(u,t))),parameterContentMediaType:null};if(e.get("content")){const t=e.get("content",l().Map({})).keySeq().first();return{schema:e.getIn(["content",t,"schema"],l().Map()),parameterContentMediaType:t}}return{schema:e.get("schema")?e.get("schema",l().Map()):l().Map(),parameterContentMediaType:null}}},60314:(e,t,n)=>{"use strict";n.d(t,{Z:()=>x});var r=n(58309),o=n.n(r),a=n(2250),i=n.n(a),s=n(25110),l=n.n(s),u=n(8712),c=n.n(u),p=n(51679),f=n.n(p),h=n(12373),d=n.n(h),m=n(18492),g=n.n(m),y=n(88306),v=n.n(y);const b=e=>t=>o()(e)&&o()(t)&&e.length===t.length&&i()(e).call(e,((e,n)=>e===t[n])),w=function(){for(var e=arguments.length,t=new Array(e),n=0;n1&&void 0!==arguments[1]?arguments[1]:w;const{Cache:n}=v();v().Cache=E;const r=v()(e,t);return v().Cache=n,r}},79742:(e,t)=>{"use strict";t.byteLength=function(e){var t=l(e),n=t[0],r=t[1];return 3*(n+r)/4-r},t.toByteArray=function(e){var t,n,a=l(e),i=a[0],s=a[1],u=new o(function(e,t,n){return 3*(t+n)/4-n}(0,i,s)),c=0,p=s>0?i-4:i;for(n=0;n>16&255,u[c++]=t>>8&255,u[c++]=255&t;2===s&&(t=r[e.charCodeAt(n)]<<2|r[e.charCodeAt(n+1)]>>4,u[c++]=255&t);1===s&&(t=r[e.charCodeAt(n)]<<10|r[e.charCodeAt(n+1)]<<4|r[e.charCodeAt(n+2)]>>2,u[c++]=t>>8&255,u[c++]=255&t);return u},t.fromByteArray=function(e){for(var t,r=e.length,o=r%3,a=[],i=16383,s=0,l=r-o;sl?l:s+i));1===o?(t=e[r-1],a.push(n[t>>2]+n[t<<4&63]+"==")):2===o&&(t=(e[r-2]<<8)+e[r-1],a.push(n[t>>10]+n[t>>4&63]+n[t<<2&63]+"="));return a.join("")};for(var n=[],r=[],o="undefined"!=typeof Uint8Array?Uint8Array:Array,a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",i=0,s=a.length;i0)throw new Error("Invalid string. Length must be a multiple of 4");var n=e.indexOf("=");return-1===n&&(n=t),[n,n===t?0:4-n%4]}function u(e,t,r){for(var o,a,i=[],s=t;s>18&63]+n[a>>12&63]+n[a>>6&63]+n[63&a]);return i.join("")}r["-".charCodeAt(0)]=62,r["_".charCodeAt(0)]=63},48764:(e,t,n)=>{"use strict";const r=n(79742),o=n(80645),a="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;t.Buffer=l,t.SlowBuffer=function(e){+e!=e&&(e=0);return l.alloc(+e)},t.INSPECT_MAX_BYTES=50;const i=2147483647;function s(e){if(e>i)throw new RangeError('The value "'+e+'" is invalid for option "size"');const t=new Uint8Array(e);return Object.setPrototypeOf(t,l.prototype),t}function l(e,t,n){if("number"==typeof e){if("string"==typeof t)throw new TypeError('The "string" argument must be of type string. Received type number');return p(e)}return u(e,t,n)}function u(e,t,n){if("string"==typeof e)return function(e,t){"string"==typeof t&&""!==t||(t="utf8");if(!l.isEncoding(t))throw new TypeError("Unknown encoding: "+t);const n=0|m(e,t);let r=s(n);const o=r.write(e,t);o!==n&&(r=r.slice(0,o));return r}(e,t);if(ArrayBuffer.isView(e))return function(e){if(G(e,Uint8Array)){const t=new Uint8Array(e);return h(t.buffer,t.byteOffset,t.byteLength)}return f(e)}(e);if(null==e)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof e);if(G(e,ArrayBuffer)||e&&G(e.buffer,ArrayBuffer))return h(e,t,n);if("undefined"!=typeof SharedArrayBuffer&&(G(e,SharedArrayBuffer)||e&&G(e.buffer,SharedArrayBuffer)))return h(e,t,n);if("number"==typeof e)throw new TypeError('The "value" argument must not be of type number. Received type number');const r=e.valueOf&&e.valueOf();if(null!=r&&r!==e)return l.from(r,t,n);const o=function(e){if(l.isBuffer(e)){const t=0|d(e.length),n=s(t);return 0===n.length||e.copy(n,0,0,t),n}if(void 0!==e.length)return"number"!=typeof e.length||Z(e.length)?s(0):f(e);if("Buffer"===e.type&&Array.isArray(e.data))return f(e.data)}(e);if(o)return o;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof e[Symbol.toPrimitive])return l.from(e[Symbol.toPrimitive]("string"),t,n);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof e)}function c(e){if("number"!=typeof e)throw new TypeError('"size" argument must be of type number');if(e<0)throw new RangeError('The value "'+e+'" is invalid for option "size"')}function p(e){return c(e),s(e<0?0:0|d(e))}function f(e){const t=e.length<0?0:0|d(e.length),n=s(t);for(let r=0;r=i)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+i.toString(16)+" bytes");return 0|e}function m(e,t){if(l.isBuffer(e))return e.length;if(ArrayBuffer.isView(e)||G(e,ArrayBuffer))return e.byteLength;if("string"!=typeof e)throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof e);const n=e.length,r=arguments.length>2&&!0===arguments[2];if(!r&&0===n)return 0;let o=!1;for(;;)switch(t){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":return H(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return J(e).length;default:if(o)return r?-1:H(e).length;t=(""+t).toLowerCase(),o=!0}}function g(e,t,n){let r=!1;if((void 0===t||t<0)&&(t=0),t>this.length)return"";if((void 0===n||n>this.length)&&(n=this.length),n<=0)return"";if((n>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return I(this,t,n);case"utf8":case"utf-8":return C(this,t,n);case"ascii":return O(this,t,n);case"latin1":case"binary":return j(this,t,n);case"base64":return A(this,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return T(this,t,n);default:if(r)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),r=!0}}function y(e,t,n){const r=e[t];e[t]=e[n],e[n]=r}function v(e,t,n,r,o){if(0===e.length)return-1;if("string"==typeof n?(r=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),Z(n=+n)&&(n=o?0:e.length-1),n<0&&(n=e.length+n),n>=e.length){if(o)return-1;n=e.length-1}else if(n<0){if(!o)return-1;n=0}if("string"==typeof t&&(t=l.from(t,r)),l.isBuffer(t))return 0===t.length?-1:b(e,t,n,r,o);if("number"==typeof t)return t&=255,"function"==typeof Uint8Array.prototype.indexOf?o?Uint8Array.prototype.indexOf.call(e,t,n):Uint8Array.prototype.lastIndexOf.call(e,t,n):b(e,[t],n,r,o);throw new TypeError("val must be string, number or Buffer")}function b(e,t,n,r,o){let a,i=1,s=e.length,l=t.length;if(void 0!==r&&("ucs2"===(r=String(r).toLowerCase())||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(e.length<2||t.length<2)return-1;i=2,s/=2,l/=2,n/=2}function u(e,t){return 1===i?e[t]:e.readUInt16BE(t*i)}if(o){let r=-1;for(a=n;as&&(n=s-l),a=n;a>=0;a--){let n=!0;for(let r=0;ro&&(r=o):r=o;const a=t.length;let i;for(r>a/2&&(r=a/2),i=0;i>8,o=n%256,a.push(o),a.push(r);return a}(t,e.length-n),e,n,r)}function A(e,t,n){return 0===t&&n===e.length?r.fromByteArray(e):r.fromByteArray(e.slice(t,n))}function C(e,t,n){n=Math.min(e.length,n);const r=[];let o=t;for(;o239?4:t>223?3:t>191?2:1;if(o+i<=n){let n,r,s,l;switch(i){case 1:t<128&&(a=t);break;case 2:n=e[o+1],128==(192&n)&&(l=(31&t)<<6|63&n,l>127&&(a=l));break;case 3:n=e[o+1],r=e[o+2],128==(192&n)&&128==(192&r)&&(l=(15&t)<<12|(63&n)<<6|63&r,l>2047&&(l<55296||l>57343)&&(a=l));break;case 4:n=e[o+1],r=e[o+2],s=e[o+3],128==(192&n)&&128==(192&r)&&128==(192&s)&&(l=(15&t)<<18|(63&n)<<12|(63&r)<<6|63&s,l>65535&&l<1114112&&(a=l))}}null===a?(a=65533,i=1):a>65535&&(a-=65536,r.push(a>>>10&1023|55296),a=56320|1023&a),r.push(a),o+=i}return function(e){const t=e.length;if(t<=k)return String.fromCharCode.apply(String,e);let n="",r=0;for(;rr.length?(l.isBuffer(t)||(t=l.from(t)),t.copy(r,o)):Uint8Array.prototype.set.call(r,t,o);else{if(!l.isBuffer(t))throw new TypeError('"list" argument must be an Array of Buffers');t.copy(r,o)}o+=t.length}return r},l.byteLength=m,l.prototype._isBuffer=!0,l.prototype.swap16=function(){const e=this.length;if(e%2!=0)throw new RangeError("Buffer size must be a multiple of 16-bits");for(let t=0;tn&&(e+=" ... "),""},a&&(l.prototype[a]=l.prototype.inspect),l.prototype.compare=function(e,t,n,r,o){if(G(e,Uint8Array)&&(e=l.from(e,e.offset,e.byteLength)),!l.isBuffer(e))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof e);if(void 0===t&&(t=0),void 0===n&&(n=e?e.length:0),void 0===r&&(r=0),void 0===o&&(o=this.length),t<0||n>e.length||r<0||o>this.length)throw new RangeError("out of range index");if(r>=o&&t>=n)return 0;if(r>=o)return-1;if(t>=n)return 1;if(this===e)return 0;let a=(o>>>=0)-(r>>>=0),i=(n>>>=0)-(t>>>=0);const s=Math.min(a,i),u=this.slice(r,o),c=e.slice(t,n);for(let e=0;e>>=0,isFinite(n)?(n>>>=0,void 0===r&&(r="utf8")):(r=n,n=void 0)}const o=this.length-t;if((void 0===n||n>o)&&(n=o),e.length>0&&(n<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");let a=!1;for(;;)switch(r){case"hex":return w(this,e,t,n);case"utf8":case"utf-8":return E(this,e,t,n);case"ascii":case"latin1":case"binary":return x(this,e,t,n);case"base64":return _(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return S(this,e,t,n);default:if(a)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),a=!0}},l.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};const k=4096;function O(e,t,n){let r="";n=Math.min(e.length,n);for(let o=t;or)&&(n=r);let o="";for(let r=t;rn)throw new RangeError("Trying to access beyond buffer length")}function P(e,t,n,r,o,a){if(!l.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>o||te.length)throw new RangeError("Index out of range")}function R(e,t,n,r,o){q(t,r,o,e,n,7);let a=Number(t&BigInt(4294967295));e[n++]=a,a>>=8,e[n++]=a,a>>=8,e[n++]=a,a>>=8,e[n++]=a;let i=Number(t>>BigInt(32)&BigInt(4294967295));return e[n++]=i,i>>=8,e[n++]=i,i>>=8,e[n++]=i,i>>=8,e[n++]=i,n}function M(e,t,n,r,o){q(t,r,o,e,n,7);let a=Number(t&BigInt(4294967295));e[n+7]=a,a>>=8,e[n+6]=a,a>>=8,e[n+5]=a,a>>=8,e[n+4]=a;let i=Number(t>>BigInt(32)&BigInt(4294967295));return e[n+3]=i,i>>=8,e[n+2]=i,i>>=8,e[n+1]=i,i>>=8,e[n]=i,n+8}function D(e,t,n,r,o,a){if(n+r>e.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function L(e,t,n,r,a){return t=+t,n>>>=0,a||D(e,0,n,4),o.write(e,t,n,r,23,4),n+4}function B(e,t,n,r,a){return t=+t,n>>>=0,a||D(e,0,n,8),o.write(e,t,n,r,52,8),n+8}l.prototype.slice=function(e,t){const n=this.length;(e=~~e)<0?(e+=n)<0&&(e=0):e>n&&(e=n),(t=void 0===t?n:~~t)<0?(t+=n)<0&&(t=0):t>n&&(t=n),t>>=0,t>>>=0,n||N(e,t,this.length);let r=this[e],o=1,a=0;for(;++a>>=0,t>>>=0,n||N(e,t,this.length);let r=this[e+--t],o=1;for(;t>0&&(o*=256);)r+=this[e+--t]*o;return r},l.prototype.readUint8=l.prototype.readUInt8=function(e,t){return e>>>=0,t||N(e,1,this.length),this[e]},l.prototype.readUint16LE=l.prototype.readUInt16LE=function(e,t){return e>>>=0,t||N(e,2,this.length),this[e]|this[e+1]<<8},l.prototype.readUint16BE=l.prototype.readUInt16BE=function(e,t){return e>>>=0,t||N(e,2,this.length),this[e]<<8|this[e+1]},l.prototype.readUint32LE=l.prototype.readUInt32LE=function(e,t){return e>>>=0,t||N(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},l.prototype.readUint32BE=l.prototype.readUInt32BE=function(e,t){return e>>>=0,t||N(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},l.prototype.readBigUInt64LE=Q((function(e){$(e>>>=0,"offset");const t=this[e],n=this[e+7];void 0!==t&&void 0!==n||V(e,this.length-8);const r=t+256*this[++e]+65536*this[++e]+this[++e]*2**24,o=this[++e]+256*this[++e]+65536*this[++e]+n*2**24;return BigInt(r)+(BigInt(o)<>>=0,"offset");const t=this[e],n=this[e+7];void 0!==t&&void 0!==n||V(e,this.length-8);const r=t*2**24+65536*this[++e]+256*this[++e]+this[++e],o=this[++e]*2**24+65536*this[++e]+256*this[++e]+n;return(BigInt(r)<>>=0,t>>>=0,n||N(e,t,this.length);let r=this[e],o=1,a=0;for(;++a=o&&(r-=Math.pow(2,8*t)),r},l.prototype.readIntBE=function(e,t,n){e>>>=0,t>>>=0,n||N(e,t,this.length);let r=t,o=1,a=this[e+--r];for(;r>0&&(o*=256);)a+=this[e+--r]*o;return o*=128,a>=o&&(a-=Math.pow(2,8*t)),a},l.prototype.readInt8=function(e,t){return e>>>=0,t||N(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},l.prototype.readInt16LE=function(e,t){e>>>=0,t||N(e,2,this.length);const n=this[e]|this[e+1]<<8;return 32768&n?4294901760|n:n},l.prototype.readInt16BE=function(e,t){e>>>=0,t||N(e,2,this.length);const n=this[e+1]|this[e]<<8;return 32768&n?4294901760|n:n},l.prototype.readInt32LE=function(e,t){return e>>>=0,t||N(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},l.prototype.readInt32BE=function(e,t){return e>>>=0,t||N(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},l.prototype.readBigInt64LE=Q((function(e){$(e>>>=0,"offset");const t=this[e],n=this[e+7];void 0!==t&&void 0!==n||V(e,this.length-8);const r=this[e+4]+256*this[e+5]+65536*this[e+6]+(n<<24);return(BigInt(r)<>>=0,"offset");const t=this[e],n=this[e+7];void 0!==t&&void 0!==n||V(e,this.length-8);const r=(t<<24)+65536*this[++e]+256*this[++e]+this[++e];return(BigInt(r)<>>=0,t||N(e,4,this.length),o.read(this,e,!0,23,4)},l.prototype.readFloatBE=function(e,t){return e>>>=0,t||N(e,4,this.length),o.read(this,e,!1,23,4)},l.prototype.readDoubleLE=function(e,t){return e>>>=0,t||N(e,8,this.length),o.read(this,e,!0,52,8)},l.prototype.readDoubleBE=function(e,t){return e>>>=0,t||N(e,8,this.length),o.read(this,e,!1,52,8)},l.prototype.writeUintLE=l.prototype.writeUIntLE=function(e,t,n,r){if(e=+e,t>>>=0,n>>>=0,!r){P(this,e,t,n,Math.pow(2,8*n)-1,0)}let o=1,a=0;for(this[t]=255&e;++a>>=0,n>>>=0,!r){P(this,e,t,n,Math.pow(2,8*n)-1,0)}let o=n-1,a=1;for(this[t+o]=255&e;--o>=0&&(a*=256);)this[t+o]=e/a&255;return t+n},l.prototype.writeUint8=l.prototype.writeUInt8=function(e,t,n){return e=+e,t>>>=0,n||P(this,e,t,1,255,0),this[t]=255&e,t+1},l.prototype.writeUint16LE=l.prototype.writeUInt16LE=function(e,t,n){return e=+e,t>>>=0,n||P(this,e,t,2,65535,0),this[t]=255&e,this[t+1]=e>>>8,t+2},l.prototype.writeUint16BE=l.prototype.writeUInt16BE=function(e,t,n){return e=+e,t>>>=0,n||P(this,e,t,2,65535,0),this[t]=e>>>8,this[t+1]=255&e,t+2},l.prototype.writeUint32LE=l.prototype.writeUInt32LE=function(e,t,n){return e=+e,t>>>=0,n||P(this,e,t,4,4294967295,0),this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e,t+4},l.prototype.writeUint32BE=l.prototype.writeUInt32BE=function(e,t,n){return e=+e,t>>>=0,n||P(this,e,t,4,4294967295,0),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},l.prototype.writeBigUInt64LE=Q((function(e,t=0){return R(this,e,t,BigInt(0),BigInt("0xffffffffffffffff"))})),l.prototype.writeBigUInt64BE=Q((function(e,t=0){return M(this,e,t,BigInt(0),BigInt("0xffffffffffffffff"))})),l.prototype.writeIntLE=function(e,t,n,r){if(e=+e,t>>>=0,!r){const r=Math.pow(2,8*n-1);P(this,e,t,n,r-1,-r)}let o=0,a=1,i=0;for(this[t]=255&e;++o>0)-i&255;return t+n},l.prototype.writeIntBE=function(e,t,n,r){if(e=+e,t>>>=0,!r){const r=Math.pow(2,8*n-1);P(this,e,t,n,r-1,-r)}let o=n-1,a=1,i=0;for(this[t+o]=255&e;--o>=0&&(a*=256);)e<0&&0===i&&0!==this[t+o+1]&&(i=1),this[t+o]=(e/a>>0)-i&255;return t+n},l.prototype.writeInt8=function(e,t,n){return e=+e,t>>>=0,n||P(this,e,t,1,127,-128),e<0&&(e=255+e+1),this[t]=255&e,t+1},l.prototype.writeInt16LE=function(e,t,n){return e=+e,t>>>=0,n||P(this,e,t,2,32767,-32768),this[t]=255&e,this[t+1]=e>>>8,t+2},l.prototype.writeInt16BE=function(e,t,n){return e=+e,t>>>=0,n||P(this,e,t,2,32767,-32768),this[t]=e>>>8,this[t+1]=255&e,t+2},l.prototype.writeInt32LE=function(e,t,n){return e=+e,t>>>=0,n||P(this,e,t,4,2147483647,-2147483648),this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24,t+4},l.prototype.writeInt32BE=function(e,t,n){return e=+e,t>>>=0,n||P(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},l.prototype.writeBigInt64LE=Q((function(e,t=0){return R(this,e,t,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),l.prototype.writeBigInt64BE=Q((function(e,t=0){return M(this,e,t,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),l.prototype.writeFloatLE=function(e,t,n){return L(this,e,t,!0,n)},l.prototype.writeFloatBE=function(e,t,n){return L(this,e,t,!1,n)},l.prototype.writeDoubleLE=function(e,t,n){return B(this,e,t,!0,n)},l.prototype.writeDoubleBE=function(e,t,n){return B(this,e,t,!1,n)},l.prototype.copy=function(e,t,n,r){if(!l.isBuffer(e))throw new TypeError("argument should be a Buffer");if(n||(n=0),r||0===r||(r=this.length),t>=e.length&&(t=e.length),t||(t=0),r>0&&r=this.length)throw new RangeError("Index out of range");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),e.length-t>>=0,n=void 0===n?this.length:n>>>0,e||(e=0),"number"==typeof e)for(o=t;o=r+4;n-=3)t=`_${e.slice(n-3,n)}${t}`;return`${e.slice(0,n)}${t}`}function q(e,t,n,r,o,a){if(e>n||e3?0===t||t===BigInt(0)?`>= 0${r} and < 2${r} ** ${8*(a+1)}${r}`:`>= -(2${r} ** ${8*(a+1)-1}${r}) and < 2 ** ${8*(a+1)-1}${r}`:`>= ${t}${r} and <= ${n}${r}`,new F.ERR_OUT_OF_RANGE("value",o,e)}!function(e,t,n){$(t,"offset"),void 0!==e[t]&&void 0!==e[t+n]||V(t,e.length-(n+1))}(r,o,a)}function $(e,t){if("number"!=typeof e)throw new F.ERR_INVALID_ARG_TYPE(t,"number",e)}function V(e,t,n){if(Math.floor(e)!==e)throw $(e,n),new F.ERR_OUT_OF_RANGE(n||"offset","an integer",e);if(t<0)throw new F.ERR_BUFFER_OUT_OF_BOUNDS;throw new F.ERR_OUT_OF_RANGE(n||"offset",`>= ${n?1:0} and <= ${t}`,e)}U("ERR_BUFFER_OUT_OF_BOUNDS",(function(e){return e?`${e} is outside of buffer bounds`:"Attempt to access memory outside buffer bounds"}),RangeError),U("ERR_INVALID_ARG_TYPE",(function(e,t){return`The "${e}" argument must be of type number. Received type ${typeof t}`}),TypeError),U("ERR_OUT_OF_RANGE",(function(e,t,n){let r=`The value of "${e}" is out of range.`,o=n;return Number.isInteger(n)&&Math.abs(n)>2**32?o=z(String(n)):"bigint"==typeof n&&(o=String(n),(n>BigInt(2)**BigInt(32)||n<-(BigInt(2)**BigInt(32)))&&(o=z(o)),o+="n"),r+=` It must be ${t}. Received ${o}`,r}),RangeError);const W=/[^+/0-9A-Za-z-_]/g;function H(e,t){let n;t=t||1/0;const r=e.length;let o=null;const a=[];for(let i=0;i55295&&n<57344){if(!o){if(n>56319){(t-=3)>-1&&a.push(239,191,189);continue}if(i+1===r){(t-=3)>-1&&a.push(239,191,189);continue}o=n;continue}if(n<56320){(t-=3)>-1&&a.push(239,191,189),o=n;continue}n=65536+(o-55296<<10|n-56320)}else o&&(t-=3)>-1&&a.push(239,191,189);if(o=null,n<128){if((t-=1)<0)break;a.push(n)}else if(n<2048){if((t-=2)<0)break;a.push(n>>6|192,63&n|128)}else if(n<65536){if((t-=3)<0)break;a.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;a.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return a}function J(e){return r.toByteArray(function(e){if((e=(e=e.split("=")[0]).trim().replace(W,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function K(e,t,n,r){let o;for(o=0;o=t.length||o>=e.length);++o)t[o+n]=e[o];return o}function G(e,t){return e instanceof t||null!=e&&null!=e.constructor&&null!=e.constructor.name&&e.constructor.name===t.name}function Z(e){return e!=e}const Y=function(){const e="0123456789abcdef",t=new Array(256);for(let n=0;n<16;++n){const r=16*n;for(let o=0;o<16;++o)t[r+o]=e[n]+e[o]}return t}();function Q(e){return"undefined"==typeof BigInt?X:e}function X(){throw new Error("BigInt not supported")}},21924:(e,t,n)=>{"use strict";var r=n(40210),o=n(55559),a=o(r("String.prototype.indexOf"));e.exports=function(e,t){var n=r(e,!!t);return"function"==typeof n&&a(e,".prototype.")>-1?o(n):n}},55559:(e,t,n)=>{"use strict";var r=n(58612),o=n(40210),a=o("%Function.prototype.apply%"),i=o("%Function.prototype.call%"),s=o("%Reflect.apply%",!0)||r.call(i,a),l=o("%Object.getOwnPropertyDescriptor%",!0),u=o("%Object.defineProperty%",!0),c=o("%Math.max%");if(u)try{u({},"a",{value:1})}catch(e){u=null}e.exports=function(e){var t=s(r,i,arguments);l&&u&&(l(t,"length").configurable&&u(t,"length",{value:1+c(0,e.length-(arguments.length-1))}));return t};var p=function(){return s(r,a,arguments)};u?u(e.exports,"apply",{value:p}):e.exports.apply=p},94184:(e,t)=>{var n;!function(){"use strict";var r={}.hasOwnProperty;function o(){for(var e=[],t=0;t{"use strict";t.parse=function(e,t){if("string"!=typeof e)throw new TypeError("argument str must be a string");var n={},r=(t||{}).decode||o,a=0;for(;a{"use strict";var r=n(11742),o={"text/plain":"Text","text/html":"Url",default:"Text"},a="Copy to clipboard: #{key}, Enter";e.exports=function(e,t){var n,i,s,l,u,c,p=!1;t||(t={}),n=t.debug||!1;try{if(s=r(),l=document.createRange(),u=document.getSelection(),(c=document.createElement("span")).textContent=e,c.ariaHidden="true",c.style.all="unset",c.style.position="fixed",c.style.top=0,c.style.clip="rect(0, 0, 0, 0)",c.style.whiteSpace="pre",c.style.webkitUserSelect="text",c.style.MozUserSelect="text",c.style.msUserSelect="text",c.style.userSelect="text",c.addEventListener("copy",(function(r){if(r.stopPropagation(),t.format)if(r.preventDefault(),void 0===r.clipboardData){n&&console.warn("unable to use e.clipboardData"),n&&console.warn("trying IE specific stuff"),window.clipboardData.clearData();var a=o[t.format]||o.default;window.clipboardData.setData(a,e)}else r.clipboardData.clearData(),r.clipboardData.setData(t.format,e);t.onCopy&&(r.preventDefault(),t.onCopy(r.clipboardData))})),document.body.appendChild(c),l.selectNodeContents(c),u.addRange(l),!document.execCommand("copy"))throw new Error("copy command was unsuccessful");p=!0}catch(r){n&&console.error("unable to copy using execCommand: ",r),n&&console.warn("trying IE specific stuff");try{window.clipboardData.setData(t.format||"text",e),t.onCopy&&t.onCopy(window.clipboardData),p=!0}catch(r){n&&console.error("unable to copy using clipboardData: ",r),n&&console.error("falling back to prompt"),i=function(e){var t=(/mac os x/i.test(navigator.userAgent)?"⌘":"Ctrl")+"+C";return e.replace(/#{\s*key\s*}/g,t)}("message"in t?t.message:a),window.prompt(i,e)}}finally{u&&("function"==typeof u.removeRange?u.removeRange(l):u.removeAllRanges()),c&&document.body.removeChild(c),s()}return p}},90093:(e,t,n)=>{var r=n(28196);e.exports=r},3688:(e,t,n)=>{var r=n(11955);e.exports=r},83838:(e,t,n)=>{var r=n(46279);e.exports=r},15684:(e,t,n)=>{var r=n(19373);e.exports=r},65362:(e,t,n)=>{var r=n(63383);e.exports=r},91254:(e,t,n)=>{var r=n(57396);e.exports=r},43536:(e,t,n)=>{var r=n(41910);e.exports=r},37331:(e,t,n)=>{var r=n(79427);e.exports=r},68522:(e,t,n)=>{var r=n(62857);e.exports=r},73151:(e,t,n)=>{var r=n(9534);e.exports=r},45012:(e,t,n)=>{var r=n(23059);e.exports=r},80281:(e,t,n)=>{var r=n(92547);n(43975),e.exports=r},40031:(e,t,n)=>{var r=n(46509);e.exports=r},17487:(e,t,n)=>{var r=n(35774);e.exports=r},54493:(e,t,n)=>{n(77971),n(53242);var r=n(54058);e.exports=r.Array.from},24034:(e,t,n)=>{n(92737);var r=n(54058);e.exports=r.Array.isArray},15367:(e,t,n)=>{n(85906);var r=n(35703);e.exports=r("Array").concat},12710:(e,t,n)=>{n(66274),n(55967);var r=n(35703);e.exports=r("Array").entries},51459:(e,t,n)=>{n(48851);var r=n(35703);e.exports=r("Array").every},6172:(e,t,n)=>{n(80290);var r=n(35703);e.exports=r("Array").fill},62383:(e,t,n)=>{n(21501);var r=n(35703);e.exports=r("Array").filter},60009:(e,t,n)=>{n(44929);var r=n(35703);e.exports=r("Array").findIndex},17671:(e,t,n)=>{n(80833);var r=n(35703);e.exports=r("Array").find},99324:(e,t,n)=>{n(2437);var r=n(35703);e.exports=r("Array").forEach},80991:(e,t,n)=>{n(97690);var r=n(35703);e.exports=r("Array").includes},8700:(e,t,n)=>{n(99076);var r=n(35703);e.exports=r("Array").indexOf},95909:(e,t,n)=>{n(66274),n(55967);var r=n(35703);e.exports=r("Array").keys},6442:(e,t,n)=>{n(75915);var r=n(35703);e.exports=r("Array").lastIndexOf},23866:(e,t,n)=>{n(68787);var r=n(35703);e.exports=r("Array").map},52999:(e,t,n)=>{n(81876);var r=n(35703);e.exports=r("Array").reduce},24900:(e,t,n)=>{n(60186);var r=n(35703);e.exports=r("Array").slice},3824:(e,t,n)=>{n(36026);var r=n(35703);e.exports=r("Array").some},2948:(e,t,n)=>{n(4115);var r=n(35703);e.exports=r("Array").sort},78209:(e,t,n)=>{n(98611);var r=n(35703);e.exports=r("Array").splice},14423:(e,t,n)=>{n(66274),n(55967);var r=n(35703);e.exports=r("Array").values},81103:(e,t,n)=>{n(95160);var r=n(54058);e.exports=r.Date.now},27700:(e,t,n)=>{n(73381);var r=n(35703);e.exports=r("Function").bind},16246:(e,t,n)=>{var r=n(7046),o=n(27700),a=Function.prototype;e.exports=function(e){var t=e.bind;return e===a||r(a,e)&&t===a.bind?o:t}},56043:(e,t,n)=>{var r=n(7046),o=n(15367),a=Array.prototype;e.exports=function(e){var t=e.concat;return e===a||r(a,e)&&t===a.concat?o:t}},13160:(e,t,n)=>{var r=n(7046),o=n(51459),a=Array.prototype;e.exports=function(e){var t=e.every;return e===a||r(a,e)&&t===a.every?o:t}},80446:(e,t,n)=>{var r=n(7046),o=n(6172),a=Array.prototype;e.exports=function(e){var t=e.fill;return e===a||r(a,e)&&t===a.fill?o:t}},2480:(e,t,n)=>{var r=n(7046),o=n(62383),a=Array.prototype;e.exports=function(e){var t=e.filter;return e===a||r(a,e)&&t===a.filter?o:t}},7147:(e,t,n)=>{var r=n(7046),o=n(60009),a=Array.prototype;e.exports=function(e){var t=e.findIndex;return e===a||r(a,e)&&t===a.findIndex?o:t}},32236:(e,t,n)=>{var r=n(7046),o=n(17671),a=Array.prototype;e.exports=function(e){var t=e.find;return e===a||r(a,e)&&t===a.find?o:t}},58557:(e,t,n)=>{var r=n(7046),o=n(80991),a=n(21631),i=Array.prototype,s=String.prototype;e.exports=function(e){var t=e.includes;return e===i||r(i,e)&&t===i.includes?o:"string"==typeof e||e===s||r(s,e)&&t===s.includes?a:t}},34570:(e,t,n)=>{var r=n(7046),o=n(8700),a=Array.prototype;e.exports=function(e){var t=e.indexOf;return e===a||r(a,e)&&t===a.indexOf?o:t}},57564:(e,t,n)=>{var r=n(7046),o=n(6442),a=Array.prototype;e.exports=function(e){var t=e.lastIndexOf;return e===a||r(a,e)&&t===a.lastIndexOf?o:t}},88287:(e,t,n)=>{var r=n(7046),o=n(23866),a=Array.prototype;e.exports=function(e){var t=e.map;return e===a||r(a,e)&&t===a.map?o:t}},68025:(e,t,n)=>{var r=n(7046),o=n(52999),a=Array.prototype;e.exports=function(e){var t=e.reduce;return e===a||r(a,e)&&t===a.reduce?o:t}},59257:(e,t,n)=>{var r=n(7046),o=n(80454),a=String.prototype;e.exports=function(e){var t=e.repeat;return"string"==typeof e||e===a||r(a,e)&&t===a.repeat?o:t}},69601:(e,t,n)=>{var r=n(7046),o=n(24900),a=Array.prototype;e.exports=function(e){var t=e.slice;return e===a||r(a,e)&&t===a.slice?o:t}},28299:(e,t,n)=>{var r=n(7046),o=n(3824),a=Array.prototype;e.exports=function(e){var t=e.some;return e===a||r(a,e)&&t===a.some?o:t}},69355:(e,t,n)=>{var r=n(7046),o=n(2948),a=Array.prototype;e.exports=function(e){var t=e.sort;return e===a||r(a,e)&&t===a.sort?o:t}},18339:(e,t,n)=>{var r=n(7046),o=n(78209),a=Array.prototype;e.exports=function(e){var t=e.splice;return e===a||r(a,e)&&t===a.splice?o:t}},71611:(e,t,n)=>{var r=n(7046),o=n(3269),a=String.prototype;e.exports=function(e){var t=e.startsWith;return"string"==typeof e||e===a||r(a,e)&&t===a.startsWith?o:t}},62774:(e,t,n)=>{var r=n(7046),o=n(13348),a=String.prototype;e.exports=function(e){var t=e.trim;return"string"==typeof e||e===a||r(a,e)&&t===a.trim?o:t}},84426:(e,t,n)=>{n(32619);var r=n(54058),o=n(79730);r.JSON||(r.JSON={stringify:JSON.stringify}),e.exports=function(e,t,n){return o(r.JSON.stringify,null,arguments)}},91018:(e,t,n)=>{n(66274),n(37501),n(55967),n(77971);var r=n(54058);e.exports=r.Map},45999:(e,t,n)=>{n(49221);var r=n(54058);e.exports=r.Object.assign},7702:(e,t,n)=>{n(74979);var r=n(54058).Object,o=e.exports=function(e,t){return r.defineProperties(e,t)};r.defineProperties.sham&&(o.sham=!0)},48171:(e,t,n)=>{n(86450);var r=n(54058).Object,o=e.exports=function(e,t,n){return r.defineProperty(e,t,n)};r.defineProperty.sham&&(o.sham=!0)},286:(e,t,n)=>{n(46924);var r=n(54058).Object,o=e.exports=function(e,t){return r.getOwnPropertyDescriptor(e,t)};r.getOwnPropertyDescriptor.sham&&(o.sham=!0)},92766:(e,t,n)=>{n(88482);var r=n(54058);e.exports=r.Object.getOwnPropertyDescriptors},30498:(e,t,n)=>{n(35824);var r=n(54058);e.exports=r.Object.getOwnPropertySymbols},48494:(e,t,n)=>{n(21724);var r=n(54058);e.exports=r.Object.keys},98430:(e,t,n)=>{n(26614);var r=n(54058);e.exports=r.Object.values},52956:(e,t,n)=>{n(47627),n(66274),n(55967),n(98881),n(4560),n(91302),n(44349),n(77971);var r=n(54058);e.exports=r.Promise},21631:(e,t,n)=>{n(11035);var r=n(35703);e.exports=r("String").includes},80454:(e,t,n)=>{n(60986);var r=n(35703);e.exports=r("String").repeat},3269:(e,t,n)=>{n(94761);var r=n(35703);e.exports=r("String").startsWith},13348:(e,t,n)=>{n(57398);var r=n(35703);e.exports=r("String").trim},57473:(e,t,n)=>{n(85906),n(55967),n(35824),n(8555),n(52615),n(21732),n(35903),n(1825),n(28394),n(45915),n(61766),n(62737),n(89911),n(74315),n(63131),n(64714),n(70659),n(69120),n(79413),n(1502);var r=n(54058);e.exports=r.Symbol},24227:(e,t,n)=>{n(66274),n(55967),n(77971),n(1825);var r=n(11477);e.exports=r.f("iterator")},62978:(e,t,n)=>{n(18084),n(63131);var r=n(11477);e.exports=r.f("toPrimitive")},14122:(e,t,n)=>{e.exports=n(89097)},44442:(e,t,n)=>{e.exports=n(51675)},57152:(e,t,n)=>{e.exports=n(82507)},69447:(e,t,n)=>{e.exports=n(628)},60269:(e,t,n)=>{e.exports=n(76936)},70573:(e,t,n)=>{e.exports=n(18180)},73685:(e,t,n)=>{e.exports=n(80621)},27533:(e,t,n)=>{e.exports=n(22948)},39057:(e,t,n)=>{e.exports=n(82108)},84710:(e,t,n)=>{e.exports=n(14058)},93799:(e,t,n)=>{e.exports=n(92093)},86600:(e,t,n)=>{e.exports=n(52201)},9759:(e,t,n)=>{e.exports=n(27398)},71384:(e,t,n)=>{e.exports=n(26189)},89097:(e,t,n)=>{var r=n(90093);e.exports=r},51675:(e,t,n)=>{var r=n(3688);e.exports=r},82507:(e,t,n)=>{var r=n(83838);e.exports=r},628:(e,t,n)=>{var r=n(15684);e.exports=r},76936:(e,t,n)=>{var r=n(65362);e.exports=r},18180:(e,t,n)=>{var r=n(91254);e.exports=r},80621:(e,t,n)=>{var r=n(43536);e.exports=r},22948:(e,t,n)=>{var r=n(37331);e.exports=r},82108:(e,t,n)=>{var r=n(68522);e.exports=r},14058:(e,t,n)=>{var r=n(73151);e.exports=r},92093:(e,t,n)=>{var r=n(45012);e.exports=r},52201:(e,t,n)=>{var r=n(80281);n(28783),n(22731),n(85605),n(65799),n(31943),n(46774),n(45414),n(80620),n(36172),e.exports=r},27398:(e,t,n)=>{var r=n(40031);e.exports=r},26189:(e,t,n)=>{var r=n(17487);e.exports=r},24883:(e,t,n)=>{var r=n(57475),o=n(69826),a=TypeError;e.exports=function(e){if(r(e))return e;throw a(o(e)+" is not a function")}},174:(e,t,n)=>{var r=n(24284),o=n(69826),a=TypeError;e.exports=function(e){if(r(e))return e;throw a(o(e)+" is not a constructor")}},11851:(e,t,n)=>{var r=n(57475),o=String,a=TypeError;e.exports=function(e){if("object"==typeof e||r(e))return e;throw a("Can't set "+o(e)+" as a prototype")}},18479:e=>{e.exports=function(){}},5743:(e,t,n)=>{var r=n(7046),o=TypeError;e.exports=function(e,t){if(r(t,e))return e;throw o("Incorrect invocation")}},96059:(e,t,n)=>{var r=n(10941),o=String,a=TypeError;e.exports=function(e){if(r(e))return e;throw a(o(e)+" is not an object")}},97135:(e,t,n)=>{var r=n(95981);e.exports=r((function(){if("function"==typeof ArrayBuffer){var e=new ArrayBuffer(8);Object.isExtensible(e)&&Object.defineProperty(e,"a",{value:8})}}))},91860:(e,t,n)=>{"use strict";var r=n(89678),o=n(59413),a=n(10623);e.exports=function(e){for(var t=r(this),n=a(t),i=arguments.length,s=o(i>1?arguments[1]:void 0,n),l=i>2?arguments[2]:void 0,u=void 0===l?n:o(l,n);u>s;)t[s++]=e;return t}},56837:(e,t,n)=>{"use strict";var r=n(3610).forEach,o=n(34194)("forEach");e.exports=o?[].forEach:function(e){return r(this,e,arguments.length>1?arguments[1]:void 0)}},11354:(e,t,n)=>{"use strict";var r=n(86843),o=n(78834),a=n(89678),i=n(75196),s=n(6782),l=n(24284),u=n(10623),c=n(55449),p=n(53476),f=n(22902),h=Array;e.exports=function(e){var t=a(e),n=l(this),d=arguments.length,m=d>1?arguments[1]:void 0,g=void 0!==m;g&&(m=r(m,d>2?arguments[2]:void 0));var y,v,b,w,E,x,_=f(t),S=0;if(!_||this===h&&s(_))for(y=u(t),v=n?new this(y):h(y);y>S;S++)x=g?m(t[S],S):t[S],c(v,S,x);else for(E=(w=p(t,_)).next,v=n?new this:[];!(b=o(E,w)).done;S++)x=g?i(w,m,[b.value,S],!0):b.value,c(v,S,x);return v.length=S,v}},31692:(e,t,n)=>{var r=n(74529),o=n(59413),a=n(10623),i=function(e){return function(t,n,i){var s,l=r(t),u=a(l),c=o(i,u);if(e&&n!=n){for(;u>c;)if((s=l[c++])!=s)return!0}else for(;u>c;c++)if((e||c in l)&&l[c]===n)return e||c||0;return!e&&-1}};e.exports={includes:i(!0),indexOf:i(!1)}},3610:(e,t,n)=>{var r=n(86843),o=n(95329),a=n(37026),i=n(89678),s=n(10623),l=n(64692),u=o([].push),c=function(e){var t=1==e,n=2==e,o=3==e,c=4==e,p=6==e,f=7==e,h=5==e||p;return function(d,m,g,y){for(var v,b,w=i(d),E=a(w),x=r(m,g),_=s(E),S=0,A=y||l,C=t?A(d,_):n||f?A(d,0):void 0;_>S;S++)if((h||S in E)&&(b=x(v=E[S],S,w),e))if(t)C[S]=b;else if(b)switch(e){case 3:return!0;case 5:return v;case 6:return S;case 2:u(C,v)}else switch(e){case 4:return!1;case 7:u(C,v)}return p?-1:o||c?c:C}};e.exports={forEach:c(0),map:c(1),filter:c(2),some:c(3),every:c(4),find:c(5),findIndex:c(6),filterReject:c(7)}},67145:(e,t,n)=>{"use strict";var r=n(79730),o=n(74529),a=n(62435),i=n(10623),s=n(34194),l=Math.min,u=[].lastIndexOf,c=!!u&&1/[1].lastIndexOf(1,-0)<0,p=s("lastIndexOf"),f=c||!p;e.exports=f?function(e){if(c)return r(u,this,arguments)||0;var t=o(this),n=i(t),s=n-1;for(arguments.length>1&&(s=l(s,a(arguments[1]))),s<0&&(s=n+s);s>=0;s--)if(s in t&&t[s]===e)return s||0;return-1}:u},50568:(e,t,n)=>{var r=n(95981),o=n(99813),a=n(53385),i=o("species");e.exports=function(e){return a>=51||!r((function(){var t=[];return(t.constructor={})[i]=function(){return{foo:1}},1!==t[e](Boolean).foo}))}},34194:(e,t,n)=>{"use strict";var r=n(95981);e.exports=function(e,t){var n=[][e];return!!n&&r((function(){n.call(null,t||function(){return 1},1)}))}},46499:(e,t,n)=>{var r=n(24883),o=n(89678),a=n(37026),i=n(10623),s=TypeError,l=function(e){return function(t,n,l,u){r(n);var c=o(t),p=a(c),f=i(c),h=e?f-1:0,d=e?-1:1;if(l<2)for(;;){if(h in p){u=p[h],h+=d;break}if(h+=d,e?h<0:f<=h)throw s("Reduce of empty array with no initial value")}for(;e?h>=0:f>h;h+=d)h in p&&(u=n(u,p[h],h,c));return u}};e.exports={left:l(!1),right:l(!0)}},89779:(e,t,n)=>{"use strict";var r=n(55746),o=n(1052),a=TypeError,i=Object.getOwnPropertyDescriptor,s=r&&!function(){if(void 0!==this)return!0;try{Object.defineProperty([],"length",{writable:!1}).length=1}catch(e){return e instanceof TypeError}}();e.exports=s?function(e,t){if(o(e)&&!i(e,"length").writable)throw a("Cannot set read only .length");return e.length=t}:function(e,t){return e.length=t}},15790:(e,t,n)=>{var r=n(59413),o=n(10623),a=n(55449),i=Array,s=Math.max;e.exports=function(e,t,n){for(var l=o(e),u=r(t,l),c=r(void 0===n?l:n,l),p=i(s(c-u,0)),f=0;u{var r=n(95329);e.exports=r([].slice)},61388:(e,t,n)=>{var r=n(15790),o=Math.floor,a=function(e,t){var n=e.length,l=o(n/2);return n<8?i(e,t):s(e,a(r(e,0,l),t),a(r(e,l),t),t)},i=function(e,t){for(var n,r,o=e.length,a=1;a0;)e[r]=e[--r];r!==a++&&(e[r]=n)}return e},s=function(e,t,n,r){for(var o=t.length,a=n.length,i=0,s=0;i{var r=n(1052),o=n(24284),a=n(10941),i=n(99813)("species"),s=Array;e.exports=function(e){var t;return r(e)&&(t=e.constructor,(o(t)&&(t===s||r(t.prototype))||a(t)&&null===(t=t[i]))&&(t=void 0)),void 0===t?s:t}},64692:(e,t,n)=>{var r=n(5693);e.exports=function(e,t){return new(r(e))(0===t?0:t)}},75196:(e,t,n)=>{var r=n(96059),o=n(7609);e.exports=function(e,t,n,a){try{return a?t(r(n)[0],n[1]):t(n)}catch(t){o(e,"throw",t)}}},21385:(e,t,n)=>{var r=n(99813)("iterator"),o=!1;try{var a=0,i={next:function(){return{done:!!a++}},return:function(){o=!0}};i[r]=function(){return this},Array.from(i,(function(){throw 2}))}catch(e){}e.exports=function(e,t){if(!t&&!o)return!1;var n=!1;try{var a={};a[r]=function(){return{next:function(){return{done:n=!0}}}},e(a)}catch(e){}return n}},82532:(e,t,n)=>{var r=n(95329),o=r({}.toString),a=r("".slice);e.exports=function(e){return a(o(e),8,-1)}},9697:(e,t,n)=>{var r=n(22885),o=n(57475),a=n(82532),i=n(99813)("toStringTag"),s=Object,l="Arguments"==a(function(){return arguments}());e.exports=r?a:function(e){var t,n,r;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=function(e,t){try{return e[t]}catch(e){}}(t=s(e),i))?n:l?a(t):"Object"==(r=a(t))&&o(t.callee)?"Arguments":r}},85616:(e,t,n)=>{"use strict";var r=n(29290),o=n(29202),a=n(94380),i=n(86843),s=n(5743),l=n(82119),u=n(93091),c=n(75105),p=n(23538),f=n(94431),h=n(55746),d=n(21647).fastKey,m=n(45402),g=m.set,y=m.getterFor;e.exports={getConstructor:function(e,t,n,c){var p=e((function(e,o){s(e,f),g(e,{type:t,index:r(null),first:void 0,last:void 0,size:0}),h||(e.size=0),l(o)||u(o,e[c],{that:e,AS_ENTRIES:n})})),f=p.prototype,m=y(t),v=function(e,t,n){var r,o,a=m(e),i=b(e,t);return i?i.value=n:(a.last=i={index:o=d(t,!0),key:t,value:n,previous:r=a.last,next:void 0,removed:!1},a.first||(a.first=i),r&&(r.next=i),h?a.size++:e.size++,"F"!==o&&(a.index[o]=i)),e},b=function(e,t){var n,r=m(e),o=d(t);if("F"!==o)return r.index[o];for(n=r.first;n;n=n.next)if(n.key==t)return n};return a(f,{clear:function(){for(var e=m(this),t=e.index,n=e.first;n;)n.removed=!0,n.previous&&(n.previous=n.previous.next=void 0),delete t[n.index],n=n.next;e.first=e.last=void 0,h?e.size=0:this.size=0},delete:function(e){var t=this,n=m(t),r=b(t,e);if(r){var o=r.next,a=r.previous;delete n.index[r.index],r.removed=!0,a&&(a.next=o),o&&(o.previous=a),n.first==r&&(n.first=o),n.last==r&&(n.last=a),h?n.size--:t.size--}return!!r},forEach:function(e){for(var t,n=m(this),r=i(e,arguments.length>1?arguments[1]:void 0);t=t?t.next:n.first;)for(r(t.value,t.key,this);t&&t.removed;)t=t.previous},has:function(e){return!!b(this,e)}}),a(f,n?{get:function(e){var t=b(this,e);return t&&t.value},set:function(e,t){return v(this,0===e?0:e,t)}}:{add:function(e){return v(this,e=0===e?0:e,e)}}),h&&o(f,"size",{configurable:!0,get:function(){return m(this).size}}),p},setStrong:function(e,t,n){var r=t+" Iterator",o=y(t),a=y(r);c(e,t,(function(e,t){g(this,{type:r,target:e,state:o(e),kind:t,last:void 0})}),(function(){for(var e=a(this),t=e.kind,n=e.last;n&&n.removed;)n=n.previous;return e.target&&(e.last=n=n?n.next:e.state.first)?p("keys"==t?n.key:"values"==t?n.value:[n.key,n.value],!1):(e.target=void 0,p(void 0,!0))}),n?"entries":"values",!n,!0),f(t)}}},24683:(e,t,n)=>{"use strict";var r=n(76887),o=n(21899),a=n(21647),i=n(95981),s=n(32029),l=n(93091),u=n(5743),c=n(57475),p=n(10941),f=n(90904),h=n(65988).f,d=n(3610).forEach,m=n(55746),g=n(45402),y=g.set,v=g.getterFor;e.exports=function(e,t,n){var g,b=-1!==e.indexOf("Map"),w=-1!==e.indexOf("Weak"),E=b?"set":"add",x=o[e],_=x&&x.prototype,S={};if(m&&c(x)&&(w||_.forEach&&!i((function(){(new x).entries().next()})))){var A=(g=t((function(t,n){y(u(t,A),{type:e,collection:new x}),null!=n&&l(n,t[E],{that:t,AS_ENTRIES:b})}))).prototype,C=v(e);d(["add","clear","delete","forEach","get","has","set","keys","values","entries"],(function(e){var t="add"==e||"set"==e;!(e in _)||w&&"clear"==e||s(A,e,(function(n,r){var o=C(this).collection;if(!t&&w&&!p(n))return"get"==e&&void 0;var a=o[e](0===n?0:n,r);return t?this:a}))})),w||h(A,"size",{configurable:!0,get:function(){return C(this).collection.size}})}else g=n.getConstructor(t,e,b,E),a.enable();return f(g,e,!1,!0),S[e]=g,r({global:!0,forced:!0},S),w||n.setStrong(g,e,b),g}},23489:(e,t,n)=>{var r=n(90953),o=n(31136),a=n(49677),i=n(65988);e.exports=function(e,t,n){for(var s=o(t),l=i.f,u=a.f,c=0;c{var r=n(99813)("match");e.exports=function(e){var t=/./;try{"/./"[e](t)}catch(n){try{return t[r]=!1,"/./"[e](t)}catch(e){}}return!1}},64160:(e,t,n)=>{var r=n(95981);e.exports=!r((function(){function e(){}return e.prototype.constructor=null,Object.getPrototypeOf(new e)!==e.prototype}))},23538:e=>{e.exports=function(e,t){return{value:e,done:t}}},32029:(e,t,n)=>{var r=n(55746),o=n(65988),a=n(31887);e.exports=r?function(e,t,n){return o.f(e,t,a(1,n))}:function(e,t,n){return e[t]=n,e}},31887:e=>{e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},55449:(e,t,n)=>{"use strict";var r=n(83894),o=n(65988),a=n(31887);e.exports=function(e,t,n){var i=r(t);i in e?o.f(e,i,a(0,n)):e[i]=n}},29202:(e,t,n)=>{var r=n(65988);e.exports=function(e,t,n){return r.f(e,t,n)}},95929:(e,t,n)=>{var r=n(32029);e.exports=function(e,t,n,o){return o&&o.enumerable?e[t]=n:r(e,t,n),e}},94380:(e,t,n)=>{var r=n(95929);e.exports=function(e,t,n){for(var o in t)n&&n.unsafe&&e[o]?e[o]=t[o]:r(e,o,t[o],n);return e}},75609:(e,t,n)=>{var r=n(21899),o=Object.defineProperty;e.exports=function(e,t){try{o(r,e,{value:t,configurable:!0,writable:!0})}catch(n){r[e]=t}return t}},15863:(e,t,n)=>{"use strict";var r=n(69826),o=TypeError;e.exports=function(e,t){if(!delete e[t])throw o("Cannot delete property "+r(t)+" of "+r(e))}},55746:(e,t,n)=>{var r=n(95981);e.exports=!r((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},76616:e=>{var t="object"==typeof document&&document.all,n=void 0===t&&void 0!==t;e.exports={all:t,IS_HTMLDDA:n}},61333:(e,t,n)=>{var r=n(21899),o=n(10941),a=r.document,i=o(a)&&o(a.createElement);e.exports=function(e){return i?a.createElement(e):{}}},66796:e=>{var t=TypeError;e.exports=function(e){if(e>9007199254740991)throw t("Maximum allowed index exceeded");return e}},63281:e=>{e.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},34342:(e,t,n)=>{var r=n(2861).match(/firefox\/(\d+)/i);e.exports=!!r&&+r[1]},23321:(e,t,n)=>{var r=n(48501),o=n(6049);e.exports=!r&&!o&&"object"==typeof window&&"object"==typeof document},56491:e=>{e.exports="function"==typeof Bun&&Bun&&"string"==typeof Bun.version},48501:e=>{e.exports="object"==typeof Deno&&Deno&&"object"==typeof Deno.version},81046:(e,t,n)=>{var r=n(2861);e.exports=/MSIE|Trident/.test(r)},4470:(e,t,n)=>{var r=n(2861);e.exports=/ipad|iphone|ipod/i.test(r)&&"undefined"!=typeof Pebble},22749:(e,t,n)=>{var r=n(2861);e.exports=/(?:ipad|iphone|ipod).*applewebkit/i.test(r)},6049:(e,t,n)=>{var r=n(34155),o=n(82532);e.exports=void 0!==r&&"process"==o(r)},58045:(e,t,n)=>{var r=n(2861);e.exports=/web0s(?!.*chrome)/i.test(r)},2861:e=>{e.exports="undefined"!=typeof navigator&&String(navigator.userAgent)||""},53385:(e,t,n)=>{var r,o,a=n(21899),i=n(2861),s=a.process,l=a.Deno,u=s&&s.versions||l&&l.version,c=u&&u.v8;c&&(o=(r=c.split("."))[0]>0&&r[0]<4?1:+(r[0]+r[1])),!o&&i&&(!(r=i.match(/Edge\/(\d+)/))||r[1]>=74)&&(r=i.match(/Chrome\/(\d+)/))&&(o=+r[1]),e.exports=o},18938:(e,t,n)=>{var r=n(2861).match(/AppleWebKit\/(\d+)\./);e.exports=!!r&&+r[1]},35703:(e,t,n)=>{var r=n(54058);e.exports=function(e){return r[e+"Prototype"]}},56759:e=>{e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},53995:(e,t,n)=>{var r=n(95329),o=Error,a=r("".replace),i=String(o("zxcasd").stack),s=/\n\s*at [^:]*:[^\n]*/,l=s.test(i);e.exports=function(e,t){if(l&&"string"==typeof e&&!o.prepareStackTrace)for(;t--;)e=a(e,s,"");return e}},79585:(e,t,n)=>{var r=n(32029),o=n(53995),a=n(18780),i=Error.captureStackTrace;e.exports=function(e,t,n,s){a&&(i?i(e,t):r(e,"stack",o(n,s)))}},18780:(e,t,n)=>{var r=n(95981),o=n(31887);e.exports=!r((function(){var e=Error("a");return!("stack"in e)||(Object.defineProperty(e,"stack",o(1,7)),7!==e.stack)}))},76887:(e,t,n)=>{"use strict";var r=n(21899),o=n(79730),a=n(97484),i=n(57475),s=n(49677).f,l=n(37252),u=n(54058),c=n(86843),p=n(32029),f=n(90953),h=function(e){var t=function(n,r,a){if(this instanceof t){switch(arguments.length){case 0:return new e;case 1:return new e(n);case 2:return new e(n,r)}return new e(n,r,a)}return o(e,this,arguments)};return t.prototype=e.prototype,t};e.exports=function(e,t){var n,o,d,m,g,y,v,b,w,E=e.target,x=e.global,_=e.stat,S=e.proto,A=x?r:_?r[E]:(r[E]||{}).prototype,C=x?u:u[E]||p(u,E,{})[E],k=C.prototype;for(m in t)o=!(n=l(x?m:E+(_?".":"#")+m,e.forced))&&A&&f(A,m),y=C[m],o&&(v=e.dontCallGetSet?(w=s(A,m))&&w.value:A[m]),g=o&&v?v:t[m],o&&typeof y==typeof g||(b=e.bind&&o?c(g,r):e.wrap&&o?h(g):S&&i(g)?a(g):g,(e.sham||g&&g.sham||y&&y.sham)&&p(b,"sham",!0),p(C,m,b),S&&(f(u,d=E+"Prototype")||p(u,d,{}),p(u[d],m,g),e.real&&k&&(n||!k[m])&&p(k,m,g)))}},95981:e=>{e.exports=function(e){try{return!!e()}catch(e){return!0}}},45602:(e,t,n)=>{var r=n(95981);e.exports=!r((function(){return Object.isExtensible(Object.preventExtensions({}))}))},79730:(e,t,n)=>{var r=n(18285),o=Function.prototype,a=o.apply,i=o.call;e.exports="object"==typeof Reflect&&Reflect.apply||(r?i.bind(a):function(){return i.apply(a,arguments)})},86843:(e,t,n)=>{var r=n(97484),o=n(24883),a=n(18285),i=r(r.bind);e.exports=function(e,t){return o(e),void 0===t?e:a?i(e,t):function(){return e.apply(t,arguments)}}},18285:(e,t,n)=>{var r=n(95981);e.exports=!r((function(){var e=function(){}.bind();return"function"!=typeof e||e.hasOwnProperty("prototype")}))},98308:(e,t,n)=>{"use strict";var r=n(95329),o=n(24883),a=n(10941),i=n(90953),s=n(93765),l=n(18285),u=Function,c=r([].concat),p=r([].join),f={},h=function(e,t,n){if(!i(f,t)){for(var r=[],o=0;o{var r=n(18285),o=Function.prototype.call;e.exports=r?o.bind(o):function(){return o.apply(o,arguments)}},79417:(e,t,n)=>{var r=n(55746),o=n(90953),a=Function.prototype,i=r&&Object.getOwnPropertyDescriptor,s=o(a,"name"),l=s&&"something"===function(){}.name,u=s&&(!r||r&&i(a,"name").configurable);e.exports={EXISTS:s,PROPER:l,CONFIGURABLE:u}},45526:(e,t,n)=>{var r=n(95329),o=n(24883);e.exports=function(e,t,n){try{return r(o(Object.getOwnPropertyDescriptor(e,t)[n]))}catch(e){}}},97484:(e,t,n)=>{var r=n(82532),o=n(95329);e.exports=function(e){if("Function"===r(e))return o(e)}},95329:(e,t,n)=>{var r=n(18285),o=Function.prototype,a=o.call,i=r&&o.bind.bind(a,a);e.exports=r?i:function(e){return function(){return a.apply(e,arguments)}}},626:(e,t,n)=>{var r=n(54058),o=n(21899),a=n(57475),i=function(e){return a(e)?e:void 0};e.exports=function(e,t){return arguments.length<2?i(r[e])||i(o[e]):r[e]&&r[e][t]||o[e]&&o[e][t]}},22902:(e,t,n)=>{var r=n(9697),o=n(14229),a=n(82119),i=n(12077),s=n(99813)("iterator");e.exports=function(e){if(!a(e))return o(e,s)||o(e,"@@iterator")||i[r(e)]}},53476:(e,t,n)=>{var r=n(78834),o=n(24883),a=n(96059),i=n(69826),s=n(22902),l=TypeError;e.exports=function(e,t){var n=arguments.length<2?s(e):t;if(o(n))return a(r(n,e));throw l(i(e)+" is not iterable")}},33323:(e,t,n)=>{var r=n(95329),o=n(1052),a=n(57475),i=n(82532),s=n(85803),l=r([].push);e.exports=function(e){if(a(e))return e;if(o(e)){for(var t=e.length,n=[],r=0;r{var r=n(24883),o=n(82119);e.exports=function(e,t){var n=e[t];return o(n)?void 0:r(n)}},21899:(e,t,n)=>{var r=function(e){return e&&e.Math==Math&&e};e.exports=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof n.g&&n.g)||function(){return this}()||Function("return this")()},90953:(e,t,n)=>{var r=n(95329),o=n(89678),a=r({}.hasOwnProperty);e.exports=Object.hasOwn||function(e,t){return a(o(e),t)}},27748:e=>{e.exports={}},34845:e=>{e.exports=function(e,t){try{1==arguments.length?console.error(e):console.error(e,t)}catch(e){}}},15463:(e,t,n)=>{var r=n(626);e.exports=r("document","documentElement")},2840:(e,t,n)=>{var r=n(55746),o=n(95981),a=n(61333);e.exports=!r&&!o((function(){return 7!=Object.defineProperty(a("div"),"a",{get:function(){return 7}}).a}))},37026:(e,t,n)=>{var r=n(95329),o=n(95981),a=n(82532),i=Object,s=r("".split);e.exports=o((function(){return!i("z").propertyIsEnumerable(0)}))?function(e){return"String"==a(e)?s(e,""):i(e)}:i},81302:(e,t,n)=>{var r=n(95329),o=n(57475),a=n(63030),i=r(Function.toString);o(a.inspectSource)||(a.inspectSource=function(e){return i(e)}),e.exports=a.inspectSource},53794:(e,t,n)=>{var r=n(10941),o=n(32029);e.exports=function(e,t){r(t)&&"cause"in t&&o(e,"cause",t.cause)}},21647:(e,t,n)=>{var r=n(76887),o=n(95329),a=n(27748),i=n(10941),s=n(90953),l=n(65988).f,u=n(10946),c=n(684),p=n(91584),f=n(99418),h=n(45602),d=!1,m=f("meta"),g=0,y=function(e){l(e,m,{value:{objectID:"O"+g++,weakData:{}}})},v=e.exports={enable:function(){v.enable=function(){},d=!0;var e=u.f,t=o([].splice),n={};n[m]=1,e(n).length&&(u.f=function(n){for(var r=e(n),o=0,a=r.length;o{var r,o,a,i=n(47093),s=n(21899),l=n(10941),u=n(32029),c=n(90953),p=n(63030),f=n(44262),h=n(27748),d="Object already initialized",m=s.TypeError,g=s.WeakMap;if(i||p.state){var y=p.state||(p.state=new g);y.get=y.get,y.has=y.has,y.set=y.set,r=function(e,t){if(y.has(e))throw m(d);return t.facade=e,y.set(e,t),t},o=function(e){return y.get(e)||{}},a=function(e){return y.has(e)}}else{var v=f("state");h[v]=!0,r=function(e,t){if(c(e,v))throw m(d);return t.facade=e,u(e,v,t),t},o=function(e){return c(e,v)?e[v]:{}},a=function(e){return c(e,v)}}e.exports={set:r,get:o,has:a,enforce:function(e){return a(e)?o(e):r(e,{})},getterFor:function(e){return function(t){var n;if(!l(t)||(n=o(t)).type!==e)throw m("Incompatible receiver, "+e+" required");return n}}}},6782:(e,t,n)=>{var r=n(99813),o=n(12077),a=r("iterator"),i=Array.prototype;e.exports=function(e){return void 0!==e&&(o.Array===e||i[a]===e)}},1052:(e,t,n)=>{var r=n(82532);e.exports=Array.isArray||function(e){return"Array"==r(e)}},57475:(e,t,n)=>{var r=n(76616),o=r.all;e.exports=r.IS_HTMLDDA?function(e){return"function"==typeof e||e===o}:function(e){return"function"==typeof e}},24284:(e,t,n)=>{var r=n(95329),o=n(95981),a=n(57475),i=n(9697),s=n(626),l=n(81302),u=function(){},c=[],p=s("Reflect","construct"),f=/^\s*(?:class|function)\b/,h=r(f.exec),d=!f.exec(u),m=function(e){if(!a(e))return!1;try{return p(u,c,e),!0}catch(e){return!1}},g=function(e){if(!a(e))return!1;switch(i(e)){case"AsyncFunction":case"GeneratorFunction":case"AsyncGeneratorFunction":return!1}try{return d||!!h(f,l(e))}catch(e){return!0}};g.sham=!0,e.exports=!p||o((function(){var e;return m(m.call)||!m(Object)||!m((function(){e=!0}))||e}))?g:m},37252:(e,t,n)=>{var r=n(95981),o=n(57475),a=/#|\.prototype\./,i=function(e,t){var n=l[s(e)];return n==c||n!=u&&(o(t)?r(t):!!t)},s=i.normalize=function(e){return String(e).replace(a,".").toLowerCase()},l=i.data={},u=i.NATIVE="N",c=i.POLYFILL="P";e.exports=i},82119:e=>{e.exports=function(e){return null==e}},10941:(e,t,n)=>{var r=n(57475),o=n(76616),a=o.all;e.exports=o.IS_HTMLDDA?function(e){return"object"==typeof e?null!==e:r(e)||e===a}:function(e){return"object"==typeof e?null!==e:r(e)}},82529:e=>{e.exports=!0},60685:(e,t,n)=>{var r=n(10941),o=n(82532),a=n(99813)("match");e.exports=function(e){var t;return r(e)&&(void 0!==(t=e[a])?!!t:"RegExp"==o(e))}},56664:(e,t,n)=>{var r=n(626),o=n(57475),a=n(7046),i=n(32302),s=Object;e.exports=i?function(e){return"symbol"==typeof e}:function(e){var t=r("Symbol");return o(t)&&a(t.prototype,s(e))}},93091:(e,t,n)=>{var r=n(86843),o=n(78834),a=n(96059),i=n(69826),s=n(6782),l=n(10623),u=n(7046),c=n(53476),p=n(22902),f=n(7609),h=TypeError,d=function(e,t){this.stopped=e,this.result=t},m=d.prototype;e.exports=function(e,t,n){var g,y,v,b,w,E,x,_=n&&n.that,S=!(!n||!n.AS_ENTRIES),A=!(!n||!n.IS_RECORD),C=!(!n||!n.IS_ITERATOR),k=!(!n||!n.INTERRUPTED),O=r(t,_),j=function(e){return g&&f(g,"normal",e),new d(!0,e)},I=function(e){return S?(a(e),k?O(e[0],e[1],j):O(e[0],e[1])):k?O(e,j):O(e)};if(A)g=e.iterator;else if(C)g=e;else{if(!(y=p(e)))throw h(i(e)+" is not iterable");if(s(y)){for(v=0,b=l(e);b>v;v++)if((w=I(e[v]))&&u(m,w))return w;return new d(!1)}g=c(e,y)}for(E=A?e.next:g.next;!(x=o(E,g)).done;){try{w=I(x.value)}catch(e){f(g,"throw",e)}if("object"==typeof w&&w&&u(m,w))return w}return new d(!1)}},7609:(e,t,n)=>{var r=n(78834),o=n(96059),a=n(14229);e.exports=function(e,t,n){var i,s;o(e);try{if(!(i=a(e,"return"))){if("throw"===t)throw n;return n}i=r(i,e)}catch(e){s=!0,i=e}if("throw"===t)throw n;if(s)throw i;return o(i),n}},53847:(e,t,n)=>{"use strict";var r=n(35143).IteratorPrototype,o=n(29290),a=n(31887),i=n(90904),s=n(12077),l=function(){return this};e.exports=function(e,t,n,u){var c=t+" Iterator";return e.prototype=o(r,{next:a(+!u,n)}),i(e,c,!1,!0),s[c]=l,e}},75105:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),a=n(82529),i=n(79417),s=n(57475),l=n(53847),u=n(249),c=n(88929),p=n(90904),f=n(32029),h=n(95929),d=n(99813),m=n(12077),g=n(35143),y=i.PROPER,v=i.CONFIGURABLE,b=g.IteratorPrototype,w=g.BUGGY_SAFARI_ITERATORS,E=d("iterator"),x="keys",_="values",S="entries",A=function(){return this};e.exports=function(e,t,n,i,d,g,C){l(n,t,i);var k,O,j,I=function(e){if(e===d&&M)return M;if(!w&&e in P)return P[e];switch(e){case x:case _:case S:return function(){return new n(this,e)}}return function(){return new n(this)}},T=t+" Iterator",N=!1,P=e.prototype,R=P[E]||P["@@iterator"]||d&&P[d],M=!w&&R||I(d),D="Array"==t&&P.entries||R;if(D&&(k=u(D.call(new e)))!==Object.prototype&&k.next&&(a||u(k)===b||(c?c(k,b):s(k[E])||h(k,E,A)),p(k,T,!0,!0),a&&(m[T]=A)),y&&d==_&&R&&R.name!==_&&(!a&&v?f(P,"name",_):(N=!0,M=function(){return o(R,this)})),d)if(O={values:I(_),keys:g?M:I(x),entries:I(S)},C)for(j in O)(w||N||!(j in P))&&h(P,j,O[j]);else r({target:t,proto:!0,forced:w||N},O);return a&&!C||P[E]===M||h(P,E,M,{name:d}),m[t]=M,O}},35143:(e,t,n)=>{"use strict";var r,o,a,i=n(95981),s=n(57475),l=n(10941),u=n(29290),c=n(249),p=n(95929),f=n(99813),h=n(82529),d=f("iterator"),m=!1;[].keys&&("next"in(a=[].keys())?(o=c(c(a)))!==Object.prototype&&(r=o):m=!0),!l(r)||i((function(){var e={};return r[d].call(e)!==e}))?r={}:h&&(r=u(r)),s(r[d])||p(r,d,(function(){return this})),e.exports={IteratorPrototype:r,BUGGY_SAFARI_ITERATORS:m}},12077:e=>{e.exports={}},10623:(e,t,n)=>{var r=n(43057);e.exports=function(e){return r(e.length)}},35331:e=>{var t=Math.ceil,n=Math.floor;e.exports=Math.trunc||function(e){var r=+e;return(r>0?n:t)(r)}},66132:(e,t,n)=>{var r,o,a,i,s,l=n(21899),u=n(86843),c=n(49677).f,p=n(42941).set,f=n(18397),h=n(22749),d=n(4470),m=n(58045),g=n(6049),y=l.MutationObserver||l.WebKitMutationObserver,v=l.document,b=l.process,w=l.Promise,E=c(l,"queueMicrotask"),x=E&&E.value;if(!x){var _=new f,S=function(){var e,t;for(g&&(e=b.domain)&&e.exit();t=_.get();)try{t()}catch(e){throw _.head&&r(),e}e&&e.enter()};h||g||m||!y||!v?!d&&w&&w.resolve?((i=w.resolve(void 0)).constructor=w,s=u(i.then,i),r=function(){s(S)}):g?r=function(){b.nextTick(S)}:(p=u(p,l),r=function(){p(S)}):(o=!0,a=v.createTextNode(""),new y(S).observe(a,{characterData:!0}),r=function(){a.data=o=!o}),x=function(e){_.head||r(),_.add(e)}}e.exports=x},69520:(e,t,n)=>{"use strict";var r=n(24883),o=TypeError,a=function(e){var t,n;this.promise=new e((function(e,r){if(void 0!==t||void 0!==n)throw o("Bad Promise constructor");t=e,n=r})),this.resolve=r(t),this.reject=r(n)};e.exports.f=function(e){return new a(e)}},14649:(e,t,n)=>{var r=n(85803);e.exports=function(e,t){return void 0===e?arguments.length<2?"":t:r(e)}},70344:(e,t,n)=>{var r=n(60685),o=TypeError;e.exports=function(e){if(r(e))throw o("The method doesn't accept regular expressions");return e}},24420:(e,t,n)=>{"use strict";var r=n(55746),o=n(95329),a=n(78834),i=n(95981),s=n(14771),l=n(87857),u=n(36760),c=n(89678),p=n(37026),f=Object.assign,h=Object.defineProperty,d=o([].concat);e.exports=!f||i((function(){if(r&&1!==f({b:1},f(h({},"a",{enumerable:!0,get:function(){h(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var e={},t={},n=Symbol(),o="abcdefghijklmnopqrst";return e[n]=7,o.split("").forEach((function(e){t[e]=e})),7!=f({},e)[n]||s(f({},t)).join("")!=o}))?function(e,t){for(var n=c(e),o=arguments.length,i=1,f=l.f,h=u.f;o>i;)for(var m,g=p(arguments[i++]),y=f?d(s(g),f(g)):s(g),v=y.length,b=0;v>b;)m=y[b++],r&&!a(h,g,m)||(n[m]=g[m]);return n}:f},29290:(e,t,n)=>{var r,o=n(96059),a=n(59938),i=n(56759),s=n(27748),l=n(15463),u=n(61333),c=n(44262),p="prototype",f="script",h=c("IE_PROTO"),d=function(){},m=function(e){return"<"+f+">"+e+""},g=function(e){e.write(m("")),e.close();var t=e.parentWindow.Object;return e=null,t},y=function(){try{r=new ActiveXObject("htmlfile")}catch(e){}var e,t,n;y="undefined"!=typeof document?document.domain&&r?g(r):(t=u("iframe"),n="java"+f+":",t.style.display="none",l.appendChild(t),t.src=String(n),(e=t.contentWindow.document).open(),e.write(m("document.F=Object")),e.close(),e.F):g(r);for(var o=i.length;o--;)delete y[p][i[o]];return y()};s[h]=!0,e.exports=Object.create||function(e,t){var n;return null!==e?(d[p]=o(e),n=new d,d[p]=null,n[h]=e):n=y(),void 0===t?n:a.f(n,t)}},59938:(e,t,n)=>{var r=n(55746),o=n(83937),a=n(65988),i=n(96059),s=n(74529),l=n(14771);t.f=r&&!o?Object.defineProperties:function(e,t){i(e);for(var n,r=s(t),o=l(t),u=o.length,c=0;u>c;)a.f(e,n=o[c++],r[n]);return e}},65988:(e,t,n)=>{var r=n(55746),o=n(2840),a=n(83937),i=n(96059),s=n(83894),l=TypeError,u=Object.defineProperty,c=Object.getOwnPropertyDescriptor,p="enumerable",f="configurable",h="writable";t.f=r?a?function(e,t,n){if(i(e),t=s(t),i(n),"function"==typeof e&&"prototype"===t&&"value"in n&&h in n&&!n[h]){var r=c(e,t);r&&r[h]&&(e[t]=n.value,n={configurable:f in n?n[f]:r[f],enumerable:p in n?n[p]:r[p],writable:!1})}return u(e,t,n)}:u:function(e,t,n){if(i(e),t=s(t),i(n),o)try{return u(e,t,n)}catch(e){}if("get"in n||"set"in n)throw l("Accessors not supported");return"value"in n&&(e[t]=n.value),e}},49677:(e,t,n)=>{var r=n(55746),o=n(78834),a=n(36760),i=n(31887),s=n(74529),l=n(83894),u=n(90953),c=n(2840),p=Object.getOwnPropertyDescriptor;t.f=r?p:function(e,t){if(e=s(e),t=l(t),c)try{return p(e,t)}catch(e){}if(u(e,t))return i(!o(a.f,e,t),e[t])}},684:(e,t,n)=>{var r=n(82532),o=n(74529),a=n(10946).f,i=n(15790),s="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[];e.exports.f=function(e){return s&&"Window"==r(e)?function(e){try{return a(e)}catch(e){return i(s)}}(e):a(o(e))}},10946:(e,t,n)=>{var r=n(55629),o=n(56759).concat("length","prototype");t.f=Object.getOwnPropertyNames||function(e){return r(e,o)}},87857:(e,t)=>{t.f=Object.getOwnPropertySymbols},249:(e,t,n)=>{var r=n(90953),o=n(57475),a=n(89678),i=n(44262),s=n(64160),l=i("IE_PROTO"),u=Object,c=u.prototype;e.exports=s?u.getPrototypeOf:function(e){var t=a(e);if(r(t,l))return t[l];var n=t.constructor;return o(n)&&t instanceof n?n.prototype:t instanceof u?c:null}},91584:(e,t,n)=>{var r=n(95981),o=n(10941),a=n(82532),i=n(97135),s=Object.isExtensible,l=r((function(){s(1)}));e.exports=l||i?function(e){return!!o(e)&&((!i||"ArrayBuffer"!=a(e))&&(!s||s(e)))}:s},7046:(e,t,n)=>{var r=n(95329);e.exports=r({}.isPrototypeOf)},55629:(e,t,n)=>{var r=n(95329),o=n(90953),a=n(74529),i=n(31692).indexOf,s=n(27748),l=r([].push);e.exports=function(e,t){var n,r=a(e),u=0,c=[];for(n in r)!o(s,n)&&o(r,n)&&l(c,n);for(;t.length>u;)o(r,n=t[u++])&&(~i(c,n)||l(c,n));return c}},14771:(e,t,n)=>{var r=n(55629),o=n(56759);e.exports=Object.keys||function(e){return r(e,o)}},36760:(e,t)=>{"use strict";var n={}.propertyIsEnumerable,r=Object.getOwnPropertyDescriptor,o=r&&!n.call({1:2},1);t.f=o?function(e){var t=r(this,e);return!!t&&t.enumerable}:n},88929:(e,t,n)=>{var r=n(45526),o=n(96059),a=n(11851);e.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var e,t=!1,n={};try{(e=r(Object.prototype,"__proto__","set"))(n,[]),t=n instanceof Array}catch(e){}return function(n,r){return o(n),a(r),t?e(n,r):n.__proto__=r,n}}():void 0)},88810:(e,t,n)=>{var r=n(55746),o=n(95329),a=n(14771),i=n(74529),s=o(n(36760).f),l=o([].push),u=function(e){return function(t){for(var n,o=i(t),u=a(o),c=u.length,p=0,f=[];c>p;)n=u[p++],r&&!s(o,n)||l(f,e?[n,o[n]]:o[n]);return f}};e.exports={entries:u(!0),values:u(!1)}},95623:(e,t,n)=>{"use strict";var r=n(22885),o=n(9697);e.exports=r?{}.toString:function(){return"[object "+o(this)+"]"}},39811:(e,t,n)=>{var r=n(78834),o=n(57475),a=n(10941),i=TypeError;e.exports=function(e,t){var n,s;if("string"===t&&o(n=e.toString)&&!a(s=r(n,e)))return s;if(o(n=e.valueOf)&&!a(s=r(n,e)))return s;if("string"!==t&&o(n=e.toString)&&!a(s=r(n,e)))return s;throw i("Can't convert object to primitive value")}},31136:(e,t,n)=>{var r=n(626),o=n(95329),a=n(10946),i=n(87857),s=n(96059),l=o([].concat);e.exports=r("Reflect","ownKeys")||function(e){var t=a.f(s(e)),n=i.f;return n?l(t,n(e)):t}},54058:e=>{e.exports={}},40002:e=>{e.exports=function(e){try{return{error:!1,value:e()}}catch(e){return{error:!0,value:e}}}},67742:(e,t,n)=>{var r=n(21899),o=n(6991),a=n(57475),i=n(37252),s=n(81302),l=n(99813),u=n(23321),c=n(48501),p=n(82529),f=n(53385),h=o&&o.prototype,d=l("species"),m=!1,g=a(r.PromiseRejectionEvent),y=i("Promise",(function(){var e=s(o),t=e!==String(o);if(!t&&66===f)return!0;if(p&&(!h.catch||!h.finally))return!0;if(!f||f<51||!/native code/.test(e)){var n=new o((function(e){e(1)})),r=function(e){e((function(){}),(function(){}))};if((n.constructor={})[d]=r,!(m=n.then((function(){}))instanceof r))return!0}return!t&&(u||c)&&!g}));e.exports={CONSTRUCTOR:y,REJECTION_EVENT:g,SUBCLASSING:m}},6991:(e,t,n)=>{var r=n(21899);e.exports=r.Promise},56584:(e,t,n)=>{var r=n(96059),o=n(10941),a=n(69520);e.exports=function(e,t){if(r(e),o(t)&&t.constructor===e)return t;var n=a.f(e);return(0,n.resolve)(t),n.promise}},31542:(e,t,n)=>{var r=n(6991),o=n(21385),a=n(67742).CONSTRUCTOR;e.exports=a||!o((function(e){r.all(e).then(void 0,(function(){}))}))},18397:e=>{var t=function(){this.head=null,this.tail=null};t.prototype={add:function(e){var t={item:e,next:null},n=this.tail;n?n.next=t:this.head=t,this.tail=t},get:function(){var e=this.head;if(e)return null===(this.head=e.next)&&(this.tail=null),e.item}},e.exports=t},48219:(e,t,n)=>{var r=n(82119),o=TypeError;e.exports=function(e){if(r(e))throw o("Can't call method on "+e);return e}},37620:(e,t,n)=>{"use strict";var r,o=n(21899),a=n(79730),i=n(57475),s=n(56491),l=n(2861),u=n(93765),c=n(18348),p=o.Function,f=/MSIE .\./.test(l)||s&&((r=o.Bun.version.split(".")).length<3||0==r[0]&&(r[1]<3||3==r[1]&&0==r[2]));e.exports=function(e,t){var n=t?2:1;return f?function(r,o){var s=c(arguments.length,1)>n,l=i(r)?r:p(r),f=s?u(arguments,n):[],h=s?function(){a(l,this,f)}:l;return t?e(h,o):e(h)}:e}},94431:(e,t,n)=>{"use strict";var r=n(626),o=n(29202),a=n(99813),i=n(55746),s=a("species");e.exports=function(e){var t=r(e);i&&t&&!t[s]&&o(t,s,{configurable:!0,get:function(){return this}})}},90904:(e,t,n)=>{var r=n(22885),o=n(65988).f,a=n(32029),i=n(90953),s=n(95623),l=n(99813)("toStringTag");e.exports=function(e,t,n,u){if(e){var c=n?e:e.prototype;i(c,l)||o(c,l,{configurable:!0,value:t}),u&&!r&&a(c,"toString",s)}}},44262:(e,t,n)=>{var r=n(68726),o=n(99418),a=r("keys");e.exports=function(e){return a[e]||(a[e]=o(e))}},63030:(e,t,n)=>{var r=n(21899),o=n(75609),a="__core-js_shared__",i=r[a]||o(a,{});e.exports=i},68726:(e,t,n)=>{var r=n(82529),o=n(63030);(e.exports=function(e,t){return o[e]||(o[e]=void 0!==t?t:{})})("versions",[]).push({version:"3.28.0",mode:r?"pure":"global",copyright:"© 2014-2023 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.28.0/LICENSE",source:"https://github.com/zloirock/core-js"})},70487:(e,t,n)=>{var r=n(96059),o=n(174),a=n(82119),i=n(99813)("species");e.exports=function(e,t){var n,s=r(e).constructor;return void 0===s||a(n=r(s)[i])?t:o(n)}},64620:(e,t,n)=>{var r=n(95329),o=n(62435),a=n(85803),i=n(48219),s=r("".charAt),l=r("".charCodeAt),u=r("".slice),c=function(e){return function(t,n){var r,c,p=a(i(t)),f=o(n),h=p.length;return f<0||f>=h?e?"":void 0:(r=l(p,f))<55296||r>56319||f+1===h||(c=l(p,f+1))<56320||c>57343?e?s(p,f):r:e?u(p,f,f+2):c-56320+(r-55296<<10)+65536}};e.exports={codeAt:c(!1),charAt:c(!0)}},73291:(e,t,n)=>{var r=n(95329),o=2147483647,a=/[^\0-\u007E]/,i=/[.\u3002\uFF0E\uFF61]/g,s="Overflow: input needs wider integers to process",l=RangeError,u=r(i.exec),c=Math.floor,p=String.fromCharCode,f=r("".charCodeAt),h=r([].join),d=r([].push),m=r("".replace),g=r("".split),y=r("".toLowerCase),v=function(e){return e+22+75*(e<26)},b=function(e,t,n){var r=0;for(e=n?c(e/700):e>>1,e+=c(e/t);e>455;)e=c(e/35),r+=36;return c(r+36*e/(e+38))},w=function(e){var t=[];e=function(e){for(var t=[],n=0,r=e.length;n=55296&&o<=56319&&n=i&&rc((o-u)/E))throw l(s);for(u+=(w-i)*E,i=w,n=0;no)throw l(s);if(r==i){for(var x=u,_=36;;){var S=_<=m?1:_>=m+26?26:_-m;if(x{"use strict";var r=n(62435),o=n(85803),a=n(48219),i=RangeError;e.exports=function(e){var t=o(a(this)),n="",s=r(e);if(s<0||s==1/0)throw i("Wrong number of repetitions");for(;s>0;(s>>>=1)&&(t+=t))1&s&&(n+=t);return n}},93093:(e,t,n)=>{var r=n(79417).PROPER,o=n(95981),a=n(73483);e.exports=function(e){return o((function(){return!!a[e]()||"​…᠎"!=="​…᠎"[e]()||r&&a[e].name!==e}))}},74853:(e,t,n)=>{var r=n(95329),o=n(48219),a=n(85803),i=n(73483),s=r("".replace),l=RegExp("^["+i+"]+"),u=RegExp("(^|[^"+i+"])["+i+"]+$"),c=function(e){return function(t){var n=a(o(t));return 1&e&&(n=s(n,l,"")),2&e&&(n=s(n,u,"$1")),n}};e.exports={start:c(1),end:c(2),trim:c(3)}},63405:(e,t,n)=>{var r=n(53385),o=n(95981);e.exports=!!Object.getOwnPropertySymbols&&!o((function(){var e=Symbol();return!String(e)||!(Object(e)instanceof Symbol)||!Symbol.sham&&r&&r<41}))},29630:(e,t,n)=>{var r=n(78834),o=n(626),a=n(99813),i=n(95929);e.exports=function(){var e=o("Symbol"),t=e&&e.prototype,n=t&&t.valueOf,s=a("toPrimitive");t&&!t[s]&&i(t,s,(function(e){return r(n,this)}),{arity:1})}},34680:(e,t,n)=>{var r=n(63405);e.exports=r&&!!Symbol.for&&!!Symbol.keyFor},42941:(e,t,n)=>{var r,o,a,i,s=n(21899),l=n(79730),u=n(86843),c=n(57475),p=n(90953),f=n(95981),h=n(15463),d=n(93765),m=n(61333),g=n(18348),y=n(22749),v=n(6049),b=s.setImmediate,w=s.clearImmediate,E=s.process,x=s.Dispatch,_=s.Function,S=s.MessageChannel,A=s.String,C=0,k={},O="onreadystatechange";f((function(){r=s.location}));var j=function(e){if(p(k,e)){var t=k[e];delete k[e],t()}},I=function(e){return function(){j(e)}},T=function(e){j(e.data)},N=function(e){s.postMessage(A(e),r.protocol+"//"+r.host)};b&&w||(b=function(e){g(arguments.length,1);var t=c(e)?e:_(e),n=d(arguments,1);return k[++C]=function(){l(t,void 0,n)},o(C),C},w=function(e){delete k[e]},v?o=function(e){E.nextTick(I(e))}:x&&x.now?o=function(e){x.now(I(e))}:S&&!y?(i=(a=new S).port2,a.port1.onmessage=T,o=u(i.postMessage,i)):s.addEventListener&&c(s.postMessage)&&!s.importScripts&&r&&"file:"!==r.protocol&&!f(N)?(o=N,s.addEventListener("message",T,!1)):o=O in m("script")?function(e){h.appendChild(m("script"))[O]=function(){h.removeChild(this),j(e)}}:function(e){setTimeout(I(e),0)}),e.exports={set:b,clear:w}},59413:(e,t,n)=>{var r=n(62435),o=Math.max,a=Math.min;e.exports=function(e,t){var n=r(e);return n<0?o(n+t,0):a(n,t)}},74529:(e,t,n)=>{var r=n(37026),o=n(48219);e.exports=function(e){return r(o(e))}},62435:(e,t,n)=>{var r=n(35331);e.exports=function(e){var t=+e;return t!=t||0===t?0:r(t)}},43057:(e,t,n)=>{var r=n(62435),o=Math.min;e.exports=function(e){return e>0?o(r(e),9007199254740991):0}},89678:(e,t,n)=>{var r=n(48219),o=Object;e.exports=function(e){return o(r(e))}},46935:(e,t,n)=>{var r=n(78834),o=n(10941),a=n(56664),i=n(14229),s=n(39811),l=n(99813),u=TypeError,c=l("toPrimitive");e.exports=function(e,t){if(!o(e)||a(e))return e;var n,l=i(e,c);if(l){if(void 0===t&&(t="default"),n=r(l,e,t),!o(n)||a(n))return n;throw u("Can't convert object to primitive value")}return void 0===t&&(t="number"),s(e,t)}},83894:(e,t,n)=>{var r=n(46935),o=n(56664);e.exports=function(e){var t=r(e,"string");return o(t)?t:t+""}},22885:(e,t,n)=>{var r={};r[n(99813)("toStringTag")]="z",e.exports="[object z]"===String(r)},85803:(e,t,n)=>{var r=n(9697),o=String;e.exports=function(e){if("Symbol"===r(e))throw TypeError("Cannot convert a Symbol value to a string");return o(e)}},69826:e=>{var t=String;e.exports=function(e){try{return t(e)}catch(e){return"Object"}}},99418:(e,t,n)=>{var r=n(95329),o=0,a=Math.random(),i=r(1..toString);e.exports=function(e){return"Symbol("+(void 0===e?"":e)+")_"+i(++o+a,36)}},14766:(e,t,n)=>{var r=n(95981),o=n(99813),a=n(82529),i=o("iterator");e.exports=!r((function(){var e=new URL("b?a=1&b=2&c=3","http://a"),t=e.searchParams,n="";return e.pathname="c%20d",t.forEach((function(e,r){t.delete("b"),n+=r+e})),a&&!e.toJSON||!t.sort||"http://a/c%20d?a=1&c=3"!==e.href||"3"!==t.get("c")||"a=1"!==String(new URLSearchParams("?a=1"))||!t[i]||"a"!==new URL("https://a@b").username||"b"!==new URLSearchParams(new URLSearchParams("a=b")).get("a")||"xn--e1aybc"!==new URL("http://тест").host||"#%D0%B1"!==new URL("http://a#б").hash||"a1c3"!==n||"x"!==new URL("http://x",void 0).host}))},32302:(e,t,n)=>{var r=n(63405);e.exports=r&&!Symbol.sham&&"symbol"==typeof Symbol.iterator},83937:(e,t,n)=>{var r=n(55746),o=n(95981);e.exports=r&&o((function(){return 42!=Object.defineProperty((function(){}),"prototype",{value:42,writable:!1}).prototype}))},18348:e=>{var t=TypeError;e.exports=function(e,n){if(e{var r=n(21899),o=n(57475),a=r.WeakMap;e.exports=o(a)&&/native code/.test(String(a))},73464:(e,t,n)=>{var r=n(54058),o=n(90953),a=n(11477),i=n(65988).f;e.exports=function(e){var t=r.Symbol||(r.Symbol={});o(t,e)||i(t,e,{value:a.f(e)})}},11477:(e,t,n)=>{var r=n(99813);t.f=r},99813:(e,t,n)=>{var r=n(21899),o=n(68726),a=n(90953),i=n(99418),s=n(63405),l=n(32302),u=r.Symbol,c=o("wks"),p=l?u.for||u:u&&u.withoutSetter||i;e.exports=function(e){return a(c,e)||(c[e]=s&&a(u,e)?u[e]:p("Symbol."+e)),c[e]}},73483:e=>{e.exports="\t\n\v\f\r                 \u2028\u2029\ufeff"},49812:(e,t,n)=>{"use strict";var r=n(76887),o=n(7046),a=n(249),i=n(88929),s=n(23489),l=n(29290),u=n(32029),c=n(31887),p=n(53794),f=n(79585),h=n(93091),d=n(14649),m=n(99813)("toStringTag"),g=Error,y=[].push,v=function(e,t){var n,r=o(b,this);i?n=i(g(),r?a(this):b):(n=r?this:l(b),u(n,m,"Error")),void 0!==t&&u(n,"message",d(t)),f(n,v,n.stack,1),arguments.length>2&&p(n,arguments[2]);var s=[];return h(e,y,{that:s}),u(n,"errors",s),n};i?i(v,g):s(v,g,{name:!0});var b=v.prototype=l(g.prototype,{constructor:c(1,v),message:c(1,""),name:c(1,"AggregateError")});r({global:!0,constructor:!0,arity:2},{AggregateError:v})},47627:(e,t,n)=>{n(49812)},85906:(e,t,n)=>{"use strict";var r=n(76887),o=n(95981),a=n(1052),i=n(10941),s=n(89678),l=n(10623),u=n(66796),c=n(55449),p=n(64692),f=n(50568),h=n(99813),d=n(53385),m=h("isConcatSpreadable"),g=d>=51||!o((function(){var e=[];return e[m]=!1,e.concat()[0]!==e})),y=function(e){if(!i(e))return!1;var t=e[m];return void 0!==t?!!t:a(e)};r({target:"Array",proto:!0,arity:1,forced:!g||!f("concat")},{concat:function(e){var t,n,r,o,a,i=s(this),f=p(i,0),h=0;for(t=-1,r=arguments.length;t{"use strict";var r=n(76887),o=n(3610).every;r({target:"Array",proto:!0,forced:!n(34194)("every")},{every:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}})},80290:(e,t,n)=>{var r=n(76887),o=n(91860),a=n(18479);r({target:"Array",proto:!0},{fill:o}),a("fill")},21501:(e,t,n)=>{"use strict";var r=n(76887),o=n(3610).filter;r({target:"Array",proto:!0,forced:!n(50568)("filter")},{filter:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}})},44929:(e,t,n)=>{"use strict";var r=n(76887),o=n(3610).findIndex,a=n(18479),i="findIndex",s=!0;i in[]&&Array(1)[i]((function(){s=!1})),r({target:"Array",proto:!0,forced:s},{findIndex:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}}),a(i)},80833:(e,t,n)=>{"use strict";var r=n(76887),o=n(3610).find,a=n(18479),i="find",s=!0;i in[]&&Array(1)[i]((function(){s=!1})),r({target:"Array",proto:!0,forced:s},{find:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}}),a(i)},2437:(e,t,n)=>{"use strict";var r=n(76887),o=n(56837);r({target:"Array",proto:!0,forced:[].forEach!=o},{forEach:o})},53242:(e,t,n)=>{var r=n(76887),o=n(11354);r({target:"Array",stat:!0,forced:!n(21385)((function(e){Array.from(e)}))},{from:o})},97690:(e,t,n)=>{"use strict";var r=n(76887),o=n(31692).includes,a=n(95981),i=n(18479);r({target:"Array",proto:!0,forced:a((function(){return!Array(1).includes()}))},{includes:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}}),i("includes")},99076:(e,t,n)=>{"use strict";var r=n(76887),o=n(97484),a=n(31692).indexOf,i=n(34194),s=o([].indexOf),l=!!s&&1/s([1],1,-0)<0;r({target:"Array",proto:!0,forced:l||!i("indexOf")},{indexOf:function(e){var t=arguments.length>1?arguments[1]:void 0;return l?s(this,e,t)||0:a(this,e,t)}})},92737:(e,t,n)=>{n(76887)({target:"Array",stat:!0},{isArray:n(1052)})},66274:(e,t,n)=>{"use strict";var r=n(74529),o=n(18479),a=n(12077),i=n(45402),s=n(65988).f,l=n(75105),u=n(23538),c=n(82529),p=n(55746),f="Array Iterator",h=i.set,d=i.getterFor(f);e.exports=l(Array,"Array",(function(e,t){h(this,{type:f,target:r(e),index:0,kind:t})}),(function(){var e=d(this),t=e.target,n=e.kind,r=e.index++;return!t||r>=t.length?(e.target=void 0,u(void 0,!0)):u("keys"==n?r:"values"==n?t[r]:[r,t[r]],!1)}),"values");var m=a.Arguments=a.Array;if(o("keys"),o("values"),o("entries"),!c&&p&&"values"!==m.name)try{s(m,"name",{value:"values"})}catch(e){}},75915:(e,t,n)=>{var r=n(76887),o=n(67145);r({target:"Array",proto:!0,forced:o!==[].lastIndexOf},{lastIndexOf:o})},68787:(e,t,n)=>{"use strict";var r=n(76887),o=n(3610).map;r({target:"Array",proto:!0,forced:!n(50568)("map")},{map:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}})},81876:(e,t,n)=>{"use strict";var r=n(76887),o=n(46499).left,a=n(34194),i=n(53385);r({target:"Array",proto:!0,forced:!n(6049)&&i>79&&i<83||!a("reduce")},{reduce:function(e){var t=arguments.length;return o(this,e,t,t>1?arguments[1]:void 0)}})},60186:(e,t,n)=>{"use strict";var r=n(76887),o=n(1052),a=n(24284),i=n(10941),s=n(59413),l=n(10623),u=n(74529),c=n(55449),p=n(99813),f=n(50568),h=n(93765),d=f("slice"),m=p("species"),g=Array,y=Math.max;r({target:"Array",proto:!0,forced:!d},{slice:function(e,t){var n,r,p,f=u(this),d=l(f),v=s(e,d),b=s(void 0===t?d:t,d);if(o(f)&&(n=f.constructor,(a(n)&&(n===g||o(n.prototype))||i(n)&&null===(n=n[m]))&&(n=void 0),n===g||void 0===n))return h(f,v,b);for(r=new(void 0===n?g:n)(y(b-v,0)),p=0;v{"use strict";var r=n(76887),o=n(3610).some;r({target:"Array",proto:!0,forced:!n(34194)("some")},{some:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}})},4115:(e,t,n)=>{"use strict";var r=n(76887),o=n(95329),a=n(24883),i=n(89678),s=n(10623),l=n(15863),u=n(85803),c=n(95981),p=n(61388),f=n(34194),h=n(34342),d=n(81046),m=n(53385),g=n(18938),y=[],v=o(y.sort),b=o(y.push),w=c((function(){y.sort(void 0)})),E=c((function(){y.sort(null)})),x=f("sort"),_=!c((function(){if(m)return m<70;if(!(h&&h>3)){if(d)return!0;if(g)return g<603;var e,t,n,r,o="";for(e=65;e<76;e++){switch(t=String.fromCharCode(e),e){case 66:case 69:case 70:case 72:n=3;break;case 68:case 71:n=4;break;default:n=2}for(r=0;r<47;r++)y.push({k:t+r,v:n})}for(y.sort((function(e,t){return t.v-e.v})),r=0;ru(n)?1:-1}}(e)),n=s(o),r=0;r{"use strict";var r=n(76887),o=n(89678),a=n(59413),i=n(62435),s=n(10623),l=n(89779),u=n(66796),c=n(64692),p=n(55449),f=n(15863),h=n(50568)("splice"),d=Math.max,m=Math.min;r({target:"Array",proto:!0,forced:!h},{splice:function(e,t){var n,r,h,g,y,v,b=o(this),w=s(b),E=a(e,w),x=arguments.length;for(0===x?n=r=0:1===x?(n=0,r=w-E):(n=x-2,r=m(d(i(t),0),w-E)),u(w+n-r),h=c(b,r),g=0;gw-r+n;g--)f(b,g-1)}else if(n>r)for(g=w-r;g>E;g--)v=g+n-1,(y=g+r-1)in b?b[v]=b[y]:f(b,v);for(g=0;g{var r=n(76887),o=n(95329),a=Date,i=o(a.prototype.getTime);r({target:"Date",stat:!0},{now:function(){return i(new a)}})},18084:()=>{},73381:(e,t,n)=>{var r=n(76887),o=n(98308);r({target:"Function",proto:!0,forced:Function.bind!==o},{bind:o})},32619:(e,t,n)=>{var r=n(76887),o=n(626),a=n(79730),i=n(78834),s=n(95329),l=n(95981),u=n(57475),c=n(56664),p=n(93765),f=n(33323),h=n(63405),d=String,m=o("JSON","stringify"),g=s(/./.exec),y=s("".charAt),v=s("".charCodeAt),b=s("".replace),w=s(1..toString),E=/[\uD800-\uDFFF]/g,x=/^[\uD800-\uDBFF]$/,_=/^[\uDC00-\uDFFF]$/,S=!h||l((function(){var e=o("Symbol")();return"[null]"!=m([e])||"{}"!=m({a:e})||"{}"!=m(Object(e))})),A=l((function(){return'"\\udf06\\ud834"'!==m("\udf06\ud834")||'"\\udead"'!==m("\udead")})),C=function(e,t){var n=p(arguments),r=f(t);if(u(r)||void 0!==e&&!c(e))return n[1]=function(e,t){if(u(r)&&(t=i(r,this,d(e),t)),!c(t))return t},a(m,null,n)},k=function(e,t,n){var r=y(n,t-1),o=y(n,t+1);return g(x,e)&&!g(_,o)||g(_,e)&&!g(x,r)?"\\u"+w(v(e,0),16):e};m&&r({target:"JSON",stat:!0,arity:3,forced:S||A},{stringify:function(e,t,n){var r=p(arguments),o=a(S?C:m,null,r);return A&&"string"==typeof o?b(o,E,k):o}})},69120:(e,t,n)=>{var r=n(21899);n(90904)(r.JSON,"JSON",!0)},23112:(e,t,n)=>{"use strict";n(24683)("Map",(function(e){return function(){return e(this,arguments.length?arguments[0]:void 0)}}),n(85616))},37501:(e,t,n)=>{n(23112)},79413:()=>{},49221:(e,t,n)=>{var r=n(76887),o=n(24420);r({target:"Object",stat:!0,arity:2,forced:Object.assign!==o},{assign:o})},74979:(e,t,n)=>{var r=n(76887),o=n(55746),a=n(59938).f;r({target:"Object",stat:!0,forced:Object.defineProperties!==a,sham:!o},{defineProperties:a})},86450:(e,t,n)=>{var r=n(76887),o=n(55746),a=n(65988).f;r({target:"Object",stat:!0,forced:Object.defineProperty!==a,sham:!o},{defineProperty:a})},46924:(e,t,n)=>{var r=n(76887),o=n(95981),a=n(74529),i=n(49677).f,s=n(55746);r({target:"Object",stat:!0,forced:!s||o((function(){i(1)})),sham:!s},{getOwnPropertyDescriptor:function(e,t){return i(a(e),t)}})},88482:(e,t,n)=>{var r=n(76887),o=n(55746),a=n(31136),i=n(74529),s=n(49677),l=n(55449);r({target:"Object",stat:!0,sham:!o},{getOwnPropertyDescriptors:function(e){for(var t,n,r=i(e),o=s.f,u=a(r),c={},p=0;u.length>p;)void 0!==(n=o(r,t=u[p++]))&&l(c,t,n);return c}})},37144:(e,t,n)=>{var r=n(76887),o=n(63405),a=n(95981),i=n(87857),s=n(89678);r({target:"Object",stat:!0,forced:!o||a((function(){i.f(1)}))},{getOwnPropertySymbols:function(e){var t=i.f;return t?t(s(e)):[]}})},21724:(e,t,n)=>{var r=n(76887),o=n(89678),a=n(14771);r({target:"Object",stat:!0,forced:n(95981)((function(){a(1)}))},{keys:function(e){return a(o(e))}})},55967:()=>{},26614:(e,t,n)=>{var r=n(76887),o=n(88810).values;r({target:"Object",stat:!0},{values:function(e){return o(e)}})},4560:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),a=n(24883),i=n(69520),s=n(40002),l=n(93091);r({target:"Promise",stat:!0,forced:n(31542)},{allSettled:function(e){var t=this,n=i.f(t),r=n.resolve,u=n.reject,c=s((function(){var n=a(t.resolve),i=[],s=0,u=1;l(e,(function(e){var a=s++,l=!1;u++,o(n,t,e).then((function(e){l||(l=!0,i[a]={status:"fulfilled",value:e},--u||r(i))}),(function(e){l||(l=!0,i[a]={status:"rejected",reason:e},--u||r(i))}))})),--u||r(i)}));return c.error&&u(c.value),n.promise}})},16890:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),a=n(24883),i=n(69520),s=n(40002),l=n(93091);r({target:"Promise",stat:!0,forced:n(31542)},{all:function(e){var t=this,n=i.f(t),r=n.resolve,u=n.reject,c=s((function(){var n=a(t.resolve),i=[],s=0,c=1;l(e,(function(e){var a=s++,l=!1;c++,o(n,t,e).then((function(e){l||(l=!0,i[a]=e,--c||r(i))}),u)})),--c||r(i)}));return c.error&&u(c.value),n.promise}})},91302:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),a=n(24883),i=n(626),s=n(69520),l=n(40002),u=n(93091),c=n(31542),p="No one promise resolved";r({target:"Promise",stat:!0,forced:c},{any:function(e){var t=this,n=i("AggregateError"),r=s.f(t),c=r.resolve,f=r.reject,h=l((function(){var r=a(t.resolve),i=[],s=0,l=1,h=!1;u(e,(function(e){var a=s++,u=!1;l++,o(r,t,e).then((function(e){u||h||(h=!0,c(e))}),(function(e){u||h||(u=!0,i[a]=e,--l||f(new n(i,p)))}))})),--l||f(new n(i,p))}));return h.error&&f(h.value),r.promise}})},83376:(e,t,n)=>{"use strict";var r=n(76887),o=n(82529),a=n(67742).CONSTRUCTOR,i=n(6991),s=n(626),l=n(57475),u=n(95929),c=i&&i.prototype;if(r({target:"Promise",proto:!0,forced:a,real:!0},{catch:function(e){return this.then(void 0,e)}}),!o&&l(i)){var p=s("Promise").prototype.catch;c.catch!==p&&u(c,"catch",p,{unsafe:!0})}},26934:(e,t,n)=>{"use strict";var r,o,a,i=n(76887),s=n(82529),l=n(6049),u=n(21899),c=n(78834),p=n(95929),f=n(88929),h=n(90904),d=n(94431),m=n(24883),g=n(57475),y=n(10941),v=n(5743),b=n(70487),w=n(42941).set,E=n(66132),x=n(34845),_=n(40002),S=n(18397),A=n(45402),C=n(6991),k=n(67742),O=n(69520),j="Promise",I=k.CONSTRUCTOR,T=k.REJECTION_EVENT,N=k.SUBCLASSING,P=A.getterFor(j),R=A.set,M=C&&C.prototype,D=C,L=M,B=u.TypeError,F=u.document,U=u.process,z=O.f,q=z,$=!!(F&&F.createEvent&&u.dispatchEvent),V="unhandledrejection",W=function(e){var t;return!(!y(e)||!g(t=e.then))&&t},H=function(e,t){var n,r,o,a=t.value,i=1==t.state,s=i?e.ok:e.fail,l=e.resolve,u=e.reject,p=e.domain;try{s?(i||(2===t.rejection&&Y(t),t.rejection=1),!0===s?n=a:(p&&p.enter(),n=s(a),p&&(p.exit(),o=!0)),n===e.promise?u(B("Promise-chain cycle")):(r=W(n))?c(r,n,l,u):l(n)):u(a)}catch(e){p&&!o&&p.exit(),u(e)}},J=function(e,t){e.notified||(e.notified=!0,E((function(){for(var n,r=e.reactions;n=r.get();)H(n,e);e.notified=!1,t&&!e.rejection&&G(e)})))},K=function(e,t,n){var r,o;$?((r=F.createEvent("Event")).promise=t,r.reason=n,r.initEvent(e,!1,!0),u.dispatchEvent(r)):r={promise:t,reason:n},!T&&(o=u["on"+e])?o(r):e===V&&x("Unhandled promise rejection",n)},G=function(e){c(w,u,(function(){var t,n=e.facade,r=e.value;if(Z(e)&&(t=_((function(){l?U.emit("unhandledRejection",r,n):K(V,n,r)})),e.rejection=l||Z(e)?2:1,t.error))throw t.value}))},Z=function(e){return 1!==e.rejection&&!e.parent},Y=function(e){c(w,u,(function(){var t=e.facade;l?U.emit("rejectionHandled",t):K("rejectionhandled",t,e.value)}))},Q=function(e,t,n){return function(r){e(t,r,n)}},X=function(e,t,n){e.done||(e.done=!0,n&&(e=n),e.value=t,e.state=2,J(e,!0))},ee=function(e,t,n){if(!e.done){e.done=!0,n&&(e=n);try{if(e.facade===t)throw B("Promise can't be resolved itself");var r=W(t);r?E((function(){var n={done:!1};try{c(r,t,Q(ee,n,e),Q(X,n,e))}catch(t){X(n,t,e)}})):(e.value=t,e.state=1,J(e,!1))}catch(t){X({done:!1},t,e)}}};if(I&&(L=(D=function(e){v(this,L),m(e),c(r,this);var t=P(this);try{e(Q(ee,t),Q(X,t))}catch(e){X(t,e)}}).prototype,(r=function(e){R(this,{type:j,done:!1,notified:!1,parent:!1,reactions:new S,rejection:!1,state:0,value:void 0})}).prototype=p(L,"then",(function(e,t){var n=P(this),r=z(b(this,D));return n.parent=!0,r.ok=!g(e)||e,r.fail=g(t)&&t,r.domain=l?U.domain:void 0,0==n.state?n.reactions.add(r):E((function(){H(r,n)})),r.promise})),o=function(){var e=new r,t=P(e);this.promise=e,this.resolve=Q(ee,t),this.reject=Q(X,t)},O.f=z=function(e){return e===D||undefined===e?new o(e):q(e)},!s&&g(C)&&M!==Object.prototype)){a=M.then,N||p(M,"then",(function(e,t){var n=this;return new D((function(e,t){c(a,n,e,t)})).then(e,t)}),{unsafe:!0});try{delete M.constructor}catch(e){}f&&f(M,L)}i({global:!0,constructor:!0,wrap:!0,forced:I},{Promise:D}),h(D,j,!1,!0),d(j)},44349:(e,t,n)=>{"use strict";var r=n(76887),o=n(82529),a=n(6991),i=n(95981),s=n(626),l=n(57475),u=n(70487),c=n(56584),p=n(95929),f=a&&a.prototype;if(r({target:"Promise",proto:!0,real:!0,forced:!!a&&i((function(){f.finally.call({then:function(){}},(function(){}))}))},{finally:function(e){var t=u(this,s("Promise")),n=l(e);return this.then(n?function(n){return c(t,e()).then((function(){return n}))}:e,n?function(n){return c(t,e()).then((function(){throw n}))}:e)}}),!o&&l(a)){var h=s("Promise").prototype.finally;f.finally!==h&&p(f,"finally",h,{unsafe:!0})}},98881:(e,t,n)=>{n(26934),n(16890),n(83376),n(55921),n(64069),n(14482)},55921:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),a=n(24883),i=n(69520),s=n(40002),l=n(93091);r({target:"Promise",stat:!0,forced:n(31542)},{race:function(e){var t=this,n=i.f(t),r=n.reject,u=s((function(){var i=a(t.resolve);l(e,(function(e){o(i,t,e).then(n.resolve,r)}))}));return u.error&&r(u.value),n.promise}})},64069:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),a=n(69520);r({target:"Promise",stat:!0,forced:n(67742).CONSTRUCTOR},{reject:function(e){var t=a.f(this);return o(t.reject,void 0,e),t.promise}})},14482:(e,t,n)=>{"use strict";var r=n(76887),o=n(626),a=n(82529),i=n(6991),s=n(67742).CONSTRUCTOR,l=n(56584),u=o("Promise"),c=a&&!s;r({target:"Promise",stat:!0,forced:a||s},{resolve:function(e){return l(c&&this===u?i:this,e)}})},1502:()=>{},11035:(e,t,n)=>{"use strict";var r=n(76887),o=n(95329),a=n(70344),i=n(48219),s=n(85803),l=n(67772),u=o("".indexOf);r({target:"String",proto:!0,forced:!l("includes")},{includes:function(e){return!!~u(s(i(this)),s(a(e)),arguments.length>1?arguments[1]:void 0)}})},77971:(e,t,n)=>{"use strict";var r=n(64620).charAt,o=n(85803),a=n(45402),i=n(75105),s=n(23538),l="String Iterator",u=a.set,c=a.getterFor(l);i(String,"String",(function(e){u(this,{type:l,string:o(e),index:0})}),(function(){var e,t=c(this),n=t.string,o=t.index;return o>=n.length?s(void 0,!0):(e=r(n,o),t.index+=e.length,s(e,!1))}))},60986:(e,t,n)=>{n(76887)({target:"String",proto:!0},{repeat:n(16178)})},94761:(e,t,n)=>{"use strict";var r,o=n(76887),a=n(97484),i=n(49677).f,s=n(43057),l=n(85803),u=n(70344),c=n(48219),p=n(67772),f=n(82529),h=a("".startsWith),d=a("".slice),m=Math.min,g=p("startsWith");o({target:"String",proto:!0,forced:!!(f||g||(r=i(String.prototype,"startsWith"),!r||r.writable))&&!g},{startsWith:function(e){var t=l(c(this));u(e);var n=s(m(arguments.length>1?arguments[1]:void 0,t.length)),r=l(e);return h?h(t,r,n):d(t,n,n+r.length)===r}})},57398:(e,t,n)=>{"use strict";var r=n(76887),o=n(74853).trim;r({target:"String",proto:!0,forced:n(93093)("trim")},{trim:function(){return o(this)}})},8555:(e,t,n)=>{n(73464)("asyncIterator")},48616:(e,t,n)=>{"use strict";var r=n(76887),o=n(21899),a=n(78834),i=n(95329),s=n(82529),l=n(55746),u=n(63405),c=n(95981),p=n(90953),f=n(7046),h=n(96059),d=n(74529),m=n(83894),g=n(85803),y=n(31887),v=n(29290),b=n(14771),w=n(10946),E=n(684),x=n(87857),_=n(49677),S=n(65988),A=n(59938),C=n(36760),k=n(95929),O=n(29202),j=n(68726),I=n(44262),T=n(27748),N=n(99418),P=n(99813),R=n(11477),M=n(73464),D=n(29630),L=n(90904),B=n(45402),F=n(3610).forEach,U=I("hidden"),z="Symbol",q="prototype",$=B.set,V=B.getterFor(z),W=Object[q],H=o.Symbol,J=H&&H[q],K=o.TypeError,G=o.QObject,Z=_.f,Y=S.f,Q=E.f,X=C.f,ee=i([].push),te=j("symbols"),ne=j("op-symbols"),re=j("wks"),oe=!G||!G[q]||!G[q].findChild,ae=l&&c((function(){return 7!=v(Y({},"a",{get:function(){return Y(this,"a",{value:7}).a}})).a}))?function(e,t,n){var r=Z(W,t);r&&delete W[t],Y(e,t,n),r&&e!==W&&Y(W,t,r)}:Y,ie=function(e,t){var n=te[e]=v(J);return $(n,{type:z,tag:e,description:t}),l||(n.description=t),n},se=function(e,t,n){e===W&&se(ne,t,n),h(e);var r=m(t);return h(n),p(te,r)?(n.enumerable?(p(e,U)&&e[U][r]&&(e[U][r]=!1),n=v(n,{enumerable:y(0,!1)})):(p(e,U)||Y(e,U,y(1,{})),e[U][r]=!0),ae(e,r,n)):Y(e,r,n)},le=function(e,t){h(e);var n=d(t),r=b(n).concat(fe(n));return F(r,(function(t){l&&!a(ue,n,t)||se(e,t,n[t])})),e},ue=function(e){var t=m(e),n=a(X,this,t);return!(this===W&&p(te,t)&&!p(ne,t))&&(!(n||!p(this,t)||!p(te,t)||p(this,U)&&this[U][t])||n)},ce=function(e,t){var n=d(e),r=m(t);if(n!==W||!p(te,r)||p(ne,r)){var o=Z(n,r);return!o||!p(te,r)||p(n,U)&&n[U][r]||(o.enumerable=!0),o}},pe=function(e){var t=Q(d(e)),n=[];return F(t,(function(e){p(te,e)||p(T,e)||ee(n,e)})),n},fe=function(e){var t=e===W,n=Q(t?ne:d(e)),r=[];return F(n,(function(e){!p(te,e)||t&&!p(W,e)||ee(r,te[e])})),r};u||(k(J=(H=function(){if(f(J,this))throw K("Symbol is not a constructor");var e=arguments.length&&void 0!==arguments[0]?g(arguments[0]):void 0,t=N(e),n=function(e){this===W&&a(n,ne,e),p(this,U)&&p(this[U],t)&&(this[U][t]=!1),ae(this,t,y(1,e))};return l&&oe&&ae(W,t,{configurable:!0,set:n}),ie(t,e)})[q],"toString",(function(){return V(this).tag})),k(H,"withoutSetter",(function(e){return ie(N(e),e)})),C.f=ue,S.f=se,A.f=le,_.f=ce,w.f=E.f=pe,x.f=fe,R.f=function(e){return ie(P(e),e)},l&&(O(J,"description",{configurable:!0,get:function(){return V(this).description}}),s||k(W,"propertyIsEnumerable",ue,{unsafe:!0}))),r({global:!0,constructor:!0,wrap:!0,forced:!u,sham:!u},{Symbol:H}),F(b(re),(function(e){M(e)})),r({target:z,stat:!0,forced:!u},{useSetter:function(){oe=!0},useSimple:function(){oe=!1}}),r({target:"Object",stat:!0,forced:!u,sham:!l},{create:function(e,t){return void 0===t?v(e):le(v(e),t)},defineProperty:se,defineProperties:le,getOwnPropertyDescriptor:ce}),r({target:"Object",stat:!0,forced:!u},{getOwnPropertyNames:pe}),D(),L(H,z),T[U]=!0},52615:()=>{},64523:(e,t,n)=>{var r=n(76887),o=n(626),a=n(90953),i=n(85803),s=n(68726),l=n(34680),u=s("string-to-symbol-registry"),c=s("symbol-to-string-registry");r({target:"Symbol",stat:!0,forced:!l},{for:function(e){var t=i(e);if(a(u,t))return u[t];var n=o("Symbol")(t);return u[t]=n,c[n]=t,n}})},21732:(e,t,n)=>{n(73464)("hasInstance")},35903:(e,t,n)=>{n(73464)("isConcatSpreadable")},1825:(e,t,n)=>{n(73464)("iterator")},35824:(e,t,n)=>{n(48616),n(64523),n(38608),n(32619),n(37144)},38608:(e,t,n)=>{var r=n(76887),o=n(90953),a=n(56664),i=n(69826),s=n(68726),l=n(34680),u=s("symbol-to-string-registry");r({target:"Symbol",stat:!0,forced:!l},{keyFor:function(e){if(!a(e))throw TypeError(i(e)+" is not a symbol");if(o(u,e))return u[e]}})},45915:(e,t,n)=>{n(73464)("matchAll")},28394:(e,t,n)=>{n(73464)("match")},61766:(e,t,n)=>{n(73464)("replace")},62737:(e,t,n)=>{n(73464)("search")},89911:(e,t,n)=>{n(73464)("species")},74315:(e,t,n)=>{n(73464)("split")},63131:(e,t,n)=>{var r=n(73464),o=n(29630);r("toPrimitive"),o()},64714:(e,t,n)=>{var r=n(626),o=n(73464),a=n(90904);o("toStringTag"),a(r("Symbol"),"Symbol")},70659:(e,t,n)=>{n(73464)("unscopables")},28783:(e,t,n)=>{n(73464)("asyncDispose")},43975:(e,t,n)=>{n(73464)("dispose")},22731:(e,t,n)=>{var r=n(76887),o=n(626),a=n(95329),i=o("Symbol"),s=i.keyFor,l=a(i.prototype.valueOf);r({target:"Symbol",stat:!0},{isRegistered:function(e){try{return void 0!==s(l(e))}catch(e){return!1}}})},85605:(e,t,n)=>{for(var r=n(76887),o=n(68726),a=n(626),i=n(95329),s=n(56664),l=n(99813),u=a("Symbol"),c=u.isWellKnown,p=a("Object","getOwnPropertyNames"),f=i(u.prototype.valueOf),h=o("wks"),d=0,m=p(u),g=m.length;d{n(73464)("matcher")},31943:(e,t,n)=>{n(73464)("metadataKey")},45414:(e,t,n)=>{n(73464)("metadata")},46774:(e,t,n)=>{n(73464)("observable")},80620:(e,t,n)=>{n(73464)("patternMatch")},36172:(e,t,n)=>{n(73464)("replaceAll")},7634:(e,t,n)=>{n(66274);var r=n(63281),o=n(21899),a=n(9697),i=n(32029),s=n(12077),l=n(99813)("toStringTag");for(var u in r){var c=o[u],p=c&&c.prototype;p&&a(p)!==l&&i(p,l,u),s[u]=s.Array}},79229:(e,t,n)=>{var r=n(76887),o=n(21899),a=n(37620)(o.setInterval,!0);r({global:!0,bind:!0,forced:o.setInterval!==a},{setInterval:a})},17749:(e,t,n)=>{var r=n(76887),o=n(21899),a=n(37620)(o.setTimeout,!0);r({global:!0,bind:!0,forced:o.setTimeout!==a},{setTimeout:a})},71249:(e,t,n)=>{n(79229),n(17749)},62524:(e,t,n)=>{"use strict";n(66274);var r=n(76887),o=n(21899),a=n(78834),i=n(95329),s=n(55746),l=n(14766),u=n(95929),c=n(94380),p=n(90904),f=n(53847),h=n(45402),d=n(5743),m=n(57475),g=n(90953),y=n(86843),v=n(9697),b=n(96059),w=n(10941),E=n(85803),x=n(29290),_=n(31887),S=n(53476),A=n(22902),C=n(18348),k=n(99813),O=n(61388),j=k("iterator"),I="URLSearchParams",T=I+"Iterator",N=h.set,P=h.getterFor(I),R=h.getterFor(T),M=Object.getOwnPropertyDescriptor,D=function(e){if(!s)return o[e];var t=M(o,e);return t&&t.value},L=D("fetch"),B=D("Request"),F=D("Headers"),U=B&&B.prototype,z=F&&F.prototype,q=o.RegExp,$=o.TypeError,V=o.decodeURIComponent,W=o.encodeURIComponent,H=i("".charAt),J=i([].join),K=i([].push),G=i("".replace),Z=i([].shift),Y=i([].splice),Q=i("".split),X=i("".slice),ee=/\+/g,te=Array(4),ne=function(e){return te[e-1]||(te[e-1]=q("((?:%[\\da-f]{2}){"+e+"})","gi"))},re=function(e){try{return V(e)}catch(t){return e}},oe=function(e){var t=G(e,ee," "),n=4;try{return V(t)}catch(e){for(;n;)t=G(t,ne(n--),re);return t}},ae=/[!'()~]|%20/g,ie={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+"},se=function(e){return ie[e]},le=function(e){return G(W(e),ae,se)},ue=f((function(e,t){N(this,{type:T,iterator:S(P(e).entries),kind:t})}),"Iterator",(function(){var e=R(this),t=e.kind,n=e.iterator.next(),r=n.value;return n.done||(n.value="keys"===t?r.key:"values"===t?r.value:[r.key,r.value]),n}),!0),ce=function(e){this.entries=[],this.url=null,void 0!==e&&(w(e)?this.parseObject(e):this.parseQuery("string"==typeof e?"?"===H(e,0)?X(e,1):e:E(e)))};ce.prototype={type:I,bindURL:function(e){this.url=e,this.update()},parseObject:function(e){var t,n,r,o,i,s,l,u=A(e);if(u)for(n=(t=S(e,u)).next;!(r=a(n,t)).done;){if(i=(o=S(b(r.value))).next,(s=a(i,o)).done||(l=a(i,o)).done||!a(i,o).done)throw $("Expected sequence with length 2");K(this.entries,{key:E(s.value),value:E(l.value)})}else for(var c in e)g(e,c)&&K(this.entries,{key:c,value:E(e[c])})},parseQuery:function(e){if(e)for(var t,n,r=Q(e,"&"),o=0;o0?arguments[0]:void 0))},fe=pe.prototype;if(c(fe,{append:function(e,t){C(arguments.length,2);var n=P(this);K(n.entries,{key:E(e),value:E(t)}),n.updateURL()},delete:function(e){C(arguments.length,1);for(var t=P(this),n=t.entries,r=E(e),o=0;ot.key?1:-1})),e.updateURL()},forEach:function(e){for(var t,n=P(this).entries,r=y(e,arguments.length>1?arguments[1]:void 0),o=0;o1?me(arguments[1]):{})}}),m(B)){var ge=function(e){return d(this,U),new B(e,arguments.length>1?me(arguments[1]):{})};U.constructor=ge,ge.prototype=U,r({global:!0,constructor:!0,dontCallGetSet:!0,forced:!0},{Request:ge})}}e.exports={URLSearchParams:pe,getState:P}},95304:(e,t,n)=>{n(62524)},47250:(e,t,n)=>{"use strict";n(77971);var r,o=n(76887),a=n(55746),i=n(14766),s=n(21899),l=n(86843),u=n(95329),c=n(95929),p=n(29202),f=n(5743),h=n(90953),d=n(24420),m=n(11354),g=n(15790),y=n(64620).codeAt,v=n(73291),b=n(85803),w=n(90904),E=n(18348),x=n(62524),_=n(45402),S=_.set,A=_.getterFor("URL"),C=x.URLSearchParams,k=x.getState,O=s.URL,j=s.TypeError,I=s.parseInt,T=Math.floor,N=Math.pow,P=u("".charAt),R=u(/./.exec),M=u([].join),D=u(1..toString),L=u([].pop),B=u([].push),F=u("".replace),U=u([].shift),z=u("".split),q=u("".slice),$=u("".toLowerCase),V=u([].unshift),W="Invalid scheme",H="Invalid host",J="Invalid port",K=/[a-z]/i,G=/[\d+-.a-z]/i,Z=/\d/,Y=/^0x/i,Q=/^[0-7]+$/,X=/^\d+$/,ee=/^[\da-f]+$/i,te=/[\0\t\n\r #%/:<>?@[\\\]^|]/,ne=/[\0\t\n\r #/:<>?@[\\\]^|]/,re=/^[\u0000-\u0020]+/,oe=/(^|[^\u0000-\u0020])[\u0000-\u0020]+$/,ae=/[\t\n\r]/g,ie=function(e){var t,n,r,o;if("number"==typeof e){for(t=[],n=0;n<4;n++)V(t,e%256),e=T(e/256);return M(t,".")}if("object"==typeof e){for(t="",r=function(e){for(var t=null,n=1,r=null,o=0,a=0;a<8;a++)0!==e[a]?(o>n&&(t=r,n=o),r=null,o=0):(null===r&&(r=a),++o);return o>n&&(t=r,n=o),t}(e),n=0;n<8;n++)o&&0===e[n]||(o&&(o=!1),r===n?(t+=n?":":"::",o=!0):(t+=D(e[n],16),n<7&&(t+=":")));return"["+t+"]"}return e},se={},le=d({},se,{" ":1,'"':1,"<":1,">":1,"`":1}),ue=d({},le,{"#":1,"?":1,"{":1,"}":1}),ce=d({},ue,{"/":1,":":1,";":1,"=":1,"@":1,"[":1,"\\":1,"]":1,"^":1,"|":1}),pe=function(e,t){var n=y(e,0);return n>32&&n<127&&!h(t,e)?e:encodeURIComponent(e)},fe={ftp:21,file:null,http:80,https:443,ws:80,wss:443},he=function(e,t){var n;return 2==e.length&&R(K,P(e,0))&&(":"==(n=P(e,1))||!t&&"|"==n)},de=function(e){var t;return e.length>1&&he(q(e,0,2))&&(2==e.length||"/"===(t=P(e,2))||"\\"===t||"?"===t||"#"===t)},me=function(e){return"."===e||"%2e"===$(e)},ge={},ye={},ve={},be={},we={},Ee={},xe={},_e={},Se={},Ae={},Ce={},ke={},Oe={},je={},Ie={},Te={},Ne={},Pe={},Re={},Me={},De={},Le=function(e,t,n){var r,o,a,i=b(e);if(t){if(o=this.parse(i))throw j(o);this.searchParams=null}else{if(void 0!==n&&(r=new Le(n,!0)),o=this.parse(i,null,r))throw j(o);(a=k(new C)).bindURL(this),this.searchParams=a}};Le.prototype={type:"URL",parse:function(e,t,n){var o,a,i,s,l,u=this,c=t||ge,p=0,f="",d=!1,y=!1,v=!1;for(e=b(e),t||(u.scheme="",u.username="",u.password="",u.host=null,u.port=null,u.path=[],u.query=null,u.fragment=null,u.cannotBeABaseURL=!1,e=F(e,re,""),e=F(e,oe,"$1")),e=F(e,ae,""),o=m(e);p<=o.length;){switch(a=o[p],c){case ge:if(!a||!R(K,a)){if(t)return W;c=ve;continue}f+=$(a),c=ye;break;case ye:if(a&&(R(G,a)||"+"==a||"-"==a||"."==a))f+=$(a);else{if(":"!=a){if(t)return W;f="",c=ve,p=0;continue}if(t&&(u.isSpecial()!=h(fe,f)||"file"==f&&(u.includesCredentials()||null!==u.port)||"file"==u.scheme&&!u.host))return;if(u.scheme=f,t)return void(u.isSpecial()&&fe[u.scheme]==u.port&&(u.port=null));f="","file"==u.scheme?c=je:u.isSpecial()&&n&&n.scheme==u.scheme?c=be:u.isSpecial()?c=_e:"/"==o[p+1]?(c=we,p++):(u.cannotBeABaseURL=!0,B(u.path,""),c=Re)}break;case ve:if(!n||n.cannotBeABaseURL&&"#"!=a)return W;if(n.cannotBeABaseURL&&"#"==a){u.scheme=n.scheme,u.path=g(n.path),u.query=n.query,u.fragment="",u.cannotBeABaseURL=!0,c=De;break}c="file"==n.scheme?je:Ee;continue;case be:if("/"!=a||"/"!=o[p+1]){c=Ee;continue}c=Se,p++;break;case we:if("/"==a){c=Ae;break}c=Pe;continue;case Ee:if(u.scheme=n.scheme,a==r)u.username=n.username,u.password=n.password,u.host=n.host,u.port=n.port,u.path=g(n.path),u.query=n.query;else if("/"==a||"\\"==a&&u.isSpecial())c=xe;else if("?"==a)u.username=n.username,u.password=n.password,u.host=n.host,u.port=n.port,u.path=g(n.path),u.query="",c=Me;else{if("#"!=a){u.username=n.username,u.password=n.password,u.host=n.host,u.port=n.port,u.path=g(n.path),u.path.length--,c=Pe;continue}u.username=n.username,u.password=n.password,u.host=n.host,u.port=n.port,u.path=g(n.path),u.query=n.query,u.fragment="",c=De}break;case xe:if(!u.isSpecial()||"/"!=a&&"\\"!=a){if("/"!=a){u.username=n.username,u.password=n.password,u.host=n.host,u.port=n.port,c=Pe;continue}c=Ae}else c=Se;break;case _e:if(c=Se,"/"!=a||"/"!=P(f,p+1))continue;p++;break;case Se:if("/"!=a&&"\\"!=a){c=Ae;continue}break;case Ae:if("@"==a){d&&(f="%40"+f),d=!0,i=m(f);for(var w=0;w65535)return J;u.port=u.isSpecial()&&_===fe[u.scheme]?null:_,f=""}if(t)return;c=Ne;continue}return J}f+=a;break;case je:if(u.scheme="file","/"==a||"\\"==a)c=Ie;else{if(!n||"file"!=n.scheme){c=Pe;continue}if(a==r)u.host=n.host,u.path=g(n.path),u.query=n.query;else if("?"==a)u.host=n.host,u.path=g(n.path),u.query="",c=Me;else{if("#"!=a){de(M(g(o,p),""))||(u.host=n.host,u.path=g(n.path),u.shortenPath()),c=Pe;continue}u.host=n.host,u.path=g(n.path),u.query=n.query,u.fragment="",c=De}}break;case Ie:if("/"==a||"\\"==a){c=Te;break}n&&"file"==n.scheme&&!de(M(g(o,p),""))&&(he(n.path[0],!0)?B(u.path,n.path[0]):u.host=n.host),c=Pe;continue;case Te:if(a==r||"/"==a||"\\"==a||"?"==a||"#"==a){if(!t&&he(f))c=Pe;else if(""==f){if(u.host="",t)return;c=Ne}else{if(s=u.parseHost(f))return s;if("localhost"==u.host&&(u.host=""),t)return;f="",c=Ne}continue}f+=a;break;case Ne:if(u.isSpecial()){if(c=Pe,"/"!=a&&"\\"!=a)continue}else if(t||"?"!=a)if(t||"#"!=a){if(a!=r&&(c=Pe,"/"!=a))continue}else u.fragment="",c=De;else u.query="",c=Me;break;case Pe:if(a==r||"/"==a||"\\"==a&&u.isSpecial()||!t&&("?"==a||"#"==a)){if(".."===(l=$(l=f))||"%2e."===l||".%2e"===l||"%2e%2e"===l?(u.shortenPath(),"/"==a||"\\"==a&&u.isSpecial()||B(u.path,"")):me(f)?"/"==a||"\\"==a&&u.isSpecial()||B(u.path,""):("file"==u.scheme&&!u.path.length&&he(f)&&(u.host&&(u.host=""),f=P(f,0)+":"),B(u.path,f)),f="","file"==u.scheme&&(a==r||"?"==a||"#"==a))for(;u.path.length>1&&""===u.path[0];)U(u.path);"?"==a?(u.query="",c=Me):"#"==a&&(u.fragment="",c=De)}else f+=pe(a,ue);break;case Re:"?"==a?(u.query="",c=Me):"#"==a?(u.fragment="",c=De):a!=r&&(u.path[0]+=pe(a,se));break;case Me:t||"#"!=a?a!=r&&("'"==a&&u.isSpecial()?u.query+="%27":u.query+="#"==a?"%23":pe(a,se)):(u.fragment="",c=De);break;case De:a!=r&&(u.fragment+=pe(a,le))}p++}},parseHost:function(e){var t,n,r;if("["==P(e,0)){if("]"!=P(e,e.length-1))return H;if(t=function(e){var t,n,r,o,a,i,s,l=[0,0,0,0,0,0,0,0],u=0,c=null,p=0,f=function(){return P(e,p)};if(":"==f()){if(":"!=P(e,1))return;p+=2,c=++u}for(;f();){if(8==u)return;if(":"!=f()){for(t=n=0;n<4&&R(ee,f());)t=16*t+I(f(),16),p++,n++;if("."==f()){if(0==n)return;if(p-=n,u>6)return;for(r=0;f();){if(o=null,r>0){if(!("."==f()&&r<4))return;p++}if(!R(Z,f()))return;for(;R(Z,f());){if(a=I(f(),10),null===o)o=a;else{if(0==o)return;o=10*o+a}if(o>255)return;p++}l[u]=256*l[u]+o,2!=++r&&4!=r||u++}if(4!=r)return;break}if(":"==f()){if(p++,!f())return}else if(f())return;l[u++]=t}else{if(null!==c)return;p++,c=++u}}if(null!==c)for(i=u-c,u=7;0!=u&&i>0;)s=l[u],l[u--]=l[c+i-1],l[c+--i]=s;else if(8!=u)return;return l}(q(e,1,-1)),!t)return H;this.host=t}else if(this.isSpecial()){if(e=v(e),R(te,e))return H;if(t=function(e){var t,n,r,o,a,i,s,l=z(e,".");if(l.length&&""==l[l.length-1]&&l.length--,(t=l.length)>4)return e;for(n=[],r=0;r1&&"0"==P(o,0)&&(a=R(Y,o)?16:8,o=q(o,8==a?1:2)),""===o)i=0;else{if(!R(10==a?X:8==a?Q:ee,o))return e;i=I(o,a)}B(n,i)}for(r=0;r=N(256,5-t))return null}else if(i>255)return null;for(s=L(n),r=0;r1?arguments[1]:void 0,r=S(t,new Le(e,!1,n));a||(t.href=r.serialize(),t.origin=r.getOrigin(),t.protocol=r.getProtocol(),t.username=r.getUsername(),t.password=r.getPassword(),t.host=r.getHost(),t.hostname=r.getHostname(),t.port=r.getPort(),t.pathname=r.getPathname(),t.search=r.getSearch(),t.searchParams=r.getSearchParams(),t.hash=r.getHash())},Fe=Be.prototype,Ue=function(e,t){return{get:function(){return A(this)[e]()},set:t&&function(e){return A(this)[t](e)},configurable:!0,enumerable:!0}};if(a&&(p(Fe,"href",Ue("serialize","setHref")),p(Fe,"origin",Ue("getOrigin")),p(Fe,"protocol",Ue("getProtocol","setProtocol")),p(Fe,"username",Ue("getUsername","setUsername")),p(Fe,"password",Ue("getPassword","setPassword")),p(Fe,"host",Ue("getHost","setHost")),p(Fe,"hostname",Ue("getHostname","setHostname")),p(Fe,"port",Ue("getPort","setPort")),p(Fe,"pathname",Ue("getPathname","setPathname")),p(Fe,"search",Ue("getSearch","setSearch")),p(Fe,"searchParams",Ue("getSearchParams")),p(Fe,"hash",Ue("getHash","setHash"))),c(Fe,"toJSON",(function(){return A(this).serialize()}),{enumerable:!0}),c(Fe,"toString",(function(){return A(this).serialize()}),{enumerable:!0}),O){var ze=O.createObjectURL,qe=O.revokeObjectURL;ze&&c(Be,"createObjectURL",l(ze,O)),qe&&c(Be,"revokeObjectURL",l(qe,O))}w(Be,"URL"),o({global:!0,constructor:!0,forced:!i,sham:!a},{URL:Be})},33601:(e,t,n)=>{n(47250)},98947:()=>{},24848:(e,t,n)=>{var r=n(54493);e.exports=r},83363:(e,t,n)=>{var r=n(24034);e.exports=r},62908:(e,t,n)=>{var r=n(12710);e.exports=r},49216:(e,t,n)=>{var r=n(99324);e.exports=r},56668:(e,t,n)=>{var r=n(95909);e.exports=r},74719:(e,t,n)=>{var r=n(14423);e.exports=r},57784:(e,t,n)=>{var r=n(81103);e.exports=r},28196:(e,t,n)=>{var r=n(16246);e.exports=r},8065:(e,t,n)=>{var r=n(56043);e.exports=r},57448:(e,t,n)=>{n(7634);var r=n(9697),o=n(90953),a=n(7046),i=n(62908),s=Array.prototype,l={DOMTokenList:!0,NodeList:!0};e.exports=function(e){var t=e.entries;return e===s||a(s,e)&&t===s.entries||o(l,r(e))?i:t}},29455:(e,t,n)=>{var r=n(13160);e.exports=r},69743:(e,t,n)=>{var r=n(80446);e.exports=r},11955:(e,t,n)=>{var r=n(2480);e.exports=r},96064:(e,t,n)=>{var r=n(7147);e.exports=r},61577:(e,t,n)=>{var r=n(32236);e.exports=r},46279:(e,t,n)=>{n(7634);var r=n(9697),o=n(90953),a=n(7046),i=n(49216),s=Array.prototype,l={DOMTokenList:!0,NodeList:!0};e.exports=function(e){var t=e.forEach;return e===s||a(s,e)&&t===s.forEach||o(l,r(e))?i:t}},33778:(e,t,n)=>{var r=n(58557);e.exports=r},19373:(e,t,n)=>{var r=n(34570);e.exports=r},73819:(e,t,n)=>{n(7634);var r=n(9697),o=n(90953),a=n(7046),i=n(56668),s=Array.prototype,l={DOMTokenList:!0,NodeList:!0};e.exports=function(e){var t=e.keys;return e===s||a(s,e)&&t===s.keys||o(l,r(e))?i:t}},11022:(e,t,n)=>{var r=n(57564);e.exports=r},61798:(e,t,n)=>{var r=n(88287);e.exports=r},52527:(e,t,n)=>{var r=n(68025);e.exports=r},36857:(e,t,n)=>{var r=n(59257);e.exports=r},82073:(e,t,n)=>{var r=n(69601);e.exports=r},45286:(e,t,n)=>{var r=n(28299);e.exports=r},62856:(e,t,n)=>{var r=n(69355);e.exports=r},2348:(e,t,n)=>{var r=n(18339);e.exports=r},35178:(e,t,n)=>{var r=n(71611);e.exports=r},76361:(e,t,n)=>{var r=n(62774);e.exports=r},71815:(e,t,n)=>{n(7634);var r=n(9697),o=n(90953),a=n(7046),i=n(74719),s=Array.prototype,l={DOMTokenList:!0,NodeList:!0};e.exports=function(e){var t=e.values;return e===s||a(s,e)&&t===s.values||o(l,r(e))?i:t}},8933:(e,t,n)=>{var r=n(84426);e.exports=r},15868:(e,t,n)=>{var r=n(91018);n(7634),e.exports=r},63383:(e,t,n)=>{var r=n(45999);e.exports=r},57396:(e,t,n)=>{var r=n(7702);e.exports=r},41910:(e,t,n)=>{var r=n(48171);e.exports=r},79427:(e,t,n)=>{var r=n(286);e.exports=r},62857:(e,t,n)=>{var r=n(92766);e.exports=r},9534:(e,t,n)=>{var r=n(30498);e.exports=r},23059:(e,t,n)=>{var r=n(48494);e.exports=r},47795:(e,t,n)=>{var r=n(98430);e.exports=r},27460:(e,t,n)=>{var r=n(52956);n(7634),e.exports=r},27989:(e,t,n)=>{n(71249);var r=n(54058);e.exports=r.setTimeout},92547:(e,t,n)=>{var r=n(57473);n(7634),e.exports=r},46509:(e,t,n)=>{var r=n(24227);n(7634),e.exports=r},35774:(e,t,n)=>{var r=n(62978);e.exports=r},57641:(e,t,n)=>{var r=n(71459);e.exports=r},71459:(e,t,n)=>{n(33601),n(98947),n(95304);var r=n(54058);e.exports=r.URL},31905:function(){!function(e){!function(t){var n={searchParams:"URLSearchParams"in e,iterable:"Symbol"in e&&"iterator"in Symbol,blob:"FileReader"in e&&"Blob"in e&&function(){try{return new Blob,!0}catch(e){return!1}}(),formData:"FormData"in e,arrayBuffer:"ArrayBuffer"in e};if(n.arrayBuffer)var r=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],o=ArrayBuffer.isView||function(e){return e&&r.indexOf(Object.prototype.toString.call(e))>-1};function a(e){if("string"!=typeof e&&(e=String(e)),/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(e))throw new TypeError("Invalid character in header field name");return e.toLowerCase()}function i(e){return"string"!=typeof e&&(e=String(e)),e}function s(e){var t={next:function(){var t=e.shift();return{done:void 0===t,value:t}}};return n.iterable&&(t[Symbol.iterator]=function(){return t}),t}function l(e){this.map={},e instanceof l?e.forEach((function(e,t){this.append(t,e)}),this):Array.isArray(e)?e.forEach((function(e){this.append(e[0],e[1])}),this):e&&Object.getOwnPropertyNames(e).forEach((function(t){this.append(t,e[t])}),this)}function u(e){if(e.bodyUsed)return Promise.reject(new TypeError("Already read"));e.bodyUsed=!0}function c(e){return new Promise((function(t,n){e.onload=function(){t(e.result)},e.onerror=function(){n(e.error)}}))}function p(e){var t=new FileReader,n=c(t);return t.readAsArrayBuffer(e),n}function f(e){if(e.slice)return e.slice(0);var t=new Uint8Array(e.byteLength);return t.set(new Uint8Array(e)),t.buffer}function h(){return this.bodyUsed=!1,this._initBody=function(e){var t;this._bodyInit=e,e?"string"==typeof e?this._bodyText=e:n.blob&&Blob.prototype.isPrototypeOf(e)?this._bodyBlob=e:n.formData&&FormData.prototype.isPrototypeOf(e)?this._bodyFormData=e:n.searchParams&&URLSearchParams.prototype.isPrototypeOf(e)?this._bodyText=e.toString():n.arrayBuffer&&n.blob&&((t=e)&&DataView.prototype.isPrototypeOf(t))?(this._bodyArrayBuffer=f(e.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):n.arrayBuffer&&(ArrayBuffer.prototype.isPrototypeOf(e)||o(e))?this._bodyArrayBuffer=f(e):this._bodyText=e=Object.prototype.toString.call(e):this._bodyText="",this.headers.get("content-type")||("string"==typeof e?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):n.searchParams&&URLSearchParams.prototype.isPrototypeOf(e)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},n.blob&&(this.blob=function(){var e=u(this);if(e)return e;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(new Blob([this._bodyArrayBuffer]));if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))},this.arrayBuffer=function(){return this._bodyArrayBuffer?u(this)||Promise.resolve(this._bodyArrayBuffer):this.blob().then(p)}),this.text=function(){var e,t,n,r=u(this);if(r)return r;if(this._bodyBlob)return e=this._bodyBlob,t=new FileReader,n=c(t),t.readAsText(e),n;if(this._bodyArrayBuffer)return Promise.resolve(function(e){for(var t=new Uint8Array(e),n=new Array(t.length),r=0;r-1?r:n),this.mode=t.mode||this.mode||null,this.signal=t.signal||this.signal,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&o)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(o)}function g(e){var t=new FormData;return e.trim().split("&").forEach((function(e){if(e){var n=e.split("="),r=n.shift().replace(/\+/g," "),o=n.join("=").replace(/\+/g," ");t.append(decodeURIComponent(r),decodeURIComponent(o))}})),t}function y(e,t){t||(t={}),this.type="default",this.status=void 0===t.status?200:t.status,this.ok=this.status>=200&&this.status<300,this.statusText="statusText"in t?t.statusText:"OK",this.headers=new l(t.headers),this.url=t.url||"",this._initBody(e)}m.prototype.clone=function(){return new m(this,{body:this._bodyInit})},h.call(m.prototype),h.call(y.prototype),y.prototype.clone=function(){return new y(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new l(this.headers),url:this.url})},y.error=function(){var e=new y(null,{status:0,statusText:""});return e.type="error",e};var v=[301,302,303,307,308];y.redirect=function(e,t){if(-1===v.indexOf(t))throw new RangeError("Invalid status code");return new y(null,{status:t,headers:{location:e}})},t.DOMException=e.DOMException;try{new t.DOMException}catch(e){t.DOMException=function(e,t){this.message=e,this.name=t;var n=Error(e);this.stack=n.stack},t.DOMException.prototype=Object.create(Error.prototype),t.DOMException.prototype.constructor=t.DOMException}function b(e,r){return new Promise((function(o,a){var i=new m(e,r);if(i.signal&&i.signal.aborted)return a(new t.DOMException("Aborted","AbortError"));var s=new XMLHttpRequest;function u(){s.abort()}s.onload=function(){var e,t,n={status:s.status,statusText:s.statusText,headers:(e=s.getAllResponseHeaders()||"",t=new l,e.replace(/\r?\n[\t ]+/g," ").split(/\r?\n/).forEach((function(e){var n=e.split(":"),r=n.shift().trim();if(r){var o=n.join(":").trim();t.append(r,o)}})),t)};n.url="responseURL"in s?s.responseURL:n.headers.get("X-Request-URL");var r="response"in s?s.response:s.responseText;o(new y(r,n))},s.onerror=function(){a(new TypeError("Network request failed"))},s.ontimeout=function(){a(new TypeError("Network request failed"))},s.onabort=function(){a(new t.DOMException("Aborted","AbortError"))},s.open(i.method,i.url,!0),"include"===i.credentials?s.withCredentials=!0:"omit"===i.credentials&&(s.withCredentials=!1),"responseType"in s&&n.blob&&(s.responseType="blob"),i.headers.forEach((function(e,t){s.setRequestHeader(t,e)})),i.signal&&(i.signal.addEventListener("abort",u),s.onreadystatechange=function(){4===s.readyState&&i.signal.removeEventListener("abort",u)}),s.send(void 0===i._bodyInit?null:i._bodyInit)}))}b.polyfill=!0,e.fetch||(e.fetch=b,e.Headers=l,e.Request=m,e.Response=y),t.Headers=l,t.Request=m,t.Response=y,t.fetch=b,Object.defineProperty(t,"__esModule",{value:!0})}({})}("undefined"!=typeof self?self:this)},8269:function(e,t,n){var r;r=void 0!==n.g?n.g:this,e.exports=function(e){if(e.CSS&&e.CSS.escape)return e.CSS.escape;var t=function(e){if(0==arguments.length)throw new TypeError("`CSS.escape` requires an argument.");for(var t,n=String(e),r=n.length,o=-1,a="",i=n.charCodeAt(0);++o=1&&t<=31||127==t||0==o&&t>=48&&t<=57||1==o&&t>=48&&t<=57&&45==i?"\\"+t.toString(16)+" ":0==o&&1==r&&45==t||!(t>=128||45==t||95==t||t>=48&&t<=57||t>=65&&t<=90||t>=97&&t<=122)?"\\"+n.charAt(o):n.charAt(o):a+="�";return a};return e.CSS||(e.CSS={}),e.CSS.escape=t,t}(r)},27698:(e,t,n)=>{"use strict";var r=n(48764).Buffer;function o(e){return e instanceof r||e instanceof Date||e instanceof RegExp}function a(e){if(e instanceof r){var t=r.alloc?r.alloc(e.length):new r(e.length);return e.copy(t),t}if(e instanceof Date)return new Date(e.getTime());if(e instanceof RegExp)return new RegExp(e);throw new Error("Unexpected situation")}function i(e){var t=[];return e.forEach((function(e,n){"object"==typeof e&&null!==e?Array.isArray(e)?t[n]=i(e):o(e)?t[n]=a(e):t[n]=l({},e):t[n]=e})),t}function s(e,t){return"__proto__"===t?void 0:e[t]}var l=e.exports=function(){if(arguments.length<1||"object"!=typeof arguments[0])return!1;if(arguments.length<2)return arguments[0];var e,t,n=arguments[0];return Array.prototype.slice.call(arguments,1).forEach((function(r){"object"!=typeof r||null===r||Array.isArray(r)||Object.keys(r).forEach((function(u){return t=s(n,u),(e=s(r,u))===n?void 0:"object"!=typeof e||null===e?void(n[u]=e):Array.isArray(e)?void(n[u]=i(e)):o(e)?void(n[u]=a(e)):"object"!=typeof t||null===t||Array.isArray(t)?void(n[u]=l({},e)):void(n[u]=l(t,e))}))})),n}},9996:e=>{"use strict";var t=function(e){return function(e){return!!e&&"object"==typeof e}(e)&&!function(e){var t=Object.prototype.toString.call(e);return"[object RegExp]"===t||"[object Date]"===t||function(e){return e.$$typeof===n}(e)}(e)};var n="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function r(e,t){return!1!==t.clone&&t.isMergeableObject(e)?l((n=e,Array.isArray(n)?[]:{}),e,t):e;var n}function o(e,t,n){return e.concat(t).map((function(e){return r(e,n)}))}function a(e){return Object.keys(e).concat(function(e){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(e).filter((function(t){return Object.propertyIsEnumerable.call(e,t)})):[]}(e))}function i(e,t){try{return t in e}catch(e){return!1}}function s(e,t,n){var o={};return n.isMergeableObject(e)&&a(e).forEach((function(t){o[t]=r(e[t],n)})),a(t).forEach((function(a){(function(e,t){return i(e,t)&&!(Object.hasOwnProperty.call(e,t)&&Object.propertyIsEnumerable.call(e,t))})(e,a)||(i(e,a)&&n.isMergeableObject(t[a])?o[a]=function(e,t){if(!t.customMerge)return l;var n=t.customMerge(e);return"function"==typeof n?n:l}(a,n)(e[a],t[a],n):o[a]=r(t[a],n))})),o}function l(e,n,a){(a=a||{}).arrayMerge=a.arrayMerge||o,a.isMergeableObject=a.isMergeableObject||t,a.cloneUnlessOtherwiseSpecified=r;var i=Array.isArray(n);return i===Array.isArray(e)?i?a.arrayMerge(e,n,a):s(e,n,a):r(n,a)}l.all=function(e,t){if(!Array.isArray(e))throw new Error("first argument should be an array");return e.reduce((function(e,n){return l(e,n,t)}),{})};var u=l;e.exports=u},27856:function(e){e.exports=function(){"use strict";function e(t){return e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e(t)}function t(e,n){return t=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},t(e,n)}function n(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(e){return!1}}function r(e,o,a){return r=n()?Reflect.construct:function(e,n,r){var o=[null];o.push.apply(o,n);var a=new(Function.bind.apply(e,o));return r&&t(a,r.prototype),a},r.apply(null,arguments)}function o(e,t){return s(e)||u(e,t)||c(e,t)||h()}function a(e){return i(e)||l(e)||c(e)||f()}function i(e){if(Array.isArray(e))return p(e)}function s(e){if(Array.isArray(e))return e}function l(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}function u(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=n){var r,o,a=[],i=!0,s=!1;try{for(n=n.call(e);!(i=(r=n.next()).done)&&(a.push(r.value),!t||a.length!==t);i=!0);}catch(e){s=!0,o=e}finally{try{i||null==n.return||n.return()}finally{if(s)throw o}}return a}}function c(e,t){if(e){if("string"==typeof e)return p(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?p(e,t):void 0}}function p(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=e.length?{done:!0}:{done:!1,value:e[r++]}},e:function(e){throw e},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var a,i=!0,s=!1;return{s:function(){n=n.call(e)},n:function(){var e=n.next();return i=e.done,e},e:function(e){s=!0,a=e},f:function(){try{i||null==n.return||n.return()}finally{if(s)throw a}}}}var m=Object.entries,g=Object.setPrototypeOf,y=Object.isFrozen,v=Object.getPrototypeOf,b=Object.getOwnPropertyDescriptor,w=Object.freeze,E=Object.seal,x=Object.create,_="undefined"!=typeof Reflect&&Reflect,S=_.apply,A=_.construct;S||(S=function(e,t,n){return e.apply(t,n)}),w||(w=function(e){return e}),E||(E=function(e){return e}),A||(A=function(e,t){return r(e,a(t))});var C=L(Array.prototype.forEach),k=L(Array.prototype.pop),O=L(Array.prototype.push),j=L(String.prototype.toLowerCase),I=L(String.prototype.toString),T=L(String.prototype.match),N=L(String.prototype.replace),P=L(String.prototype.indexOf),R=L(String.prototype.trim),M=L(RegExp.prototype.test),D=B(TypeError);function L(e){return function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o/gm),te=E(/\${[\w\W]*}/gm),ne=E(/^data-[\-\w.\u00B7-\uFFFF]/),re=E(/^aria-[\-\w]+$/),oe=E(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),ae=E(/^(?:\w+script|data):/i),ie=E(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),se=E(/^html$/i),le=function(){return"undefined"==typeof window?null:window},ue=function(t,n){if("object"!==e(t)||"function"!=typeof t.createPolicy)return null;var r=null,o="data-tt-policy-suffix";n.currentScript&&n.currentScript.hasAttribute(o)&&(r=n.currentScript.getAttribute(o));var a="dompurify"+(r?"#"+r:"");try{return t.createPolicy(a,{createHTML:function(e){return e},createScriptURL:function(e){return e}})}catch(e){return console.warn("TrustedTypes policy "+a+" could not be created."),null}};function ce(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:le(),n=function(e){return ce(e)};if(n.version="3.0.1",n.removed=[],!t||!t.document||9!==t.document.nodeType)return n.isSupported=!1,n;var r=t.document,o=t.document,i=t.DocumentFragment,s=t.HTMLTemplateElement,l=t.Node,u=t.Element,c=t.NodeFilter,p=t.NamedNodeMap,f=void 0===p?t.NamedNodeMap||t.MozNamedAttrMap:p,h=t.HTMLFormElement,d=t.DOMParser,g=t.trustedTypes,y=u.prototype,v=z(y,"cloneNode"),b=z(y,"nextSibling"),E=z(y,"childNodes"),x=z(y,"parentNode");if("function"==typeof s){var _=o.createElement("template");_.content&&_.content.ownerDocument&&(o=_.content.ownerDocument)}var S=ue(g,r),A=S?S.createHTML(""):"",L=o,B=L.implementation,pe=L.createNodeIterator,fe=L.createDocumentFragment,he=L.getElementsByTagName,de=r.importNode,me={};n.isSupported="function"==typeof m&&"function"==typeof x&&B&&void 0!==B.createHTMLDocument;var ge,ye,ve=X,be=ee,we=te,Ee=ne,xe=re,_e=ae,Se=ie,Ae=oe,Ce=null,ke=F({},[].concat(a(q),a($),a(V),a(H),a(K))),Oe=null,je=F({},[].concat(a(G),a(Z),a(Y),a(Q))),Ie=Object.seal(Object.create(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),Te=null,Ne=null,Pe=!0,Re=!0,Me=!1,De=!0,Le=!1,Be=!1,Fe=!1,Ue=!1,ze=!1,qe=!1,$e=!1,Ve=!0,We=!1,He="user-content-",Je=!0,Ke=!1,Ge={},Ze=null,Ye=F({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]),Qe=null,Xe=F({},["audio","video","img","source","image","track"]),et=null,tt=F({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),nt="http://www.w3.org/1998/Math/MathML",rt="http://www.w3.org/2000/svg",ot="http://www.w3.org/1999/xhtml",at=ot,it=!1,st=null,lt=F({},[nt,rt,ot],I),ut=["application/xhtml+xml","text/html"],ct="text/html",pt=null,ft=o.createElement("form"),ht=function(e){return e instanceof RegExp||e instanceof Function},dt=function(t){pt&&pt===t||(t&&"object"===e(t)||(t={}),t=U(t),ge=ge=-1===ut.indexOf(t.PARSER_MEDIA_TYPE)?ct:t.PARSER_MEDIA_TYPE,ye="application/xhtml+xml"===ge?I:j,Ce="ALLOWED_TAGS"in t?F({},t.ALLOWED_TAGS,ye):ke,Oe="ALLOWED_ATTR"in t?F({},t.ALLOWED_ATTR,ye):je,st="ALLOWED_NAMESPACES"in t?F({},t.ALLOWED_NAMESPACES,I):lt,et="ADD_URI_SAFE_ATTR"in t?F(U(tt),t.ADD_URI_SAFE_ATTR,ye):tt,Qe="ADD_DATA_URI_TAGS"in t?F(U(Xe),t.ADD_DATA_URI_TAGS,ye):Xe,Ze="FORBID_CONTENTS"in t?F({},t.FORBID_CONTENTS,ye):Ye,Te="FORBID_TAGS"in t?F({},t.FORBID_TAGS,ye):{},Ne="FORBID_ATTR"in t?F({},t.FORBID_ATTR,ye):{},Ge="USE_PROFILES"in t&&t.USE_PROFILES,Pe=!1!==t.ALLOW_ARIA_ATTR,Re=!1!==t.ALLOW_DATA_ATTR,Me=t.ALLOW_UNKNOWN_PROTOCOLS||!1,De=!1!==t.ALLOW_SELF_CLOSE_IN_ATTR,Le=t.SAFE_FOR_TEMPLATES||!1,Be=t.WHOLE_DOCUMENT||!1,ze=t.RETURN_DOM||!1,qe=t.RETURN_DOM_FRAGMENT||!1,$e=t.RETURN_TRUSTED_TYPE||!1,Ue=t.FORCE_BODY||!1,Ve=!1!==t.SANITIZE_DOM,We=t.SANITIZE_NAMED_PROPS||!1,Je=!1!==t.KEEP_CONTENT,Ke=t.IN_PLACE||!1,Ae=t.ALLOWED_URI_REGEXP||Ae,at=t.NAMESPACE||ot,Ie=t.CUSTOM_ELEMENT_HANDLING||{},t.CUSTOM_ELEMENT_HANDLING&&ht(t.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(Ie.tagNameCheck=t.CUSTOM_ELEMENT_HANDLING.tagNameCheck),t.CUSTOM_ELEMENT_HANDLING&&ht(t.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(Ie.attributeNameCheck=t.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),t.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof t.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(Ie.allowCustomizedBuiltInElements=t.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),Le&&(Re=!1),qe&&(ze=!0),Ge&&(Ce=F({},a(K)),Oe=[],!0===Ge.html&&(F(Ce,q),F(Oe,G)),!0===Ge.svg&&(F(Ce,$),F(Oe,Z),F(Oe,Q)),!0===Ge.svgFilters&&(F(Ce,V),F(Oe,Z),F(Oe,Q)),!0===Ge.mathMl&&(F(Ce,H),F(Oe,Y),F(Oe,Q))),t.ADD_TAGS&&(Ce===ke&&(Ce=U(Ce)),F(Ce,t.ADD_TAGS,ye)),t.ADD_ATTR&&(Oe===je&&(Oe=U(Oe)),F(Oe,t.ADD_ATTR,ye)),t.ADD_URI_SAFE_ATTR&&F(et,t.ADD_URI_SAFE_ATTR,ye),t.FORBID_CONTENTS&&(Ze===Ye&&(Ze=U(Ze)),F(Ze,t.FORBID_CONTENTS,ye)),Je&&(Ce["#text"]=!0),Be&&F(Ce,["html","head","body"]),Ce.table&&(F(Ce,["tbody"]),delete Te.tbody),w&&w(t),pt=t)},mt=F({},["mi","mo","mn","ms","mtext"]),gt=F({},["foreignobject","desc","title","annotation-xml"]),yt=F({},["title","style","font","a","script"]),vt=F({},$);F(vt,V),F(vt,W);var bt=F({},H);F(bt,J);var wt=function(e){var t=x(e);t&&t.tagName||(t={namespaceURI:at,tagName:"template"});var n=j(e.tagName),r=j(t.tagName);return!!st[e.namespaceURI]&&(e.namespaceURI===rt?t.namespaceURI===ot?"svg"===n:t.namespaceURI===nt?"svg"===n&&("annotation-xml"===r||mt[r]):Boolean(vt[n]):e.namespaceURI===nt?t.namespaceURI===ot?"math"===n:t.namespaceURI===rt?"math"===n&>[r]:Boolean(bt[n]):e.namespaceURI===ot?!(t.namespaceURI===rt&&!gt[r])&&!(t.namespaceURI===nt&&!mt[r])&&!bt[n]&&(yt[n]||!vt[n]):!("application/xhtml+xml"!==ge||!st[e.namespaceURI]))},Et=function(e){O(n.removed,{element:e});try{e.parentNode.removeChild(e)}catch(t){e.remove()}},xt=function(e,t){try{O(n.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){O(n.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e&&!Oe[e])if(ze||qe)try{Et(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},_t=function(e){var t,n;if(Ue)e=""+e;else{var r=T(e,/^[\r\n\t ]+/);n=r&&r[0]}"application/xhtml+xml"===ge&&at===ot&&(e=''+e+"");var a=S?S.createHTML(e):e;if(at===ot)try{t=(new d).parseFromString(a,ge)}catch(e){}if(!t||!t.documentElement){t=B.createDocument(at,"template",null);try{t.documentElement.innerHTML=it?A:a}catch(e){}}var i=t.body||t.documentElement;return e&&n&&i.insertBefore(o.createTextNode(n),i.childNodes[0]||null),at===ot?he.call(t,Be?"html":"body")[0]:Be?t.documentElement:i},St=function(e){return pe.call(e.ownerDocument||e,e,c.SHOW_ELEMENT|c.SHOW_COMMENT|c.SHOW_TEXT,null,!1)},At=function(e){return e instanceof h&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof f)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore||"function"!=typeof e.hasChildNodes)},Ct=function(t){return"object"===e(l)?t instanceof l:t&&"object"===e(t)&&"number"==typeof t.nodeType&&"string"==typeof t.nodeName},kt=function(e,t,r){me[e]&&C(me[e],(function(e){e.call(n,t,r,pt)}))},Ot=function(e){var t;if(kt("beforeSanitizeElements",e,null),At(e))return Et(e),!0;var r=ye(e.nodeName);if(kt("uponSanitizeElement",e,{tagName:r,allowedTags:Ce}),e.hasChildNodes()&&!Ct(e.firstElementChild)&&(!Ct(e.content)||!Ct(e.content.firstElementChild))&&M(/<[/\w]/g,e.innerHTML)&&M(/<[/\w]/g,e.textContent))return Et(e),!0;if(!Ce[r]||Te[r]){if(!Te[r]&&It(r)){if(Ie.tagNameCheck instanceof RegExp&&M(Ie.tagNameCheck,r))return!1;if(Ie.tagNameCheck instanceof Function&&Ie.tagNameCheck(r))return!1}if(Je&&!Ze[r]){var o=x(e)||e.parentNode,a=E(e)||e.childNodes;if(a&&o)for(var i=a.length-1;i>=0;--i)o.insertBefore(v(a[i],!0),b(e))}return Et(e),!0}return e instanceof u&&!wt(e)?(Et(e),!0):"noscript"!==r&&"noembed"!==r||!M(/<\/no(script|embed)/i,e.innerHTML)?(Le&&3===e.nodeType&&(t=e.textContent,t=N(t,ve," "),t=N(t,be," "),t=N(t,we," "),e.textContent!==t&&(O(n.removed,{element:e.cloneNode()}),e.textContent=t)),kt("afterSanitizeElements",e,null),!1):(Et(e),!0)},jt=function(e,t,n){if(Ve&&("id"===t||"name"===t)&&(n in o||n in ft))return!1;if(Re&&!Ne[t]&&M(Ee,t));else if(Pe&&M(xe,t));else if(!Oe[t]||Ne[t]){if(!(It(e)&&(Ie.tagNameCheck instanceof RegExp&&M(Ie.tagNameCheck,e)||Ie.tagNameCheck instanceof Function&&Ie.tagNameCheck(e))&&(Ie.attributeNameCheck instanceof RegExp&&M(Ie.attributeNameCheck,t)||Ie.attributeNameCheck instanceof Function&&Ie.attributeNameCheck(t))||"is"===t&&Ie.allowCustomizedBuiltInElements&&(Ie.tagNameCheck instanceof RegExp&&M(Ie.tagNameCheck,n)||Ie.tagNameCheck instanceof Function&&Ie.tagNameCheck(n))))return!1}else if(et[t]);else if(M(Ae,N(n,Se,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==P(n,"data:")||!Qe[e])if(Me&&!M(_e,N(n,Se,"")));else if(n)return!1;return!0},It=function(e){return e.indexOf("-")>0},Tt=function(t){var r,o,a,i;kt("beforeSanitizeAttributes",t,null);var s=t.attributes;if(s){var l={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:Oe};for(i=s.length;i--;){var u=r=s[i],c=u.name,p=u.namespaceURI;if(o="value"===c?r.value:R(r.value),a=ye(c),l.attrName=a,l.attrValue=o,l.keepAttr=!0,l.forceKeepAttr=void 0,kt("uponSanitizeAttribute",t,l),o=l.attrValue,!l.forceKeepAttr&&(xt(c,t),l.keepAttr))if(De||!M(/\/>/i,o)){Le&&(o=N(o,ve," "),o=N(o,be," "),o=N(o,we," "));var f=ye(t.nodeName);if(jt(f,a,o)){if(!We||"id"!==a&&"name"!==a||(xt(c,t),o=He+o),S&&"object"===e(g)&&"function"==typeof g.getAttributeType)if(p);else switch(g.getAttributeType(f,a)){case"TrustedHTML":o=S.createHTML(o);break;case"TrustedScriptURL":o=S.createScriptURL(o)}try{p?t.setAttributeNS(p,c,o):t.setAttribute(c,o),k(n.removed)}catch(e){}}}else xt(c,t)}kt("afterSanitizeAttributes",t,null)}},Nt=function e(t){var n,r=St(t);for(kt("beforeSanitizeShadowDOM",t,null);n=r.nextNode();)kt("uponSanitizeShadowNode",n,null),Ot(n)||(n.content instanceof i&&e(n.content),Tt(n));kt("afterSanitizeShadowDOM",t,null)};return n.sanitize=function(e){var t,o,a,s,u=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if((it=!e)&&(e="\x3c!--\x3e"),"string"!=typeof e&&!Ct(e)){if("function"!=typeof e.toString)throw D("toString is not a function");if("string"!=typeof(e=e.toString()))throw D("dirty is not a string, aborting")}if(!n.isSupported)return e;if(Fe||dt(u),n.removed=[],"string"==typeof e&&(Ke=!1),Ke){if(e.nodeName){var c=ye(e.nodeName);if(!Ce[c]||Te[c])throw D("root node is forbidden and cannot be sanitized in-place")}}else if(e instanceof l)1===(o=(t=_t("\x3c!----\x3e")).ownerDocument.importNode(e,!0)).nodeType&&"BODY"===o.nodeName||"HTML"===o.nodeName?t=o:t.appendChild(o);else{if(!ze&&!Le&&!Be&&-1===e.indexOf("<"))return S&&$e?S.createHTML(e):e;if(!(t=_t(e)))return ze?null:$e?A:""}t&&Ue&&Et(t.firstChild);for(var p=St(Ke?e:t);a=p.nextNode();)Ot(a)||(a.content instanceof i&&Nt(a.content),Tt(a));if(Ke)return e;if(ze){if(qe)for(s=fe.call(t.ownerDocument);t.firstChild;)s.appendChild(t.firstChild);else s=t;return(Oe.shadowroot||Oe.shadowrootmod)&&(s=de.call(r,s,!0)),s}var f=Be?t.outerHTML:t.innerHTML;return Be&&Ce["!doctype"]&&t.ownerDocument&&t.ownerDocument.doctype&&t.ownerDocument.doctype.name&&M(se,t.ownerDocument.doctype.name)&&(f="\n"+f),Le&&(f=N(f,ve," "),f=N(f,be," "),f=N(f,we," ")),S&&$e?S.createHTML(f):f},n.setConfig=function(e){dt(e),Fe=!0},n.clearConfig=function(){pt=null,Fe=!1},n.isValidAttribute=function(e,t,n){pt||dt({});var r=ye(e),o=ye(t);return jt(r,o,n)},n.addHook=function(e,t){"function"==typeof t&&(me[e]=me[e]||[],O(me[e],t))},n.removeHook=function(e){if(me[e])return k(me[e])},n.removeHooks=function(e){me[e]&&(me[e]=[])},n.removeAllHooks=function(){me={}},n}return ce()}()},69450:e=>{"use strict";class t{constructor(e,t){this.low=e,this.high=t,this.length=1+t-e}overlaps(e){return!(this.highe.high)}touches(e){return!(this.high+1e.high)}add(e){return new t(Math.min(this.low,e.low),Math.max(this.high,e.high))}subtract(e){return e.low<=this.low&&e.high>=this.high?[]:e.low>this.low&&e.highe+t.length),0)}add(e,r){var o=e=>{for(var t=0;t{for(var t=0;t{for(var n=0;n{for(var n=t.low;n<=t.high;)e.push(n),n++;return e}),[])}subranges(){return this.ranges.map((e=>({low:e.low,high:e.high,length:1+e.high-e.low})))}}e.exports=n},17187:e=>{"use strict";var t,n="object"==typeof Reflect?Reflect:null,r=n&&"function"==typeof n.apply?n.apply:function(e,t,n){return Function.prototype.apply.call(e,t,n)};t=n&&"function"==typeof n.ownKeys?n.ownKeys:Object.getOwnPropertySymbols?function(e){return Object.getOwnPropertyNames(e).concat(Object.getOwnPropertySymbols(e))}:function(e){return Object.getOwnPropertyNames(e)};var o=Number.isNaN||function(e){return e!=e};function a(){a.init.call(this)}e.exports=a,e.exports.once=function(e,t){return new Promise((function(n,r){function o(n){e.removeListener(t,a),r(n)}function a(){"function"==typeof e.removeListener&&e.removeListener("error",o),n([].slice.call(arguments))}m(e,t,a,{once:!0}),"error"!==t&&function(e,t,n){"function"==typeof e.on&&m(e,"error",t,n)}(e,o,{once:!0})}))},a.EventEmitter=a,a.prototype._events=void 0,a.prototype._eventsCount=0,a.prototype._maxListeners=void 0;var i=10;function s(e){if("function"!=typeof e)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof e)}function l(e){return void 0===e._maxListeners?a.defaultMaxListeners:e._maxListeners}function u(e,t,n,r){var o,a,i,u;if(s(n),void 0===(a=e._events)?(a=e._events=Object.create(null),e._eventsCount=0):(void 0!==a.newListener&&(e.emit("newListener",t,n.listener?n.listener:n),a=e._events),i=a[t]),void 0===i)i=a[t]=n,++e._eventsCount;else if("function"==typeof i?i=a[t]=r?[n,i]:[i,n]:r?i.unshift(n):i.push(n),(o=l(e))>0&&i.length>o&&!i.warned){i.warned=!0;var c=new Error("Possible EventEmitter memory leak detected. "+i.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit");c.name="MaxListenersExceededWarning",c.emitter=e,c.type=t,c.count=i.length,u=c,console&&console.warn&&console.warn(u)}return e}function c(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function p(e,t,n){var r={fired:!1,wrapFn:void 0,target:e,type:t,listener:n},o=c.bind(r);return o.listener=n,r.wrapFn=o,o}function f(e,t,n){var r=e._events;if(void 0===r)return[];var o=r[t];return void 0===o?[]:"function"==typeof o?n?[o.listener||o]:[o]:n?function(e){for(var t=new Array(e.length),n=0;n0&&(i=t[0]),i instanceof Error)throw i;var s=new Error("Unhandled error."+(i?" ("+i.message+")":""));throw s.context=i,s}var l=a[e];if(void 0===l)return!1;if("function"==typeof l)r(l,this,t);else{var u=l.length,c=d(l,u);for(n=0;n=0;a--)if(n[a]===t||n[a].listener===t){i=n[a].listener,o=a;break}if(o<0)return this;0===o?n.shift():function(e,t){for(;t+1=0;r--)this.removeListener(e,t[r]);return this},a.prototype.listeners=function(e){return f(this,e,!0)},a.prototype.rawListeners=function(e){return f(this,e,!1)},a.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):h.call(e,t)},a.prototype.listenerCount=h,a.prototype.eventNames=function(){return this._eventsCount>0?t(this._events):[]}},21102:(e,t,n)=>{"use strict";var r=n(46291),o=a(Error);function a(e){return t.displayName=e.displayName||e.name,t;function t(t){return t&&(t=r.apply(null,arguments)),new e(t)}}e.exports=o,o.eval=a(EvalError),o.range=a(RangeError),o.reference=a(ReferenceError),o.syntax=a(SyntaxError),o.type=a(TypeError),o.uri=a(URIError),o.create=a},46291:e=>{!function(){var t;function n(e){for(var t,n,r,o,a=1,i=[].slice.call(arguments),s=0,l=e.length,u="",c=!1,p=!1,f=function(){return i[a++]},h=function(){for(var n="";/\d/.test(e[s]);)n+=e[s++],t=e[s];return n.length>0?parseInt(n):null};s{"use strict";var t="Function.prototype.bind called on incompatible ",n=Array.prototype.slice,r=Object.prototype.toString,o="[object Function]";e.exports=function(e){var a=this;if("function"!=typeof a||r.call(a)!==o)throw new TypeError(t+a);for(var i,s=n.call(arguments,1),l=Math.max(0,a.length-s.length),u=[],c=0;c{"use strict";var r=n(17648);e.exports=Function.prototype.bind||r},40210:(e,t,n)=>{"use strict";var r,o=SyntaxError,a=Function,i=TypeError,s=function(e){try{return a('"use strict"; return ('+e+").constructor;")()}catch(e){}},l=Object.getOwnPropertyDescriptor;if(l)try{l({},"")}catch(e){l=null}var u=function(){throw new i},c=l?function(){try{return u}catch(e){try{return l(arguments,"callee").get}catch(e){return u}}}():u,p=n(41405)(),f=Object.getPrototypeOf||function(e){return e.__proto__},h={},d="undefined"==typeof Uint8Array?r:f(Uint8Array),m={"%AggregateError%":"undefined"==typeof AggregateError?r:AggregateError,"%Array%":Array,"%ArrayBuffer%":"undefined"==typeof ArrayBuffer?r:ArrayBuffer,"%ArrayIteratorPrototype%":p?f([][Symbol.iterator]()):r,"%AsyncFromSyncIteratorPrototype%":r,"%AsyncFunction%":h,"%AsyncGenerator%":h,"%AsyncGeneratorFunction%":h,"%AsyncIteratorPrototype%":h,"%Atomics%":"undefined"==typeof Atomics?r:Atomics,"%BigInt%":"undefined"==typeof BigInt?r:BigInt,"%BigInt64Array%":"undefined"==typeof BigInt64Array?r:BigInt64Array,"%BigUint64Array%":"undefined"==typeof BigUint64Array?r:BigUint64Array,"%Boolean%":Boolean,"%DataView%":"undefined"==typeof DataView?r:DataView,"%Date%":Date,"%decodeURI%":decodeURI,"%decodeURIComponent%":decodeURIComponent,"%encodeURI%":encodeURI,"%encodeURIComponent%":encodeURIComponent,"%Error%":Error,"%eval%":eval,"%EvalError%":EvalError,"%Float32Array%":"undefined"==typeof Float32Array?r:Float32Array,"%Float64Array%":"undefined"==typeof Float64Array?r:Float64Array,"%FinalizationRegistry%":"undefined"==typeof FinalizationRegistry?r:FinalizationRegistry,"%Function%":a,"%GeneratorFunction%":h,"%Int8Array%":"undefined"==typeof Int8Array?r:Int8Array,"%Int16Array%":"undefined"==typeof Int16Array?r:Int16Array,"%Int32Array%":"undefined"==typeof Int32Array?r:Int32Array,"%isFinite%":isFinite,"%isNaN%":isNaN,"%IteratorPrototype%":p?f(f([][Symbol.iterator]())):r,"%JSON%":"object"==typeof JSON?JSON:r,"%Map%":"undefined"==typeof Map?r:Map,"%MapIteratorPrototype%":"undefined"!=typeof Map&&p?f((new Map)[Symbol.iterator]()):r,"%Math%":Math,"%Number%":Number,"%Object%":Object,"%parseFloat%":parseFloat,"%parseInt%":parseInt,"%Promise%":"undefined"==typeof Promise?r:Promise,"%Proxy%":"undefined"==typeof Proxy?r:Proxy,"%RangeError%":RangeError,"%ReferenceError%":ReferenceError,"%Reflect%":"undefined"==typeof Reflect?r:Reflect,"%RegExp%":RegExp,"%Set%":"undefined"==typeof Set?r:Set,"%SetIteratorPrototype%":"undefined"!=typeof Set&&p?f((new Set)[Symbol.iterator]()):r,"%SharedArrayBuffer%":"undefined"==typeof SharedArrayBuffer?r:SharedArrayBuffer,"%String%":String,"%StringIteratorPrototype%":p?f(""[Symbol.iterator]()):r,"%Symbol%":p?Symbol:r,"%SyntaxError%":o,"%ThrowTypeError%":c,"%TypedArray%":d,"%TypeError%":i,"%Uint8Array%":"undefined"==typeof Uint8Array?r:Uint8Array,"%Uint8ClampedArray%":"undefined"==typeof Uint8ClampedArray?r:Uint8ClampedArray,"%Uint16Array%":"undefined"==typeof Uint16Array?r:Uint16Array,"%Uint32Array%":"undefined"==typeof Uint32Array?r:Uint32Array,"%URIError%":URIError,"%WeakMap%":"undefined"==typeof WeakMap?r:WeakMap,"%WeakRef%":"undefined"==typeof WeakRef?r:WeakRef,"%WeakSet%":"undefined"==typeof WeakSet?r:WeakSet};try{null.error}catch(e){var g=f(f(e));m["%Error.prototype%"]=g}var y=function e(t){var n;if("%AsyncFunction%"===t)n=s("async function () {}");else if("%GeneratorFunction%"===t)n=s("function* () {}");else if("%AsyncGeneratorFunction%"===t)n=s("async function* () {}");else if("%AsyncGenerator%"===t){var r=e("%AsyncGeneratorFunction%");r&&(n=r.prototype)}else if("%AsyncIteratorPrototype%"===t){var o=e("%AsyncGenerator%");o&&(n=f(o.prototype))}return m[t]=n,n},v={"%ArrayBufferPrototype%":["ArrayBuffer","prototype"],"%ArrayPrototype%":["Array","prototype"],"%ArrayProto_entries%":["Array","prototype","entries"],"%ArrayProto_forEach%":["Array","prototype","forEach"],"%ArrayProto_keys%":["Array","prototype","keys"],"%ArrayProto_values%":["Array","prototype","values"],"%AsyncFunctionPrototype%":["AsyncFunction","prototype"],"%AsyncGenerator%":["AsyncGeneratorFunction","prototype"],"%AsyncGeneratorPrototype%":["AsyncGeneratorFunction","prototype","prototype"],"%BooleanPrototype%":["Boolean","prototype"],"%DataViewPrototype%":["DataView","prototype"],"%DatePrototype%":["Date","prototype"],"%ErrorPrototype%":["Error","prototype"],"%EvalErrorPrototype%":["EvalError","prototype"],"%Float32ArrayPrototype%":["Float32Array","prototype"],"%Float64ArrayPrototype%":["Float64Array","prototype"],"%FunctionPrototype%":["Function","prototype"],"%Generator%":["GeneratorFunction","prototype"],"%GeneratorPrototype%":["GeneratorFunction","prototype","prototype"],"%Int8ArrayPrototype%":["Int8Array","prototype"],"%Int16ArrayPrototype%":["Int16Array","prototype"],"%Int32ArrayPrototype%":["Int32Array","prototype"],"%JSONParse%":["JSON","parse"],"%JSONStringify%":["JSON","stringify"],"%MapPrototype%":["Map","prototype"],"%NumberPrototype%":["Number","prototype"],"%ObjectPrototype%":["Object","prototype"],"%ObjProto_toString%":["Object","prototype","toString"],"%ObjProto_valueOf%":["Object","prototype","valueOf"],"%PromisePrototype%":["Promise","prototype"],"%PromiseProto_then%":["Promise","prototype","then"],"%Promise_all%":["Promise","all"],"%Promise_reject%":["Promise","reject"],"%Promise_resolve%":["Promise","resolve"],"%RangeErrorPrototype%":["RangeError","prototype"],"%ReferenceErrorPrototype%":["ReferenceError","prototype"],"%RegExpPrototype%":["RegExp","prototype"],"%SetPrototype%":["Set","prototype"],"%SharedArrayBufferPrototype%":["SharedArrayBuffer","prototype"],"%StringPrototype%":["String","prototype"],"%SymbolPrototype%":["Symbol","prototype"],"%SyntaxErrorPrototype%":["SyntaxError","prototype"],"%TypedArrayPrototype%":["TypedArray","prototype"],"%TypeErrorPrototype%":["TypeError","prototype"],"%Uint8ArrayPrototype%":["Uint8Array","prototype"],"%Uint8ClampedArrayPrototype%":["Uint8ClampedArray","prototype"],"%Uint16ArrayPrototype%":["Uint16Array","prototype"],"%Uint32ArrayPrototype%":["Uint32Array","prototype"],"%URIErrorPrototype%":["URIError","prototype"],"%WeakMapPrototype%":["WeakMap","prototype"],"%WeakSetPrototype%":["WeakSet","prototype"]},b=n(58612),w=n(17642),E=b.call(Function.call,Array.prototype.concat),x=b.call(Function.apply,Array.prototype.splice),_=b.call(Function.call,String.prototype.replace),S=b.call(Function.call,String.prototype.slice),A=b.call(Function.call,RegExp.prototype.exec),C=/[^%.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|%$))/g,k=/\\(\\)?/g,O=function(e){var t=S(e,0,1),n=S(e,-1);if("%"===t&&"%"!==n)throw new o("invalid intrinsic syntax, expected closing `%`");if("%"===n&&"%"!==t)throw new o("invalid intrinsic syntax, expected opening `%`");var r=[];return _(e,C,(function(e,t,n,o){r[r.length]=n?_(o,k,"$1"):t||e})),r},j=function(e,t){var n,r=e;if(w(v,r)&&(r="%"+(n=v[r])[0]+"%"),w(m,r)){var a=m[r];if(a===h&&(a=y(r)),void 0===a&&!t)throw new i("intrinsic "+e+" exists, but is not available. Please file an issue!");return{alias:n,name:r,value:a}}throw new o("intrinsic "+e+" does not exist!")};e.exports=function(e,t){if("string"!=typeof e||0===e.length)throw new i("intrinsic name must be a non-empty string");if(arguments.length>1&&"boolean"!=typeof t)throw new i('"allowMissing" argument must be a boolean');if(null===A(/^%?[^%]*%?$/,e))throw new o("`%` may not be present anywhere but at the beginning and end of the intrinsic name");var n=O(e),r=n.length>0?n[0]:"",a=j("%"+r+"%",t),s=a.name,u=a.value,c=!1,p=a.alias;p&&(r=p[0],x(n,E([0,1],p)));for(var f=1,h=!0;f=n.length){var v=l(u,d);u=(h=!!v)&&"get"in v&&!("originalValue"in v.get)?v.get:u[d]}else h=w(u,d),u=u[d];h&&!c&&(m[s]=u)}}return u}},41405:(e,t,n)=>{"use strict";var r="undefined"!=typeof Symbol&&Symbol,o=n(55419);e.exports=function(){return"function"==typeof r&&("function"==typeof Symbol&&("symbol"==typeof r("foo")&&("symbol"==typeof Symbol("bar")&&o())))}},55419:e=>{"use strict";e.exports=function(){if("function"!=typeof Symbol||"function"!=typeof Object.getOwnPropertySymbols)return!1;if("symbol"==typeof Symbol.iterator)return!0;var e={},t=Symbol("test"),n=Object(t);if("string"==typeof t)return!1;if("[object Symbol]"!==Object.prototype.toString.call(t))return!1;if("[object Symbol]"!==Object.prototype.toString.call(n))return!1;for(t in e[t]=42,e)return!1;if("function"==typeof Object.keys&&0!==Object.keys(e).length)return!1;if("function"==typeof Object.getOwnPropertyNames&&0!==Object.getOwnPropertyNames(e).length)return!1;var r=Object.getOwnPropertySymbols(e);if(1!==r.length||r[0]!==t)return!1;if(!Object.prototype.propertyIsEnumerable.call(e,t))return!1;if("function"==typeof Object.getOwnPropertyDescriptor){var o=Object.getOwnPropertyDescriptor(e,t);if(42!==o.value||!0!==o.enumerable)return!1}return!0}},17642:(e,t,n)=>{"use strict";var r=n(58612);e.exports=r.call(Function.call,Object.prototype.hasOwnProperty)},47802:e=>{function t(e){return e instanceof Map?e.clear=e.delete=e.set=function(){throw new Error("map is read-only")}:e instanceof Set&&(e.add=e.clear=e.delete=function(){throw new Error("set is read-only")}),Object.freeze(e),Object.getOwnPropertyNames(e).forEach((function(n){var r=e[n];"object"!=typeof r||Object.isFrozen(r)||t(r)})),e}var n=t,r=t;n.default=r;class o{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1}ignoreMatch(){this.isMatchIgnored=!0}}function a(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function i(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t];return t.forEach((function(e){for(const t in e)n[t]=e[t]})),n}const s=e=>!!e.kind;class l{constructor(e,t){this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){this.buffer+=a(e)}openNode(e){if(!s(e))return;let t=e.kind;e.sublanguage||(t=`${this.classPrefix}${t}`),this.span(t)}closeNode(e){s(e)&&(this.buffer+="")}value(){return this.buffer}span(e){this.buffer+=``}}class u{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const t={kind:e,children:[]};this.add(t),this.stack.push(t)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t),t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){"string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{u._collapse(e)})))}}class c extends u{constructor(e){super(),this.options=e}addKeyword(e,t){""!==e&&(this.openNode(t),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,t){const n=e.root;n.kind=t,n.sublanguage=!0,this.add(n)}toHTML(){return new l(this,this.options).value()}finalize(){return!0}}function p(e){return e?"string"==typeof e?e:e.source:null}const f=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;const h="[a-zA-Z]\\w*",d="[a-zA-Z_]\\w*",m="\\b\\d+(\\.\\d+)?",g="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",y="\\b(0b[01]+)",v={begin:"\\\\[\\s\\S]",relevance:0},b={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[v]},w={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[v]},E={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},x=function(e,t,n={}){const r=i({className:"comment",begin:e,end:t,contains:[]},n);return r.contains.push(E),r.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),r},_=x("//","$"),S=x("/\\*","\\*/"),A=x("#","$"),C={className:"number",begin:m,relevance:0},k={className:"number",begin:g,relevance:0},O={className:"number",begin:y,relevance:0},j={className:"number",begin:m+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},I={begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[v,{begin:/\[/,end:/\]/,relevance:0,contains:[v]}]}]},T={className:"title",begin:h,relevance:0},N={className:"title",begin:d,relevance:0},P={begin:"\\.\\s*"+d,relevance:0};var R=Object.freeze({__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:h,UNDERSCORE_IDENT_RE:d,NUMBER_RE:m,C_NUMBER_RE:g,BINARY_NUMBER_RE:y,RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(e={})=>{const t=/^#![ ]*\//;return e.binary&&(e.begin=function(...e){return e.map((e=>p(e))).join("")}(t,/.*\b/,e.binary,/\b.*/)),i({className:"meta",begin:t,end:/$/,relevance:0,"on:begin":(e,t)=>{0!==e.index&&t.ignoreMatch()}},e)},BACKSLASH_ESCAPE:v,APOS_STRING_MODE:b,QUOTE_STRING_MODE:w,PHRASAL_WORDS_MODE:E,COMMENT:x,C_LINE_COMMENT_MODE:_,C_BLOCK_COMMENT_MODE:S,HASH_COMMENT_MODE:A,NUMBER_MODE:C,C_NUMBER_MODE:k,BINARY_NUMBER_MODE:O,CSS_NUMBER_MODE:j,REGEXP_MODE:I,TITLE_MODE:T,UNDERSCORE_TITLE_MODE:N,METHOD_GUARD:P,END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{t.data._beginMatch!==e[1]&&t.ignoreMatch()}})}});function M(e,t){"."===e.input[e.index-1]&&t.ignoreMatch()}function D(e,t){t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",e.__beforeBegin=M,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords,void 0===e.relevance&&(e.relevance=0))}function L(e,t){Array.isArray(e.illegal)&&(e.illegal=function(...e){return"("+e.map((e=>p(e))).join("|")+")"}(...e.illegal))}function B(e,t){if(e.match){if(e.begin||e.end)throw new Error("begin & end are not supported with match");e.begin=e.match,delete e.match}}function F(e,t){void 0===e.relevance&&(e.relevance=1)}const U=["of","and","for","in","not","or","if","then","parent","list","value"],z="keyword";function q(e,t,n=z){const r={};return"string"==typeof e?o(n,e.split(" ")):Array.isArray(e)?o(n,e):Object.keys(e).forEach((function(n){Object.assign(r,q(e[n],t,n))})),r;function o(e,n){t&&(n=n.map((e=>e.toLowerCase()))),n.forEach((function(t){const n=t.split("|");r[n[0]]=[e,$(n[0],n[1])]}))}}function $(e,t){return t?Number(t):function(e){return U.includes(e.toLowerCase())}(e)?0:1}function V(e,{plugins:t}){function n(t,n){return new RegExp(p(t),"m"+(e.case_insensitive?"i":"")+(n?"g":""))}class r{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,t){t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]),this.matchAt+=function(e){return new RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const e=this.regexes.map((e=>e[1]));this.matcherRe=n(function(e,t="|"){let n=0;return e.map((e=>{n+=1;const t=n;let r=p(e),o="";for(;r.length>0;){const e=f.exec(r);if(!e){o+=r;break}o+=r.substring(0,e.index),r=r.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?o+="\\"+String(Number(e[1])+t):(o+=e[0],"("===e[0]&&n++)}return o})).map((e=>`(${e})`)).join(t)}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const t=this.matcherRe.exec(e);if(!t)return null;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),r=this.matchIndexes[n];return t.splice(0,n),Object.assign(t,r)}}class o{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const t=new r;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))),t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex;let n=t.exec(e);if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)}return n&&(this.regexIndex+=n.position+1,this.regexIndex===this.count&&this.considerAll()),n}}if(e.compilerExtensions||(e.compilerExtensions=[]),e.contains&&e.contains.includes("self"))throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return e.classNameAliases=i(e.classNameAliases||{}),function t(r,a){const s=r;if(r.isCompiled)return s;[B].forEach((e=>e(r,a))),e.compilerExtensions.forEach((e=>e(r,a))),r.__beforeBegin=null,[D,L,F].forEach((e=>e(r,a))),r.isCompiled=!0;let l=null;if("object"==typeof r.keywords&&(l=r.keywords.$pattern,delete r.keywords.$pattern),r.keywords&&(r.keywords=q(r.keywords,e.case_insensitive)),r.lexemes&&l)throw new Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return l=l||r.lexemes||/\w+/,s.keywordPatternRe=n(l,!0),a&&(r.begin||(r.begin=/\B|\b/),s.beginRe=n(r.begin),r.endSameAsBegin&&(r.end=r.begin),r.end||r.endsWithParent||(r.end=/\B|\b/),r.end&&(s.endRe=n(r.end)),s.terminatorEnd=p(r.end)||"",r.endsWithParent&&a.terminatorEnd&&(s.terminatorEnd+=(r.end?"|":"")+a.terminatorEnd)),r.illegal&&(s.illegalRe=n(r.illegal)),r.contains||(r.contains=[]),r.contains=[].concat(...r.contains.map((function(e){return function(e){e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((function(t){return i(e,{variants:null},t)})));if(e.cachedVariants)return e.cachedVariants;if(W(e))return i(e,{starts:e.starts?i(e.starts):null});if(Object.isFrozen(e))return i(e);return e}("self"===e?r:e)}))),r.contains.forEach((function(e){t(e,s)})),r.starts&&t(r.starts,a),s.matcher=function(e){const t=new o;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin"}))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end"}),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t}(s),s}(e)}function W(e){return!!e&&(e.endsWithParent||W(e.starts))}function H(e){const t={props:["language","code","autodetect"],data:function(){return{detectedLanguage:"",unknownLanguage:!1}},computed:{className(){return this.unknownLanguage?"":"hljs "+this.detectedLanguage},highlighted(){if(!this.autoDetect&&!e.getLanguage(this.language))return console.warn(`The language "${this.language}" you specified could not be found.`),this.unknownLanguage=!0,a(this.code);let t={};return this.autoDetect?(t=e.highlightAuto(this.code),this.detectedLanguage=t.language):(t=e.highlight(this.language,this.code,this.ignoreIllegals),this.detectedLanguage=this.language),t.value},autoDetect(){return!this.language||(e=this.autodetect,Boolean(e||""===e));var e},ignoreIllegals:()=>!0},render(e){return e("pre",{},[e("code",{class:this.className,domProps:{innerHTML:this.highlighted}})])}};return{Component:t,VuePlugin:{install(e){e.component("highlightjs",t)}}}}const J={"after:highlightElement":({el:e,result:t,text:n})=>{const r=G(e);if(!r.length)return;const o=document.createElement("div");o.innerHTML=t.value,t.value=function(e,t,n){let r=0,o="";const i=[];function s(){return e.length&&t.length?e[0].offset!==t[0].offset?e[0].offset"}function u(e){o+=""}function c(e){("start"===e.event?l:u)(e.node)}for(;e.length||t.length;){let t=s();if(o+=a(n.substring(r,t[0].offset)),r=t[0].offset,t===e){i.reverse().forEach(u);do{c(t.splice(0,1)[0]),t=s()}while(t===e&&t.length&&t[0].offset===r);i.reverse().forEach(l)}else"start"===t[0].event?i.push(t[0].node):i.pop(),c(t.splice(0,1)[0])}return o+a(n.substr(r))}(r,G(o),n)}};function K(e){return e.nodeName.toLowerCase()}function G(e){const t=[];return function e(n,r){for(let o=n.firstChild;o;o=o.nextSibling)3===o.nodeType?r+=o.nodeValue.length:1===o.nodeType&&(t.push({event:"start",offset:r,node:o}),r=e(o,r),K(o).match(/br|hr|img|input/)||t.push({event:"stop",offset:r,node:o}));return r}(e,0),t}const Z={},Y=e=>{console.error(e)},Q=(e,...t)=>{console.log(`WARN: ${e}`,...t)},X=(e,t)=>{Z[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),Z[`${e}/${t}`]=!0)},ee=a,te=i,ne=Symbol("nomatch");var re=function(e){const t=Object.create(null),r=Object.create(null),a=[];let i=!0;const s=/(^(<[^>]+>|\t|)+|\n)/gm,l="Could not find the language '{}', did you forget to load/include a language module?",u={disableAutodetect:!0,name:"Plain text",contains:[]};let p={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:c};function f(e){return p.noHighlightRe.test(e)}function h(e,t,n,r){let o="",a="";"object"==typeof t?(o=e,n=t.ignoreIllegals,a=t.language,r=void 0):(X("10.7.0","highlight(lang, code, ...args) has been deprecated."),X("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"),a=e,o=t);const i={code:o,language:a};C("before:highlight",i);const s=i.result?i.result:d(i.language,i.code,n,r);return s.code=i.code,C("after:highlight",s),s}function d(e,n,r,s){function u(e,t){const n=E.case_insensitive?t[0].toLowerCase():t[0];return Object.prototype.hasOwnProperty.call(e.keywords,n)&&e.keywords[n]}function c(){null!=A.subLanguage?function(){if(""===O)return;let e=null;if("string"==typeof A.subLanguage){if(!t[A.subLanguage])return void k.addText(O);e=d(A.subLanguage,O,!0,C[A.subLanguage]),C[A.subLanguage]=e.top}else e=m(O,A.subLanguage.length?A.subLanguage:null);A.relevance>0&&(j+=e.relevance),k.addSublanguage(e.emitter,e.language)}():function(){if(!A.keywords)return void k.addText(O);let e=0;A.keywordPatternRe.lastIndex=0;let t=A.keywordPatternRe.exec(O),n="";for(;t;){n+=O.substring(e,t.index);const r=u(A,t);if(r){const[e,o]=r;if(k.addText(n),n="",j+=o,e.startsWith("_"))n+=t[0];else{const n=E.classNameAliases[e]||e;k.addKeyword(t[0],n)}}else n+=t[0];e=A.keywordPatternRe.lastIndex,t=A.keywordPatternRe.exec(O)}n+=O.substr(e),k.addText(n)}(),O=""}function f(e){return e.className&&k.openNode(E.classNameAliases[e.className]||e.className),A=Object.create(e,{parent:{value:A}}),A}function h(e,t,n){let r=function(e,t){const n=e&&e.exec(t);return n&&0===n.index}(e.endRe,n);if(r){if(e["on:end"]){const n=new o(e);e["on:end"](t,n),n.isMatchIgnored&&(r=!1)}if(r){for(;e.endsParent&&e.parent;)e=e.parent;return e}}if(e.endsWithParent)return h(e.parent,t,n)}function g(e){return 0===A.matcher.regexIndex?(O+=e[0],1):(N=!0,0)}function y(e){const t=e[0],n=e.rule,r=new o(n),a=[n.__beforeBegin,n["on:begin"]];for(const n of a)if(n&&(n(e,r),r.isMatchIgnored))return g(t);return n&&n.endSameAsBegin&&(n.endRe=new RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),n.skip?O+=t:(n.excludeBegin&&(O+=t),c(),n.returnBegin||n.excludeBegin||(O=t)),f(n),n.returnBegin?0:t.length}function v(e){const t=e[0],r=n.substr(e.index),o=h(A,e,r);if(!o)return ne;const a=A;a.skip?O+=t:(a.returnEnd||a.excludeEnd||(O+=t),c(),a.excludeEnd&&(O=t));do{A.className&&k.closeNode(),A.skip||A.subLanguage||(j+=A.relevance),A=A.parent}while(A!==o.parent);return o.starts&&(o.endSameAsBegin&&(o.starts.endRe=o.endRe),f(o.starts)),a.returnEnd?0:t.length}let b={};function w(t,o){const a=o&&o[0];if(O+=t,null==a)return c(),0;if("begin"===b.type&&"end"===o.type&&b.index===o.index&&""===a){if(O+=n.slice(o.index,o.index+1),!i){const t=new Error("0 width match regex");throw t.languageName=e,t.badRule=b.rule,t}return 1}if(b=o,"begin"===o.type)return y(o);if("illegal"===o.type&&!r){const e=new Error('Illegal lexeme "'+a+'" for mode "'+(A.className||"")+'"');throw e.mode=A,e}if("end"===o.type){const e=v(o);if(e!==ne)return e}if("illegal"===o.type&&""===a)return 1;if(T>1e5&&T>3*o.index){throw new Error("potential infinite loop, way more iterations than matches")}return O+=a,a.length}const E=_(e);if(!E)throw Y(l.replace("{}",e)),new Error('Unknown language: "'+e+'"');const x=V(E,{plugins:a});let S="",A=s||x;const C={},k=new p.__emitter(p);!function(){const e=[];for(let t=A;t!==E;t=t.parent)t.className&&e.unshift(t.className);e.forEach((e=>k.openNode(e)))}();let O="",j=0,I=0,T=0,N=!1;try{for(A.matcher.considerAll();;){T++,N?N=!1:A.matcher.considerAll(),A.matcher.lastIndex=I;const e=A.matcher.exec(n);if(!e)break;const t=w(n.substring(I,e.index),e);I=e.index+t}return w(n.substr(I)),k.closeAllNodes(),k.finalize(),S=k.toHTML(),{relevance:Math.floor(j),value:S,language:e,illegal:!1,emitter:k,top:A}}catch(t){if(t.message&&t.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:t.message,context:n.slice(I-100,I+100),mode:t.mode},sofar:S,relevance:0,value:ee(n),emitter:k};if(i)return{illegal:!1,relevance:0,value:ee(n),emitter:k,language:e,top:A,errorRaised:t};throw t}}function m(e,n){n=n||p.languages||Object.keys(t);const r=function(e){const t={relevance:0,emitter:new p.__emitter(p),value:ee(e),illegal:!1,top:u};return t.emitter.addText(e),t}(e),o=n.filter(_).filter(A).map((t=>d(t,e,!1)));o.unshift(r);const a=o.sort(((e,t)=>{if(e.relevance!==t.relevance)return t.relevance-e.relevance;if(e.language&&t.language){if(_(e.language).supersetOf===t.language)return 1;if(_(t.language).supersetOf===e.language)return-1}return 0})),[i,s]=a,l=i;return l.second_best=s,l}const g={"before:highlightElement":({el:e})=>{p.useBR&&(e.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n"))},"after:highlightElement":({result:e})=>{p.useBR&&(e.value=e.value.replace(/\n/g,"
"))}},y=/^(<[^>]+>|\t)+/gm,v={"after:highlightElement":({result:e})=>{p.tabReplace&&(e.value=e.value.replace(y,(e=>e.replace(/\t/g,p.tabReplace))))}};function b(e){let t=null;const n=function(e){let t=e.className+" ";t+=e.parentNode?e.parentNode.className:"";const n=p.languageDetectRe.exec(t);if(n){const t=_(n[1]);return t||(Q(l.replace("{}",n[1])),Q("Falling back to no-highlight mode for this block.",e)),t?n[1]:"no-highlight"}return t.split(/\s+/).find((e=>f(e)||_(e)))}(e);if(f(n))return;C("before:highlightElement",{el:e,language:n}),t=e;const o=t.textContent,a=n?h(o,{language:n,ignoreIllegals:!0}):m(o);C("after:highlightElement",{el:e,result:a,text:o}),e.innerHTML=a.value,function(e,t,n){const o=t?r[t]:n;e.classList.add("hljs"),o&&e.classList.add(o)}(e,n,a.language),e.result={language:a.language,re:a.relevance,relavance:a.relevance},a.second_best&&(e.second_best={language:a.second_best.language,re:a.second_best.relevance,relavance:a.second_best.relevance})}const w=()=>{if(w.called)return;w.called=!0,X("10.6.0","initHighlighting() is deprecated. Use highlightAll() instead.");document.querySelectorAll("pre code").forEach(b)};let E=!1;function x(){if("loading"===document.readyState)return void(E=!0);document.querySelectorAll("pre code").forEach(b)}function _(e){return e=(e||"").toLowerCase(),t[e]||t[r[e]]}function S(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{r[e.toLowerCase()]=t}))}function A(e){const t=_(e);return t&&!t.disableAutodetect}function C(e,t){const n=e;a.forEach((function(e){e[n]&&e[n](t)}))}"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(function(){E&&x()}),!1),Object.assign(e,{highlight:h,highlightAuto:m,highlightAll:x,fixMarkup:function(e){return X("10.2.0","fixMarkup will be removed entirely in v11.0"),X("10.2.0","Please see https://github.com/highlightjs/highlight.js/issues/2534"),t=e,p.tabReplace||p.useBR?t.replace(s,(e=>"\n"===e?p.useBR?"
":e:p.tabReplace?e.replace(/\t/g,p.tabReplace):e)):t;var t},highlightElement:b,highlightBlock:function(e){return X("10.7.0","highlightBlock will be removed entirely in v12.0"),X("10.7.0","Please use highlightElement now."),b(e)},configure:function(e){e.useBR&&(X("10.3.0","'useBR' will be removed entirely in v11.0"),X("10.3.0","Please see https://github.com/highlightjs/highlight.js/issues/2559")),p=te(p,e)},initHighlighting:w,initHighlightingOnLoad:function(){X("10.6.0","initHighlightingOnLoad() is deprecated. Use highlightAll() instead."),E=!0},registerLanguage:function(n,r){let o=null;try{o=r(e)}catch(e){if(Y("Language definition for '{}' could not be registered.".replace("{}",n)),!i)throw e;Y(e),o=u}o.name||(o.name=n),t[n]=o,o.rawDefinition=r.bind(null,e),o.aliases&&S(o.aliases,{languageName:n})},unregisterLanguage:function(e){delete t[e];for(const t of Object.keys(r))r[t]===e&&delete r[t]},listLanguages:function(){return Object.keys(t)},getLanguage:_,registerAliases:S,requireLanguage:function(e){X("10.4.0","requireLanguage will be removed entirely in v11."),X("10.4.0","Please see https://github.com/highlightjs/highlight.js/pull/2844");const t=_(e);if(t)return t;throw new Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:A,inherit:te,addPlugin:function(e){!function(e){e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=t=>{e["before:highlightBlock"](Object.assign({block:t.el},t))}),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=t=>{e["after:highlightBlock"](Object.assign({block:t.el},t))})}(e),a.push(e)},vuePlugin:H(e).VuePlugin}),e.debugMode=function(){i=!1},e.safeMode=function(){i=!0},e.versionString="10.7.3";for(const e in R)"object"==typeof R[e]&&n(R[e]);return Object.assign(e,R),e.addPlugin(g),e.addPlugin(J),e.addPlugin(v),e}({});e.exports=re},61519:e=>{function t(...e){return e.map((e=>{return(t=e)?"string"==typeof t?t:t.source:null;var t})).join("")}e.exports=function(e){const n={},r={begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/,contains:[n]}]};Object.assign(n,{className:"variable",variants:[{begin:t(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},r]});const o={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},a={begin:/<<-?\s*(?=\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,className:"string"})]}},i={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,n,o]};o.contains.push(i);const s={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,n]},l=e.SHEBANG({binary:`(${["fish","bash","zsh","sh","csh","ksh","tcsh","dash","scsh"].join("|")})`,relevance:10}),u={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b[a-z._-]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp"},contains:[l,e.SHEBANG(),u,s,e.HASH_COMMENT_MODE,a,i,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},n]}}},30786:e=>{function t(...e){return e.map((e=>{return(t=e)?"string"==typeof t?t:t.source:null;var t})).join("")}e.exports=function(e){const n="HTTP/(2|1\\.[01])",r={className:"attribute",begin:t("^",/[A-Za-z][A-Za-z0-9-]*/,"(?=\\:\\s)"),starts:{contains:[{className:"punctuation",begin:/: /,relevance:0,starts:{end:"$",relevance:0}}]}},o=[r,{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}];return{name:"HTTP",aliases:["https"],illegal:/\S/,contains:[{begin:"^(?="+n+" \\d{3})",end:/$/,contains:[{className:"meta",begin:n},{className:"number",begin:"\\b\\d{3}\\b"}],starts:{end:/\b\B/,illegal:/\S/,contains:o}},{begin:"(?=^[A-Z]+ (.*?) "+n+"$)",end:/$/,contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{className:"meta",begin:n},{className:"keyword",begin:"[A-Z]+"}],starts:{end:/\b\B/,illegal:/\S/,contains:o}},e.inherit(r,{relevance:0})]}}},96344:e=>{const t="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],r=["true","false","null","undefined","NaN","Infinity"],o=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer","BigInt64Array","BigUint64Array","BigInt"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function a(e){return i("(?=",e,")")}function i(...e){return e.map((e=>{return(t=e)?"string"==typeof t?t:t.source:null;var t})).join("")}e.exports=function(e){const s=t,l="<>",u="",c={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,t)=>{const n=e[0].length+e.index,r=e.input[n];"<"!==r?">"===r&&(((e,{after:t})=>{const n="",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:e.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:p,contains:_}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:l,end:u},{begin:c.begin,"on:begin":c.isTrulyOpeningTag,end:c.end}],subLanguage:"xml",contains:[{begin:c.begin,end:c.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[{;]/,excludeEnd:!0,keywords:p,contains:["self",e.inherit(e.TITLE_MODE,{begin:s}),S],illegal:/%/},{beginKeywords:"while if switch catch for"},{className:"function",begin:e.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",returnBegin:!0,contains:[S,e.inherit(e.TITLE_MODE,{begin:s})]},{variants:[{begin:"\\."+s},{begin:"\\$"+s}],relevance:0},{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"[\]]/,contains:[{beginKeywords:"extends"},e.UNDERSCORE_TITLE_MODE]},{begin:/\b(?=constructor)/,end:/[{;]/,excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:s}),"self",S]},{begin:"(get|set)\\s+(?="+s+"\\()",end:/\{/,keywords:"get set",contains:[e.inherit(e.TITLE_MODE,{begin:s}),{begin:/\(\)/},S]},{begin:/\$[(.]/}]}}},82026:e=>{e.exports=function(e){const t={literal:"true false null"},n=[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE],r=[e.QUOTE_STRING_MODE,e.C_NUMBER_MODE],o={end:",",endsWithParent:!0,excludeEnd:!0,contains:r,keywords:t},a={begin:/\{/,end:/\}/,contains:[{className:"attr",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE],illegal:"\\n"},e.inherit(o,{begin:/:/})].concat(n),illegal:"\\S"},i={begin:"\\[",end:"\\]",contains:[e.inherit(o)],illegal:"\\S"};return r.push(a,i),n.forEach((function(e){r.push(e)})),{name:"JSON",contains:r,keywords:t,illegal:"\\S"}}},66336:e=>{e.exports=function(e){const t={$pattern:/-?[A-z\.\-]+\b/,keyword:"if else foreach return do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch hidden static parameter",built_in:"ac asnp cat cd CFS chdir clc clear clhy cli clp cls clv cnsn compare copy cp cpi cpp curl cvpa dbp del diff dir dnsn ebp echo|0 epal epcsv epsn erase etsn exsn fc fhx fl ft fw gal gbp gc gcb gci gcm gcs gdr gerr ghy gi gin gjb gl gm gmo gp gps gpv group gsn gsnp gsv gtz gu gv gwmi h history icm iex ihy ii ipal ipcsv ipmo ipsn irm ise iwmi iwr kill lp ls man md measure mi mount move mp mv nal ndr ni nmo npssc nsn nv ogv oh popd ps pushd pwd r rbp rcjb rcsn rd rdr ren ri rjb rm rmdir rmo rni rnp rp rsn rsnp rujb rv rvpa rwmi sajb sal saps sasv sbp sc scb select set shcm si sl sleep sls sort sp spjb spps spsv start stz sujb sv swmi tee trcm type wget where wjb write"},n={begin:"`[\\s\\S]",relevance:0},r={className:"variable",variants:[{begin:/\$\B/},{className:"keyword",begin:/\$this/},{begin:/\$[\w\d][\w\d_:]*/}]},o={className:"string",variants:[{begin:/"/,end:/"/},{begin:/@"/,end:/^"@/}],contains:[n,r,{className:"variable",begin:/\$[A-z]/,end:/[^A-z]/}]},a={className:"string",variants:[{begin:/'/,end:/'/},{begin:/@'/,end:/^'@/}]},i=e.inherit(e.COMMENT(null,null),{variants:[{begin:/#/,end:/$/},{begin:/<#/,end:/#>/}],contains:[{className:"doctag",variants:[{begin:/\.(synopsis|description|example|inputs|outputs|notes|link|component|role|functionality)/},{begin:/\.(parameter|forwardhelptargetname|forwardhelpcategory|remotehelprunspace|externalhelp)\s+\S+/}]}]}),s={className:"built_in",variants:[{begin:"(".concat("Add|Clear|Close|Copy|Enter|Exit|Find|Format|Get|Hide|Join|Lock|Move|New|Open|Optimize|Pop|Push|Redo|Remove|Rename|Reset|Resize|Search|Select|Set|Show|Skip|Split|Step|Switch|Undo|Unlock|Watch|Backup|Checkpoint|Compare|Compress|Convert|ConvertFrom|ConvertTo|Dismount|Edit|Expand|Export|Group|Import|Initialize|Limit|Merge|Mount|Out|Publish|Restore|Save|Sync|Unpublish|Update|Approve|Assert|Build|Complete|Confirm|Deny|Deploy|Disable|Enable|Install|Invoke|Register|Request|Restart|Resume|Start|Stop|Submit|Suspend|Uninstall|Unregister|Wait|Debug|Measure|Ping|Repair|Resolve|Test|Trace|Connect|Disconnect|Read|Receive|Send|Write|Block|Grant|Protect|Revoke|Unblock|Unprotect|Use|ForEach|Sort|Tee|Where",")+(-)[\\w\\d]+")}]},l={className:"class",beginKeywords:"class enum",end:/\s*[{]/,excludeEnd:!0,relevance:0,contains:[e.TITLE_MODE]},u={className:"function",begin:/function\s+/,end:/\s*\{|$/,excludeEnd:!0,returnBegin:!0,relevance:0,contains:[{begin:"function",relevance:0,className:"keyword"},{className:"title",begin:/\w[\w\d]*((-)[\w\d]+)*/,relevance:0},{begin:/\(/,end:/\)/,className:"params",relevance:0,contains:[r]}]},c={begin:/using\s/,end:/$/,returnBegin:!0,contains:[o,a,{className:"keyword",begin:/(using|assembly|command|module|namespace|type)/}]},p={variants:[{className:"operator",begin:"(".concat("-and|-as|-band|-bnot|-bor|-bxor|-casesensitive|-ccontains|-ceq|-cge|-cgt|-cle|-clike|-clt|-cmatch|-cne|-cnotcontains|-cnotlike|-cnotmatch|-contains|-creplace|-csplit|-eq|-exact|-f|-file|-ge|-gt|-icontains|-ieq|-ige|-igt|-ile|-ilike|-ilt|-imatch|-in|-ine|-inotcontains|-inotlike|-inotmatch|-ireplace|-is|-isnot|-isplit|-join|-le|-like|-lt|-match|-ne|-not|-notcontains|-notin|-notlike|-notmatch|-or|-regex|-replace|-shl|-shr|-split|-wildcard|-xor",")\\b")},{className:"literal",begin:/(-)[\w\d]+/,relevance:0}]},f={className:"function",begin:/\[.*\]\s*[\w]+[ ]??\(/,end:/$/,returnBegin:!0,relevance:0,contains:[{className:"keyword",begin:"(".concat(t.keyword.toString().replace(/\s/g,"|"),")\\b"),endsParent:!0,relevance:0},e.inherit(e.TITLE_MODE,{endsParent:!0})]},h=[f,i,n,e.NUMBER_MODE,o,a,s,r,{className:"literal",begin:/\$(null|true|false)\b/},{className:"selector-tag",begin:/@\B/,relevance:0}],d={begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[].concat("self",h,{begin:"("+["string","char","byte","int","long","bool","decimal","single","double","DateTime","xml","array","hashtable","void"].join("|")+")",className:"built_in",relevance:0},{className:"type",begin:/[\.\w\d]+/,relevance:0})};return f.contains.unshift(d),{name:"PowerShell",aliases:["ps","ps1"],case_insensitive:!0,keywords:t,contains:h.concat(l,u,c,p,d)}}},42157:e=>{function t(e){return e?"string"==typeof e?e:e.source:null}function n(e){return r("(?=",e,")")}function r(...e){return e.map((e=>t(e))).join("")}function o(...e){return"("+e.map((e=>t(e))).join("|")+")"}e.exports=function(e){const t=r(/[A-Z_]/,r("(",/[A-Z0-9_.-]*:/,")?"),/[A-Z0-9_.-]*/),a={className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},i={begin:/\s/,contains:[{className:"meta-keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]},s=e.inherit(i,{begin:/\(/,end:/\)/}),l=e.inherit(e.APOS_STRING_MODE,{className:"meta-string"}),u=e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"}),c={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin://,relevance:10,contains:[i,u,l,s,{begin:/\[/,end:/\]/,contains:[{className:"meta",begin://,contains:[i,s,u,l]}]}]},e.COMMENT(//,{relevance:10}),{begin://,relevance:10},a,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:/)/,end:/>/,keywords:{name:"style"},contains:[c],starts:{end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:/)/,end:/>/,keywords:{name:"script"},contains:[c],starts:{end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:/<>|<\/>/},{className:"tag",begin:r(//,/>/,/\s/)))),end:/\/?>/,contains:[{className:"name",begin:t,relevance:0,starts:c}]},{className:"tag",begin:r(/<\//,n(r(t,/>/))),contains:[{className:"name",begin:t,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}}},54587:e=>{e.exports=function(e){var t="true false yes no null",n="[\\w#;/?:@&=+$,.~*'()[\\]]+",r={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},o=e.inherit(r,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),a={className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},i={end:",",endsWithParent:!0,excludeEnd:!0,keywords:t,relevance:0},s={begin:/\{/,end:/\}/,contains:[i],illegal:"\\n",relevance:0},l={begin:"\\[",end:"\\]",contains:[i],illegal:"\\n",relevance:0},u=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$",relevance:10},{className:"string",begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+n},{className:"type",begin:"!<"+n+">"},{className:"type",begin:"!"+n},{className:"type",begin:"!!"+n},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:t,keywords:{literal:t}},a,{className:"number",begin:e.C_NUMBER_RE+"\\b",relevance:0},s,l,r],c=[...u];return c.pop(),c.push(o),i.contains=c,{name:"YAML",case_insensitive:!0,aliases:["yml"],contains:u}}},8679:(e,t,n)=>{"use strict";var r=n(59864),o={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},a={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},i={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},s={};function l(e){return r.isMemo(e)?i:s[e.$$typeof]||o}s[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},s[r.Memo]=i;var u=Object.defineProperty,c=Object.getOwnPropertyNames,p=Object.getOwnPropertySymbols,f=Object.getOwnPropertyDescriptor,h=Object.getPrototypeOf,d=Object.prototype;e.exports=function e(t,n,r){if("string"!=typeof n){if(d){var o=h(n);o&&o!==d&&e(t,o,r)}var i=c(n);p&&(i=i.concat(p(n)));for(var s=l(t),m=l(n),g=0;g{t.read=function(e,t,n,r,o){var a,i,s=8*o-r-1,l=(1<>1,c=-7,p=n?o-1:0,f=n?-1:1,h=e[t+p];for(p+=f,a=h&(1<<-c)-1,h>>=-c,c+=s;c>0;a=256*a+e[t+p],p+=f,c-=8);for(i=a&(1<<-c)-1,a>>=-c,c+=r;c>0;i=256*i+e[t+p],p+=f,c-=8);if(0===a)a=1-u;else{if(a===l)return i?NaN:1/0*(h?-1:1);i+=Math.pow(2,r),a-=u}return(h?-1:1)*i*Math.pow(2,a-r)},t.write=function(e,t,n,r,o,a){var i,s,l,u=8*a-o-1,c=(1<>1,f=23===o?Math.pow(2,-24)-Math.pow(2,-77):0,h=r?0:a-1,d=r?1:-1,m=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(s=isNaN(t)?1:0,i=c):(i=Math.floor(Math.log(t)/Math.LN2),t*(l=Math.pow(2,-i))<1&&(i--,l*=2),(t+=i+p>=1?f/l:f*Math.pow(2,1-p))*l>=2&&(i++,l/=2),i+p>=c?(s=0,i=c):i+p>=1?(s=(t*l-1)*Math.pow(2,o),i+=p):(s=t*Math.pow(2,p-1)*Math.pow(2,o),i=0));o>=8;e[n+h]=255&s,h+=d,s/=256,o-=8);for(i=i<0;e[n+h]=255&i,h+=d,i/=256,u-=8);e[n+h-d]|=128*m}},43393:function(e){e.exports=function(){"use strict";var e=Array.prototype.slice;function t(e,t){t&&(e.prototype=Object.create(t.prototype)),e.prototype.constructor=e}function n(e){return i(e)?e:J(e)}function r(e){return s(e)?e:K(e)}function o(e){return l(e)?e:G(e)}function a(e){return i(e)&&!u(e)?e:Z(e)}function i(e){return!(!e||!e[p])}function s(e){return!(!e||!e[f])}function l(e){return!(!e||!e[h])}function u(e){return s(e)||l(e)}function c(e){return!(!e||!e[d])}t(r,n),t(o,n),t(a,n),n.isIterable=i,n.isKeyed=s,n.isIndexed=l,n.isAssociative=u,n.isOrdered=c,n.Keyed=r,n.Indexed=o,n.Set=a;var p="@@__IMMUTABLE_ITERABLE__@@",f="@@__IMMUTABLE_KEYED__@@",h="@@__IMMUTABLE_INDEXED__@@",d="@@__IMMUTABLE_ORDERED__@@",m="delete",g=5,y=1<>>0;if(""+n!==t||4294967295===n)return NaN;t=n}return t<0?C(e)+t:t}function O(){return!0}function j(e,t,n){return(0===e||void 0!==n&&e<=-n)&&(void 0===t||void 0!==n&&t>=n)}function I(e,t){return N(e,t,0)}function T(e,t){return N(e,t,t)}function N(e,t,n){return void 0===e?n:e<0?Math.max(0,t+e):void 0===t?e:Math.min(t,e)}var P=0,R=1,M=2,D="function"==typeof Symbol&&Symbol.iterator,L="@@iterator",B=D||L;function F(e){this.next=e}function U(e,t,n,r){var o=0===e?t:1===e?n:[t,n];return r?r.value=o:r={value:o,done:!1},r}function z(){return{value:void 0,done:!0}}function q(e){return!!W(e)}function $(e){return e&&"function"==typeof e.next}function V(e){var t=W(e);return t&&t.call(e)}function W(e){var t=e&&(D&&e[D]||e[L]);if("function"==typeof t)return t}function H(e){return e&&"number"==typeof e.length}function J(e){return null==e?ie():i(e)?e.toSeq():ue(e)}function K(e){return null==e?ie().toKeyedSeq():i(e)?s(e)?e.toSeq():e.fromEntrySeq():se(e)}function G(e){return null==e?ie():i(e)?s(e)?e.entrySeq():e.toIndexedSeq():le(e)}function Z(e){return(null==e?ie():i(e)?s(e)?e.entrySeq():e:le(e)).toSetSeq()}F.prototype.toString=function(){return"[Iterator]"},F.KEYS=P,F.VALUES=R,F.ENTRIES=M,F.prototype.inspect=F.prototype.toSource=function(){return this.toString()},F.prototype[B]=function(){return this},t(J,n),J.of=function(){return J(arguments)},J.prototype.toSeq=function(){return this},J.prototype.toString=function(){return this.__toString("Seq {","}")},J.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},J.prototype.__iterate=function(e,t){return pe(this,e,t,!0)},J.prototype.__iterator=function(e,t){return fe(this,e,t,!0)},t(K,J),K.prototype.toKeyedSeq=function(){return this},t(G,J),G.of=function(){return G(arguments)},G.prototype.toIndexedSeq=function(){return this},G.prototype.toString=function(){return this.__toString("Seq [","]")},G.prototype.__iterate=function(e,t){return pe(this,e,t,!1)},G.prototype.__iterator=function(e,t){return fe(this,e,t,!1)},t(Z,J),Z.of=function(){return Z(arguments)},Z.prototype.toSetSeq=function(){return this},J.isSeq=ae,J.Keyed=K,J.Set=Z,J.Indexed=G;var Y,Q,X,ee="@@__IMMUTABLE_SEQ__@@";function te(e){this._array=e,this.size=e.length}function ne(e){var t=Object.keys(e);this._object=e,this._keys=t,this.size=t.length}function re(e){this._iterable=e,this.size=e.length||e.size}function oe(e){this._iterator=e,this._iteratorCache=[]}function ae(e){return!(!e||!e[ee])}function ie(){return Y||(Y=new te([]))}function se(e){var t=Array.isArray(e)?new te(e).fromEntrySeq():$(e)?new oe(e).fromEntrySeq():q(e)?new re(e).fromEntrySeq():"object"==typeof e?new ne(e):void 0;if(!t)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+e);return t}function le(e){var t=ce(e);if(!t)throw new TypeError("Expected Array or iterable object of values: "+e);return t}function ue(e){var t=ce(e)||"object"==typeof e&&new ne(e);if(!t)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+e);return t}function ce(e){return H(e)?new te(e):$(e)?new oe(e):q(e)?new re(e):void 0}function pe(e,t,n,r){var o=e._cache;if(o){for(var a=o.length-1,i=0;i<=a;i++){var s=o[n?a-i:i];if(!1===t(s[1],r?s[0]:i,e))return i+1}return i}return e.__iterateUncached(t,n)}function fe(e,t,n,r){var o=e._cache;if(o){var a=o.length-1,i=0;return new F((function(){var e=o[n?a-i:i];return i++>a?z():U(t,r?e[0]:i-1,e[1])}))}return e.__iteratorUncached(t,n)}function he(e,t){return t?de(t,e,"",{"":e}):me(e)}function de(e,t,n,r){return Array.isArray(t)?e.call(r,n,G(t).map((function(n,r){return de(e,n,r,t)}))):ge(t)?e.call(r,n,K(t).map((function(n,r){return de(e,n,r,t)}))):t}function me(e){return Array.isArray(e)?G(e).map(me).toList():ge(e)?K(e).map(me).toMap():e}function ge(e){return e&&(e.constructor===Object||void 0===e.constructor)}function ye(e,t){if(e===t||e!=e&&t!=t)return!0;if(!e||!t)return!1;if("function"==typeof e.valueOf&&"function"==typeof t.valueOf){if((e=e.valueOf())===(t=t.valueOf())||e!=e&&t!=t)return!0;if(!e||!t)return!1}return!("function"!=typeof e.equals||"function"!=typeof t.equals||!e.equals(t))}function ve(e,t){if(e===t)return!0;if(!i(t)||void 0!==e.size&&void 0!==t.size&&e.size!==t.size||void 0!==e.__hash&&void 0!==t.__hash&&e.__hash!==t.__hash||s(e)!==s(t)||l(e)!==l(t)||c(e)!==c(t))return!1;if(0===e.size&&0===t.size)return!0;var n=!u(e);if(c(e)){var r=e.entries();return t.every((function(e,t){var o=r.next().value;return o&&ye(o[1],e)&&(n||ye(o[0],t))}))&&r.next().done}var o=!1;if(void 0===e.size)if(void 0===t.size)"function"==typeof e.cacheResult&&e.cacheResult();else{o=!0;var a=e;e=t,t=a}var p=!0,f=t.__iterate((function(t,r){if(n?!e.has(t):o?!ye(t,e.get(r,b)):!ye(e.get(r,b),t))return p=!1,!1}));return p&&e.size===f}function be(e,t){if(!(this instanceof be))return new be(e,t);if(this._value=e,this.size=void 0===t?1/0:Math.max(0,t),0===this.size){if(Q)return Q;Q=this}}function we(e,t){if(!e)throw new Error(t)}function Ee(e,t,n){if(!(this instanceof Ee))return new Ee(e,t,n);if(we(0!==n,"Cannot step a Range by 0"),e=e||0,void 0===t&&(t=1/0),n=void 0===n?1:Math.abs(n),tr?z():U(e,o,n[t?r-o++:o++])}))},t(ne,K),ne.prototype.get=function(e,t){return void 0===t||this.has(e)?this._object[e]:t},ne.prototype.has=function(e){return this._object.hasOwnProperty(e)},ne.prototype.__iterate=function(e,t){for(var n=this._object,r=this._keys,o=r.length-1,a=0;a<=o;a++){var i=r[t?o-a:a];if(!1===e(n[i],i,this))return a+1}return a},ne.prototype.__iterator=function(e,t){var n=this._object,r=this._keys,o=r.length-1,a=0;return new F((function(){var i=r[t?o-a:a];return a++>o?z():U(e,i,n[i])}))},ne.prototype[d]=!0,t(re,G),re.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);var n=V(this._iterable),r=0;if($(n))for(var o;!(o=n.next()).done&&!1!==e(o.value,r++,this););return r},re.prototype.__iteratorUncached=function(e,t){if(t)return this.cacheResult().__iterator(e,t);var n=V(this._iterable);if(!$(n))return new F(z);var r=0;return new F((function(){var t=n.next();return t.done?t:U(e,r++,t.value)}))},t(oe,G),oe.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);for(var n,r=this._iterator,o=this._iteratorCache,a=0;a=r.length){var t=n.next();if(t.done)return t;r[o]=t.value}return U(e,o,r[o++])}))},t(be,G),be.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},be.prototype.get=function(e,t){return this.has(e)?this._value:t},be.prototype.includes=function(e){return ye(this._value,e)},be.prototype.slice=function(e,t){var n=this.size;return j(e,t,n)?this:new be(this._value,T(t,n)-I(e,n))},be.prototype.reverse=function(){return this},be.prototype.indexOf=function(e){return ye(this._value,e)?0:-1},be.prototype.lastIndexOf=function(e){return ye(this._value,e)?this.size:-1},be.prototype.__iterate=function(e,t){for(var n=0;n=0&&t=0&&nn?z():U(e,a++,i)}))},Ee.prototype.equals=function(e){return e instanceof Ee?this._start===e._start&&this._end===e._end&&this._step===e._step:ve(this,e)},t(xe,n),t(_e,xe),t(Se,xe),t(Ae,xe),xe.Keyed=_e,xe.Indexed=Se,xe.Set=Ae;var Ce="function"==typeof Math.imul&&-2===Math.imul(4294967295,2)?Math.imul:function(e,t){var n=65535&(e|=0),r=65535&(t|=0);return n*r+((e>>>16)*r+n*(t>>>16)<<16>>>0)|0};function ke(e){return e>>>1&1073741824|3221225471&e}function Oe(e){if(!1===e||null==e)return 0;if("function"==typeof e.valueOf&&(!1===(e=e.valueOf())||null==e))return 0;if(!0===e)return 1;var t=typeof e;if("number"===t){if(e!=e||e===1/0)return 0;var n=0|e;for(n!==e&&(n^=4294967295*e);e>4294967295;)n^=e/=4294967295;return ke(n)}if("string"===t)return e.length>Fe?je(e):Ie(e);if("function"==typeof e.hashCode)return e.hashCode();if("object"===t)return Te(e);if("function"==typeof e.toString)return Ie(e.toString());throw new Error("Value type "+t+" cannot be hashed.")}function je(e){var t=qe[e];return void 0===t&&(t=Ie(e),ze===Ue&&(ze=0,qe={}),ze++,qe[e]=t),t}function Ie(e){for(var t=0,n=0;n0)switch(e.nodeType){case 1:return e.uniqueID;case 9:return e.documentElement&&e.documentElement.uniqueID}}var Me,De="function"==typeof WeakMap;De&&(Me=new WeakMap);var Le=0,Be="__immutablehash__";"function"==typeof Symbol&&(Be=Symbol(Be));var Fe=16,Ue=255,ze=0,qe={};function $e(e){we(e!==1/0,"Cannot perform this action with an infinite size.")}function Ve(e){return null==e?ot():We(e)&&!c(e)?e:ot().withMutations((function(t){var n=r(e);$e(n.size),n.forEach((function(e,n){return t.set(n,e)}))}))}function We(e){return!(!e||!e[Je])}t(Ve,_e),Ve.of=function(){var t=e.call(arguments,0);return ot().withMutations((function(e){for(var n=0;n=t.length)throw new Error("Missing value for key: "+t[n]);e.set(t[n],t[n+1])}}))},Ve.prototype.toString=function(){return this.__toString("Map {","}")},Ve.prototype.get=function(e,t){return this._root?this._root.get(0,void 0,e,t):t},Ve.prototype.set=function(e,t){return at(this,e,t)},Ve.prototype.setIn=function(e,t){return this.updateIn(e,b,(function(){return t}))},Ve.prototype.remove=function(e){return at(this,e,b)},Ve.prototype.deleteIn=function(e){return this.updateIn(e,(function(){return b}))},Ve.prototype.update=function(e,t,n){return 1===arguments.length?e(this):this.updateIn([e],t,n)},Ve.prototype.updateIn=function(e,t,n){n||(n=t,t=void 0);var r=gt(this,xn(e),t,n);return r===b?void 0:r},Ve.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):ot()},Ve.prototype.merge=function(){return ft(this,void 0,arguments)},Ve.prototype.mergeWith=function(t){return ft(this,t,e.call(arguments,1))},Ve.prototype.mergeIn=function(t){var n=e.call(arguments,1);return this.updateIn(t,ot(),(function(e){return"function"==typeof e.merge?e.merge.apply(e,n):n[n.length-1]}))},Ve.prototype.mergeDeep=function(){return ft(this,ht,arguments)},Ve.prototype.mergeDeepWith=function(t){var n=e.call(arguments,1);return ft(this,dt(t),n)},Ve.prototype.mergeDeepIn=function(t){var n=e.call(arguments,1);return this.updateIn(t,ot(),(function(e){return"function"==typeof e.mergeDeep?e.mergeDeep.apply(e,n):n[n.length-1]}))},Ve.prototype.sort=function(e){return qt(pn(this,e))},Ve.prototype.sortBy=function(e,t){return qt(pn(this,t,e))},Ve.prototype.withMutations=function(e){var t=this.asMutable();return e(t),t.wasAltered()?t.__ensureOwner(this.__ownerID):this},Ve.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new S)},Ve.prototype.asImmutable=function(){return this.__ensureOwner()},Ve.prototype.wasAltered=function(){return this.__altered},Ve.prototype.__iterator=function(e,t){return new et(this,e,t)},Ve.prototype.__iterate=function(e,t){var n=this,r=0;return this._root&&this._root.iterate((function(t){return r++,e(t[1],t[0],n)}),t),r},Ve.prototype.__ensureOwner=function(e){return e===this.__ownerID?this:e?rt(this.size,this._root,e,this.__hash):(this.__ownerID=e,this.__altered=!1,this)},Ve.isMap=We;var He,Je="@@__IMMUTABLE_MAP__@@",Ke=Ve.prototype;function Ge(e,t){this.ownerID=e,this.entries=t}function Ze(e,t,n){this.ownerID=e,this.bitmap=t,this.nodes=n}function Ye(e,t,n){this.ownerID=e,this.count=t,this.nodes=n}function Qe(e,t,n){this.ownerID=e,this.keyHash=t,this.entries=n}function Xe(e,t,n){this.ownerID=e,this.keyHash=t,this.entry=n}function et(e,t,n){this._type=t,this._reverse=n,this._stack=e._root&&nt(e._root)}function tt(e,t){return U(e,t[0],t[1])}function nt(e,t){return{node:e,index:0,__prev:t}}function rt(e,t,n,r){var o=Object.create(Ke);return o.size=e,o._root=t,o.__ownerID=n,o.__hash=r,o.__altered=!1,o}function ot(){return He||(He=rt(0))}function at(e,t,n){var r,o;if(e._root){var a=x(w),i=x(E);if(r=it(e._root,e.__ownerID,0,void 0,t,n,a,i),!i.value)return e;o=e.size+(a.value?n===b?-1:1:0)}else{if(n===b)return e;o=1,r=new Ge(e.__ownerID,[[t,n]])}return e.__ownerID?(e.size=o,e._root=r,e.__hash=void 0,e.__altered=!0,e):r?rt(o,r):ot()}function it(e,t,n,r,o,a,i,s){return e?e.update(t,n,r,o,a,i,s):a===b?e:(_(s),_(i),new Xe(t,r,[o,a]))}function st(e){return e.constructor===Xe||e.constructor===Qe}function lt(e,t,n,r,o){if(e.keyHash===r)return new Qe(t,r,[e.entry,o]);var a,i=(0===n?e.keyHash:e.keyHash>>>n)&v,s=(0===n?r:r>>>n)&v;return new Ze(t,1<>>=1)i[s]=1&n?t[a++]:void 0;return i[r]=o,new Ye(e,a+1,i)}function ft(e,t,n){for(var o=[],a=0;a>1&1431655765))+(e>>2&858993459))+(e>>4)&252645135,e+=e>>8,127&(e+=e>>16)}function vt(e,t,n,r){var o=r?e:A(e);return o[t]=n,o}function bt(e,t,n,r){var o=e.length+1;if(r&&t+1===o)return e[t]=n,e;for(var a=new Array(o),i=0,s=0;s=Et)return ut(e,l,r,o);var f=e&&e===this.ownerID,h=f?l:A(l);return p?s?u===c-1?h.pop():h[u]=h.pop():h[u]=[r,o]:h.push([r,o]),f?(this.entries=h,this):new Ge(e,h)}},Ze.prototype.get=function(e,t,n,r){void 0===t&&(t=Oe(n));var o=1<<((0===e?t:t>>>e)&v),a=this.bitmap;return 0==(a&o)?r:this.nodes[yt(a&o-1)].get(e+g,t,n,r)},Ze.prototype.update=function(e,t,n,r,o,a,i){void 0===n&&(n=Oe(r));var s=(0===t?n:n>>>t)&v,l=1<=xt)return pt(e,f,u,s,d);if(c&&!d&&2===f.length&&st(f[1^p]))return f[1^p];if(c&&d&&1===f.length&&st(d))return d;var m=e&&e===this.ownerID,y=c?d?u:u^l:u|l,w=c?d?vt(f,p,d,m):wt(f,p,m):bt(f,p,d,m);return m?(this.bitmap=y,this.nodes=w,this):new Ze(e,y,w)},Ye.prototype.get=function(e,t,n,r){void 0===t&&(t=Oe(n));var o=(0===e?t:t>>>e)&v,a=this.nodes[o];return a?a.get(e+g,t,n,r):r},Ye.prototype.update=function(e,t,n,r,o,a,i){void 0===n&&(n=Oe(r));var s=(0===t?n:n>>>t)&v,l=o===b,u=this.nodes,c=u[s];if(l&&!c)return this;var p=it(c,e,t+g,n,r,o,a,i);if(p===c)return this;var f=this.count;if(c){if(!p&&--f<_t)return ct(e,u,f,s)}else f++;var h=e&&e===this.ownerID,d=vt(u,s,p,h);return h?(this.count=f,this.nodes=d,this):new Ye(e,f,d)},Qe.prototype.get=function(e,t,n,r){for(var o=this.entries,a=0,i=o.length;a0&&r=0&&e>>t&v;if(r>=this.array.length)return new Ot([],e);var o,a=0===r;if(t>0){var i=this.array[r];if((o=i&&i.removeBefore(e,t-g,n))===i&&a)return this}if(a&&!o)return this;var s=Lt(this,e);if(!a)for(var l=0;l>>t&v;if(o>=this.array.length)return this;if(t>0){var a=this.array[o];if((r=a&&a.removeAfter(e,t-g,n))===a&&o===this.array.length-1)return this}var i=Lt(this,e);return i.array.splice(o+1),r&&(i.array[o]=r),i};var jt,It,Tt={};function Nt(e,t){var n=e._origin,r=e._capacity,o=zt(r),a=e._tail;return i(e._root,e._level,0);function i(e,t,n){return 0===t?s(e,n):l(e,t,n)}function s(e,i){var s=i===o?a&&a.array:e&&e.array,l=i>n?0:n-i,u=r-i;return u>y&&(u=y),function(){if(l===u)return Tt;var e=t?--u:l++;return s&&s[e]}}function l(e,o,a){var s,l=e&&e.array,u=a>n?0:n-a>>o,c=1+(r-a>>o);return c>y&&(c=y),function(){for(;;){if(s){var e=s();if(e!==Tt)return e;s=null}if(u===c)return Tt;var n=t?--c:u++;s=i(l&&l[n],o-g,a+(n<=e.size||t<0)return e.withMutations((function(e){t<0?Ft(e,t).set(0,n):Ft(e,0,t+1).set(t,n)}));t+=e._origin;var r=e._tail,o=e._root,a=x(E);return t>=zt(e._capacity)?r=Dt(r,e.__ownerID,0,t,n,a):o=Dt(o,e.__ownerID,e._level,t,n,a),a.value?e.__ownerID?(e._root=o,e._tail=r,e.__hash=void 0,e.__altered=!0,e):Pt(e._origin,e._capacity,e._level,o,r):e}function Dt(e,t,n,r,o,a){var i,s=r>>>n&v,l=e&&s0){var u=e&&e.array[s],c=Dt(u,t,n-g,r,o,a);return c===u?e:((i=Lt(e,t)).array[s]=c,i)}return l&&e.array[s]===o?e:(_(a),i=Lt(e,t),void 0===o&&s===i.array.length-1?i.array.pop():i.array[s]=o,i)}function Lt(e,t){return t&&e&&t===e.ownerID?e:new Ot(e?e.array.slice():[],t)}function Bt(e,t){if(t>=zt(e._capacity))return e._tail;if(t<1<0;)n=n.array[t>>>r&v],r-=g;return n}}function Ft(e,t,n){void 0!==t&&(t|=0),void 0!==n&&(n|=0);var r=e.__ownerID||new S,o=e._origin,a=e._capacity,i=o+t,s=void 0===n?a:n<0?a+n:o+n;if(i===o&&s===a)return e;if(i>=s)return e.clear();for(var l=e._level,u=e._root,c=0;i+c<0;)u=new Ot(u&&u.array.length?[void 0,u]:[],r),c+=1<<(l+=g);c&&(i+=c,o+=c,s+=c,a+=c);for(var p=zt(a),f=zt(s);f>=1<p?new Ot([],r):h;if(h&&f>p&&ig;y-=g){var b=p>>>y&v;m=m.array[b]=Lt(m.array[b],r)}m.array[p>>>g&v]=h}if(s=f)i-=f,s-=f,l=g,u=null,d=d&&d.removeBefore(r,0,i);else if(i>o||f>>l&v;if(w!==f>>>l&v)break;w&&(c+=(1<o&&(u=u.removeBefore(r,l,i-c)),u&&fa&&(a=u.size),i(l)||(u=u.map((function(e){return he(e)}))),r.push(u)}return a>e.size&&(e=e.setSize(a)),mt(e,t,r)}function zt(e){return e>>g<=y&&i.size>=2*a.size?(r=(o=i.filter((function(e,t){return void 0!==e&&s!==t}))).toKeyedSeq().map((function(e){return e[0]})).flip().toMap(),e.__ownerID&&(r.__ownerID=o.__ownerID=e.__ownerID)):(r=a.remove(t),o=s===i.size-1?i.pop():i.set(s,void 0))}else if(l){if(n===i.get(s)[1])return e;r=a,o=i.set(s,[t,n])}else r=a.set(t,i.size),o=i.set(i.size,[t,n]);return e.__ownerID?(e.size=r.size,e._map=r,e._list=o,e.__hash=void 0,e):Vt(r,o)}function Jt(e,t){this._iter=e,this._useKeys=t,this.size=e.size}function Kt(e){this._iter=e,this.size=e.size}function Gt(e){this._iter=e,this.size=e.size}function Zt(e){this._iter=e,this.size=e.size}function Yt(e){var t=bn(e);return t._iter=e,t.size=e.size,t.flip=function(){return e},t.reverse=function(){var t=e.reverse.apply(this);return t.flip=function(){return e.reverse()},t},t.has=function(t){return e.includes(t)},t.includes=function(t){return e.has(t)},t.cacheResult=wn,t.__iterateUncached=function(t,n){var r=this;return e.__iterate((function(e,n){return!1!==t(n,e,r)}),n)},t.__iteratorUncached=function(t,n){if(t===M){var r=e.__iterator(t,n);return new F((function(){var e=r.next();if(!e.done){var t=e.value[0];e.value[0]=e.value[1],e.value[1]=t}return e}))}return e.__iterator(t===R?P:R,n)},t}function Qt(e,t,n){var r=bn(e);return r.size=e.size,r.has=function(t){return e.has(t)},r.get=function(r,o){var a=e.get(r,b);return a===b?o:t.call(n,a,r,e)},r.__iterateUncached=function(r,o){var a=this;return e.__iterate((function(e,o,i){return!1!==r(t.call(n,e,o,i),o,a)}),o)},r.__iteratorUncached=function(r,o){var a=e.__iterator(M,o);return new F((function(){var o=a.next();if(o.done)return o;var i=o.value,s=i[0];return U(r,s,t.call(n,i[1],s,e),o)}))},r}function Xt(e,t){var n=bn(e);return n._iter=e,n.size=e.size,n.reverse=function(){return e},e.flip&&(n.flip=function(){var t=Yt(e);return t.reverse=function(){return e.flip()},t}),n.get=function(n,r){return e.get(t?n:-1-n,r)},n.has=function(n){return e.has(t?n:-1-n)},n.includes=function(t){return e.includes(t)},n.cacheResult=wn,n.__iterate=function(t,n){var r=this;return e.__iterate((function(e,n){return t(e,n,r)}),!n)},n.__iterator=function(t,n){return e.__iterator(t,!n)},n}function en(e,t,n,r){var o=bn(e);return r&&(o.has=function(r){var o=e.get(r,b);return o!==b&&!!t.call(n,o,r,e)},o.get=function(r,o){var a=e.get(r,b);return a!==b&&t.call(n,a,r,e)?a:o}),o.__iterateUncached=function(o,a){var i=this,s=0;return e.__iterate((function(e,a,l){if(t.call(n,e,a,l))return s++,o(e,r?a:s-1,i)}),a),s},o.__iteratorUncached=function(o,a){var i=e.__iterator(M,a),s=0;return new F((function(){for(;;){var a=i.next();if(a.done)return a;var l=a.value,u=l[0],c=l[1];if(t.call(n,c,u,e))return U(o,r?u:s++,c,a)}}))},o}function tn(e,t,n){var r=Ve().asMutable();return e.__iterate((function(o,a){r.update(t.call(n,o,a,e),0,(function(e){return e+1}))})),r.asImmutable()}function nn(e,t,n){var r=s(e),o=(c(e)?qt():Ve()).asMutable();e.__iterate((function(a,i){o.update(t.call(n,a,i,e),(function(e){return(e=e||[]).push(r?[i,a]:a),e}))}));var a=vn(e);return o.map((function(t){return mn(e,a(t))}))}function rn(e,t,n,r){var o=e.size;if(void 0!==t&&(t|=0),void 0!==n&&(n===1/0?n=o:n|=0),j(t,n,o))return e;var a=I(t,o),i=T(n,o);if(a!=a||i!=i)return rn(e.toSeq().cacheResult(),t,n,r);var s,l=i-a;l==l&&(s=l<0?0:l);var u=bn(e);return u.size=0===s?s:e.size&&s||void 0,!r&&ae(e)&&s>=0&&(u.get=function(t,n){return(t=k(this,t))>=0&&ts)return z();var e=o.next();return r||t===R?e:U(t,l-1,t===P?void 0:e.value[1],e)}))},u}function on(e,t,n){var r=bn(e);return r.__iterateUncached=function(r,o){var a=this;if(o)return this.cacheResult().__iterate(r,o);var i=0;return e.__iterate((function(e,o,s){return t.call(n,e,o,s)&&++i&&r(e,o,a)})),i},r.__iteratorUncached=function(r,o){var a=this;if(o)return this.cacheResult().__iterator(r,o);var i=e.__iterator(M,o),s=!0;return new F((function(){if(!s)return z();var e=i.next();if(e.done)return e;var o=e.value,l=o[0],u=o[1];return t.call(n,u,l,a)?r===M?e:U(r,l,u,e):(s=!1,z())}))},r}function an(e,t,n,r){var o=bn(e);return o.__iterateUncached=function(o,a){var i=this;if(a)return this.cacheResult().__iterate(o,a);var s=!0,l=0;return e.__iterate((function(e,a,u){if(!s||!(s=t.call(n,e,a,u)))return l++,o(e,r?a:l-1,i)})),l},o.__iteratorUncached=function(o,a){var i=this;if(a)return this.cacheResult().__iterator(o,a);var s=e.__iterator(M,a),l=!0,u=0;return new F((function(){var e,a,c;do{if((e=s.next()).done)return r||o===R?e:U(o,u++,o===P?void 0:e.value[1],e);var p=e.value;a=p[0],c=p[1],l&&(l=t.call(n,c,a,i))}while(l);return o===M?e:U(o,a,c,e)}))},o}function sn(e,t){var n=s(e),o=[e].concat(t).map((function(e){return i(e)?n&&(e=r(e)):e=n?se(e):le(Array.isArray(e)?e:[e]),e})).filter((function(e){return 0!==e.size}));if(0===o.length)return e;if(1===o.length){var a=o[0];if(a===e||n&&s(a)||l(e)&&l(a))return a}var u=new te(o);return n?u=u.toKeyedSeq():l(e)||(u=u.toSetSeq()),(u=u.flatten(!0)).size=o.reduce((function(e,t){if(void 0!==e){var n=t.size;if(void 0!==n)return e+n}}),0),u}function ln(e,t,n){var r=bn(e);return r.__iterateUncached=function(r,o){var a=0,s=!1;function l(e,u){var c=this;e.__iterate((function(e,o){return(!t||u0}function dn(e,t,r){var o=bn(e);return o.size=new te(r).map((function(e){return e.size})).min(),o.__iterate=function(e,t){for(var n,r=this.__iterator(R,t),o=0;!(n=r.next()).done&&!1!==e(n.value,o++,this););return o},o.__iteratorUncached=function(e,o){var a=r.map((function(e){return e=n(e),V(o?e.reverse():e)})),i=0,s=!1;return new F((function(){var n;return s||(n=a.map((function(e){return e.next()})),s=n.some((function(e){return e.done}))),s?z():U(e,i++,t.apply(null,n.map((function(e){return e.value}))))}))},o}function mn(e,t){return ae(e)?t:e.constructor(t)}function gn(e){if(e!==Object(e))throw new TypeError("Expected [K, V] tuple: "+e)}function yn(e){return $e(e.size),C(e)}function vn(e){return s(e)?r:l(e)?o:a}function bn(e){return Object.create((s(e)?K:l(e)?G:Z).prototype)}function wn(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):J.prototype.cacheResult.call(this)}function En(e,t){return e>t?1:e=0;n--)t={value:arguments[n],next:t};return this.__ownerID?(this.size=e,this._head=t,this.__hash=void 0,this.__altered=!0,this):Kn(e,t)},$n.prototype.pushAll=function(e){if(0===(e=o(e)).size)return this;$e(e.size);var t=this.size,n=this._head;return e.reverse().forEach((function(e){t++,n={value:e,next:n}})),this.__ownerID?(this.size=t,this._head=n,this.__hash=void 0,this.__altered=!0,this):Kn(t,n)},$n.prototype.pop=function(){return this.slice(1)},$n.prototype.unshift=function(){return this.push.apply(this,arguments)},$n.prototype.unshiftAll=function(e){return this.pushAll(e)},$n.prototype.shift=function(){return this.pop.apply(this,arguments)},$n.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):Gn()},$n.prototype.slice=function(e,t){if(j(e,t,this.size))return this;var n=I(e,this.size);if(T(t,this.size)!==this.size)return Se.prototype.slice.call(this,e,t);for(var r=this.size-n,o=this._head;n--;)o=o.next;return this.__ownerID?(this.size=r,this._head=o,this.__hash=void 0,this.__altered=!0,this):Kn(r,o)},$n.prototype.__ensureOwner=function(e){return e===this.__ownerID?this:e?Kn(this.size,this._head,e,this.__hash):(this.__ownerID=e,this.__altered=!1,this)},$n.prototype.__iterate=function(e,t){if(t)return this.reverse().__iterate(e);for(var n=0,r=this._head;r&&!1!==e(r.value,n++,this);)r=r.next;return n},$n.prototype.__iterator=function(e,t){if(t)return this.reverse().__iterator(e);var n=0,r=this._head;return new F((function(){if(r){var t=r.value;return r=r.next,U(e,n++,t)}return z()}))},$n.isStack=Vn;var Wn,Hn="@@__IMMUTABLE_STACK__@@",Jn=$n.prototype;function Kn(e,t,n,r){var o=Object.create(Jn);return o.size=e,o._head=t,o.__ownerID=n,o.__hash=r,o.__altered=!1,o}function Gn(){return Wn||(Wn=Kn(0))}function Zn(e,t){var n=function(n){e.prototype[n]=t[n]};return Object.keys(t).forEach(n),Object.getOwnPropertySymbols&&Object.getOwnPropertySymbols(t).forEach(n),e}Jn[Hn]=!0,Jn.withMutations=Ke.withMutations,Jn.asMutable=Ke.asMutable,Jn.asImmutable=Ke.asImmutable,Jn.wasAltered=Ke.wasAltered,n.Iterator=F,Zn(n,{toArray:function(){$e(this.size);var e=new Array(this.size||0);return this.valueSeq().__iterate((function(t,n){e[n]=t})),e},toIndexedSeq:function(){return new Kt(this)},toJS:function(){return this.toSeq().map((function(e){return e&&"function"==typeof e.toJS?e.toJS():e})).__toJS()},toJSON:function(){return this.toSeq().map((function(e){return e&&"function"==typeof e.toJSON?e.toJSON():e})).__toJS()},toKeyedSeq:function(){return new Jt(this,!0)},toMap:function(){return Ve(this.toKeyedSeq())},toObject:function(){$e(this.size);var e={};return this.__iterate((function(t,n){e[n]=t})),e},toOrderedMap:function(){return qt(this.toKeyedSeq())},toOrderedSet:function(){return Ln(s(this)?this.valueSeq():this)},toSet:function(){return jn(s(this)?this.valueSeq():this)},toSetSeq:function(){return new Gt(this)},toSeq:function(){return l(this)?this.toIndexedSeq():s(this)?this.toKeyedSeq():this.toSetSeq()},toStack:function(){return $n(s(this)?this.valueSeq():this)},toList:function(){return St(s(this)?this.valueSeq():this)},toString:function(){return"[Iterable]"},__toString:function(e,t){return 0===this.size?e+t:e+" "+this.toSeq().map(this.__toStringMapper).join(", ")+" "+t},concat:function(){return mn(this,sn(this,e.call(arguments,0)))},includes:function(e){return this.some((function(t){return ye(t,e)}))},entries:function(){return this.__iterator(M)},every:function(e,t){$e(this.size);var n=!0;return this.__iterate((function(r,o,a){if(!e.call(t,r,o,a))return n=!1,!1})),n},filter:function(e,t){return mn(this,en(this,e,t,!0))},find:function(e,t,n){var r=this.findEntry(e,t);return r?r[1]:n},forEach:function(e,t){return $e(this.size),this.__iterate(t?e.bind(t):e)},join:function(e){$e(this.size),e=void 0!==e?""+e:",";var t="",n=!0;return this.__iterate((function(r){n?n=!1:t+=e,t+=null!=r?r.toString():""})),t},keys:function(){return this.__iterator(P)},map:function(e,t){return mn(this,Qt(this,e,t))},reduce:function(e,t,n){var r,o;return $e(this.size),arguments.length<2?o=!0:r=t,this.__iterate((function(t,a,i){o?(o=!1,r=t):r=e.call(n,r,t,a,i)})),r},reduceRight:function(e,t,n){var r=this.toKeyedSeq().reverse();return r.reduce.apply(r,arguments)},reverse:function(){return mn(this,Xt(this,!0))},slice:function(e,t){return mn(this,rn(this,e,t,!0))},some:function(e,t){return!this.every(tr(e),t)},sort:function(e){return mn(this,pn(this,e))},values:function(){return this.__iterator(R)},butLast:function(){return this.slice(0,-1)},isEmpty:function(){return void 0!==this.size?0===this.size:!this.some((function(){return!0}))},count:function(e,t){return C(e?this.toSeq().filter(e,t):this)},countBy:function(e,t){return tn(this,e,t)},equals:function(e){return ve(this,e)},entrySeq:function(){var e=this;if(e._cache)return new te(e._cache);var t=e.toSeq().map(er).toIndexedSeq();return t.fromEntrySeq=function(){return e.toSeq()},t},filterNot:function(e,t){return this.filter(tr(e),t)},findEntry:function(e,t,n){var r=n;return this.__iterate((function(n,o,a){if(e.call(t,n,o,a))return r=[o,n],!1})),r},findKey:function(e,t){var n=this.findEntry(e,t);return n&&n[0]},findLast:function(e,t,n){return this.toKeyedSeq().reverse().find(e,t,n)},findLastEntry:function(e,t,n){return this.toKeyedSeq().reverse().findEntry(e,t,n)},findLastKey:function(e,t){return this.toKeyedSeq().reverse().findKey(e,t)},first:function(){return this.find(O)},flatMap:function(e,t){return mn(this,un(this,e,t))},flatten:function(e){return mn(this,ln(this,e,!0))},fromEntrySeq:function(){return new Zt(this)},get:function(e,t){return this.find((function(t,n){return ye(n,e)}),void 0,t)},getIn:function(e,t){for(var n,r=this,o=xn(e);!(n=o.next()).done;){var a=n.value;if((r=r&&r.get?r.get(a,b):b)===b)return t}return r},groupBy:function(e,t){return nn(this,e,t)},has:function(e){return this.get(e,b)!==b},hasIn:function(e){return this.getIn(e,b)!==b},isSubset:function(e){return e="function"==typeof e.includes?e:n(e),this.every((function(t){return e.includes(t)}))},isSuperset:function(e){return(e="function"==typeof e.isSubset?e:n(e)).isSubset(this)},keyOf:function(e){return this.findKey((function(t){return ye(t,e)}))},keySeq:function(){return this.toSeq().map(Xn).toIndexedSeq()},last:function(){return this.toSeq().reverse().first()},lastKeyOf:function(e){return this.toKeyedSeq().reverse().keyOf(e)},max:function(e){return fn(this,e)},maxBy:function(e,t){return fn(this,t,e)},min:function(e){return fn(this,e?nr(e):ar)},minBy:function(e,t){return fn(this,t?nr(t):ar,e)},rest:function(){return this.slice(1)},skip:function(e){return this.slice(Math.max(0,e))},skipLast:function(e){return mn(this,this.toSeq().reverse().skip(e).reverse())},skipWhile:function(e,t){return mn(this,an(this,e,t,!0))},skipUntil:function(e,t){return this.skipWhile(tr(e),t)},sortBy:function(e,t){return mn(this,pn(this,t,e))},take:function(e){return this.slice(0,Math.max(0,e))},takeLast:function(e){return mn(this,this.toSeq().reverse().take(e).reverse())},takeWhile:function(e,t){return mn(this,on(this,e,t))},takeUntil:function(e,t){return this.takeWhile(tr(e),t)},valueSeq:function(){return this.toIndexedSeq()},hashCode:function(){return this.__hash||(this.__hash=ir(this))}});var Yn=n.prototype;Yn[p]=!0,Yn[B]=Yn.values,Yn.__toJS=Yn.toArray,Yn.__toStringMapper=rr,Yn.inspect=Yn.toSource=function(){return this.toString()},Yn.chain=Yn.flatMap,Yn.contains=Yn.includes,Zn(r,{flip:function(){return mn(this,Yt(this))},mapEntries:function(e,t){var n=this,r=0;return mn(this,this.toSeq().map((function(o,a){return e.call(t,[a,o],r++,n)})).fromEntrySeq())},mapKeys:function(e,t){var n=this;return mn(this,this.toSeq().flip().map((function(r,o){return e.call(t,r,o,n)})).flip())}});var Qn=r.prototype;function Xn(e,t){return t}function er(e,t){return[t,e]}function tr(e){return function(){return!e.apply(this,arguments)}}function nr(e){return function(){return-e.apply(this,arguments)}}function rr(e){return"string"==typeof e?JSON.stringify(e):String(e)}function or(){return A(arguments)}function ar(e,t){return et?-1:0}function ir(e){if(e.size===1/0)return 0;var t=c(e),n=s(e),r=t?1:0;return sr(e.__iterate(n?t?function(e,t){r=31*r+lr(Oe(e),Oe(t))|0}:function(e,t){r=r+lr(Oe(e),Oe(t))|0}:t?function(e){r=31*r+Oe(e)|0}:function(e){r=r+Oe(e)|0}),r)}function sr(e,t){return t=Ce(t,3432918353),t=Ce(t<<15|t>>>-15,461845907),t=Ce(t<<13|t>>>-13,5),t=Ce((t=(t+3864292196|0)^e)^t>>>16,2246822507),t=ke((t=Ce(t^t>>>13,3266489909))^t>>>16)}function lr(e,t){return e^t+2654435769+(e<<6)+(e>>2)|0}return Qn[f]=!0,Qn[B]=Yn.entries,Qn.__toJS=Yn.toObject,Qn.__toStringMapper=function(e,t){return JSON.stringify(t)+": "+rr(e)},Zn(o,{toKeyedSeq:function(){return new Jt(this,!1)},filter:function(e,t){return mn(this,en(this,e,t,!1))},findIndex:function(e,t){var n=this.findEntry(e,t);return n?n[0]:-1},indexOf:function(e){var t=this.keyOf(e);return void 0===t?-1:t},lastIndexOf:function(e){var t=this.lastKeyOf(e);return void 0===t?-1:t},reverse:function(){return mn(this,Xt(this,!1))},slice:function(e,t){return mn(this,rn(this,e,t,!1))},splice:function(e,t){var n=arguments.length;if(t=Math.max(0|t,0),0===n||2===n&&!t)return this;e=I(e,e<0?this.count():this.size);var r=this.slice(0,e);return mn(this,1===n?r:r.concat(A(arguments,2),this.slice(e+t)))},findLastIndex:function(e,t){var n=this.findLastEntry(e,t);return n?n[0]:-1},first:function(){return this.get(0)},flatten:function(e){return mn(this,ln(this,e,!1))},get:function(e,t){return(e=k(this,e))<0||this.size===1/0||void 0!==this.size&&e>this.size?t:this.find((function(t,n){return n===e}),void 0,t)},has:function(e){return(e=k(this,e))>=0&&(void 0!==this.size?this.size===1/0||e{"function"==typeof Object.create?e.exports=function(e,t){t&&(e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}))}:e.exports=function(e,t){if(t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}}},35823:e=>{e.exports=function(e,t,n,r){var o=new Blob(void 0!==r?[r,e]:[e],{type:n||"application/octet-stream"});if(void 0!==window.navigator.msSaveBlob)window.navigator.msSaveBlob(o,t);else{var a=window.URL&&window.URL.createObjectURL?window.URL.createObjectURL(o):window.webkitURL.createObjectURL(o),i=document.createElement("a");i.style.display="none",i.href=a,i.setAttribute("download",t),void 0===i.download&&i.setAttribute("target","_blank"),document.body.appendChild(i),i.click(),setTimeout((function(){document.body.removeChild(i),window.URL.revokeObjectURL(a)}),200)}}},91296:(e,t,n)=>{var r="Expected a function",o=NaN,a="[object Symbol]",i=/^\s+|\s+$/g,s=/^[-+]0x[0-9a-f]+$/i,l=/^0b[01]+$/i,u=/^0o[0-7]+$/i,c=parseInt,p="object"==typeof n.g&&n.g&&n.g.Object===Object&&n.g,f="object"==typeof self&&self&&self.Object===Object&&self,h=p||f||Function("return this")(),d=Object.prototype.toString,m=Math.max,g=Math.min,y=function(){return h.Date.now()};function v(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function b(e){if("number"==typeof e)return e;if(function(e){return"symbol"==typeof e||function(e){return!!e&&"object"==typeof e}(e)&&d.call(e)==a}(e))return o;if(v(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=v(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(i,"");var n=l.test(e);return n||u.test(e)?c(e.slice(2),n?2:8):s.test(e)?o:+e}e.exports=function(e,t,n){var o,a,i,s,l,u,c=0,p=!1,f=!1,h=!0;if("function"!=typeof e)throw new TypeError(r);function d(t){var n=o,r=a;return o=a=void 0,c=t,s=e.apply(r,n)}function w(e){var n=e-u;return void 0===u||n>=t||n<0||f&&e-c>=i}function E(){var e=y();if(w(e))return x(e);l=setTimeout(E,function(e){var n=t-(e-u);return f?g(n,i-(e-c)):n}(e))}function x(e){return l=void 0,h&&o?d(e):(o=a=void 0,s)}function _(){var e=y(),n=w(e);if(o=arguments,a=this,u=e,n){if(void 0===l)return function(e){return c=e,l=setTimeout(E,t),p?d(e):s}(u);if(f)return l=setTimeout(E,t),d(u)}return void 0===l&&(l=setTimeout(E,t)),s}return t=b(t)||0,v(n)&&(p=!!n.leading,i=(f="maxWait"in n)?m(b(n.maxWait)||0,t):i,h="trailing"in n?!!n.trailing:h),_.cancel=function(){void 0!==l&&clearTimeout(l),c=0,o=u=a=l=void 0},_.flush=function(){return void 0===l?s:x(y())},_}},18552:(e,t,n)=>{var r=n(10852)(n(55639),"DataView");e.exports=r},1989:(e,t,n)=>{var r=n(51789),o=n(80401),a=n(57667),i=n(21327),s=n(81866);function l(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t{var r=n(27040),o=n(14125),a=n(82117),i=n(67518),s=n(54705);function l(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t{var r=n(10852)(n(55639),"Map");e.exports=r},83369:(e,t,n)=>{var r=n(24785),o=n(11285),a=n(96e3),i=n(49916),s=n(95265);function l(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t{var r=n(10852)(n(55639),"Promise");e.exports=r},58525:(e,t,n)=>{var r=n(10852)(n(55639),"Set");e.exports=r},88668:(e,t,n)=>{var r=n(83369),o=n(90619),a=n(72385);function i(e){var t=-1,n=null==e?0:e.length;for(this.__data__=new r;++t{var r=n(38407),o=n(37465),a=n(63779),i=n(67599),s=n(44758),l=n(34309);function u(e){var t=this.__data__=new r(e);this.size=t.size}u.prototype.clear=o,u.prototype.delete=a,u.prototype.get=i,u.prototype.has=s,u.prototype.set=l,e.exports=u},62705:(e,t,n)=>{var r=n(55639).Symbol;e.exports=r},11149:(e,t,n)=>{var r=n(55639).Uint8Array;e.exports=r},70577:(e,t,n)=>{var r=n(10852)(n(55639),"WeakMap");e.exports=r},96874:e=>{e.exports=function(e,t,n){switch(n.length){case 0:return e.call(t);case 1:return e.call(t,n[0]);case 2:return e.call(t,n[0],n[1]);case 3:return e.call(t,n[0],n[1],n[2])}return e.apply(t,n)}},77412:e=>{e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length;++n{e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,o=0,a=[];++n{var r=n(22545),o=n(35694),a=n(1469),i=n(44144),s=n(65776),l=n(36719),u=Object.prototype.hasOwnProperty;e.exports=function(e,t){var n=a(e),c=!n&&o(e),p=!n&&!c&&i(e),f=!n&&!c&&!p&&l(e),h=n||c||p||f,d=h?r(e.length,String):[],m=d.length;for(var g in e)!t&&!u.call(e,g)||h&&("length"==g||p&&("offset"==g||"parent"==g)||f&&("buffer"==g||"byteLength"==g||"byteOffset"==g)||s(g,m))||d.push(g);return d}},29932:e=>{e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,o=Array(r);++n{e.exports=function(e,t){for(var n=-1,r=t.length,o=e.length;++n{e.exports=function(e,t,n,r){var o=-1,a=null==e?0:e.length;for(r&&a&&(n=e[++o]);++o{e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length;++n{e.exports=function(e){return e.split("")}},49029:e=>{var t=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;e.exports=function(e){return e.match(t)||[]}},86556:(e,t,n)=>{var r=n(89465),o=n(77813);e.exports=function(e,t,n){(void 0!==n&&!o(e[t],n)||void 0===n&&!(t in e))&&r(e,t,n)}},34865:(e,t,n)=>{var r=n(89465),o=n(77813),a=Object.prototype.hasOwnProperty;e.exports=function(e,t,n){var i=e[t];a.call(e,t)&&o(i,n)&&(void 0!==n||t in e)||r(e,t,n)}},18470:(e,t,n)=>{var r=n(77813);e.exports=function(e,t){for(var n=e.length;n--;)if(r(e[n][0],t))return n;return-1}},44037:(e,t,n)=>{var r=n(98363),o=n(3674);e.exports=function(e,t){return e&&r(t,o(t),e)}},63886:(e,t,n)=>{var r=n(98363),o=n(81704);e.exports=function(e,t){return e&&r(t,o(t),e)}},89465:(e,t,n)=>{var r=n(38777);e.exports=function(e,t,n){"__proto__"==t&&r?r(e,t,{configurable:!0,enumerable:!0,value:n,writable:!0}):e[t]=n}},85990:(e,t,n)=>{var r=n(46384),o=n(77412),a=n(34865),i=n(44037),s=n(63886),l=n(64626),u=n(278),c=n(18805),p=n(1911),f=n(58234),h=n(46904),d=n(98882),m=n(43824),g=n(29148),y=n(38517),v=n(1469),b=n(44144),w=n(56688),E=n(13218),x=n(72928),_=n(3674),S=n(81704),A=1,C=2,k=4,O="[object Arguments]",j="[object Function]",I="[object GeneratorFunction]",T="[object Object]",N={};N[O]=N["[object Array]"]=N["[object ArrayBuffer]"]=N["[object DataView]"]=N["[object Boolean]"]=N["[object Date]"]=N["[object Float32Array]"]=N["[object Float64Array]"]=N["[object Int8Array]"]=N["[object Int16Array]"]=N["[object Int32Array]"]=N["[object Map]"]=N["[object Number]"]=N[T]=N["[object RegExp]"]=N["[object Set]"]=N["[object String]"]=N["[object Symbol]"]=N["[object Uint8Array]"]=N["[object Uint8ClampedArray]"]=N["[object Uint16Array]"]=N["[object Uint32Array]"]=!0,N["[object Error]"]=N[j]=N["[object WeakMap]"]=!1,e.exports=function e(t,n,P,R,M,D){var L,B=n&A,F=n&C,U=n&k;if(P&&(L=M?P(t,R,M,D):P(t)),void 0!==L)return L;if(!E(t))return t;var z=v(t);if(z){if(L=m(t),!B)return u(t,L)}else{var q=d(t),$=q==j||q==I;if(b(t))return l(t,B);if(q==T||q==O||$&&!M){if(L=F||$?{}:y(t),!B)return F?p(t,s(L,t)):c(t,i(L,t))}else{if(!N[q])return M?t:{};L=g(t,q,B)}}D||(D=new r);var V=D.get(t);if(V)return V;D.set(t,L),x(t)?t.forEach((function(r){L.add(e(r,n,P,r,t,D))})):w(t)&&t.forEach((function(r,o){L.set(o,e(r,n,P,o,t,D))}));var W=z?void 0:(U?F?h:f:F?S:_)(t);return o(W||t,(function(r,o){W&&(r=t[o=r]),a(L,o,e(r,n,P,o,t,D))})),L}},3118:(e,t,n)=>{var r=n(13218),o=Object.create,a=function(){function e(){}return function(t){if(!r(t))return{};if(o)return o(t);e.prototype=t;var n=new e;return e.prototype=void 0,n}}();e.exports=a},89881:(e,t,n)=>{var r=n(47816),o=n(99291)(r);e.exports=o},41848:e=>{e.exports=function(e,t,n,r){for(var o=e.length,a=n+(r?1:-1);r?a--:++a{var r=n(62488),o=n(37285);e.exports=function e(t,n,a,i,s){var l=-1,u=t.length;for(a||(a=o),s||(s=[]);++l0&&a(c)?n>1?e(c,n-1,a,i,s):r(s,c):i||(s[s.length]=c)}return s}},28483:(e,t,n)=>{var r=n(25063)();e.exports=r},47816:(e,t,n)=>{var r=n(28483),o=n(3674);e.exports=function(e,t){return e&&r(e,t,o)}},97786:(e,t,n)=>{var r=n(71811),o=n(40327);e.exports=function(e,t){for(var n=0,a=(t=r(t,e)).length;null!=e&&n{var r=n(62488),o=n(1469);e.exports=function(e,t,n){var a=t(e);return o(e)?a:r(a,n(e))}},44239:(e,t,n)=>{var r=n(62705),o=n(89607),a=n(2333),i="[object Null]",s="[object Undefined]",l=r?r.toStringTag:void 0;e.exports=function(e){return null==e?void 0===e?s:i:l&&l in Object(e)?o(e):a(e)}},13:e=>{e.exports=function(e,t){return null!=e&&t in Object(e)}},9454:(e,t,n)=>{var r=n(44239),o=n(37005),a="[object Arguments]";e.exports=function(e){return o(e)&&r(e)==a}},90939:(e,t,n)=>{var r=n(2492),o=n(37005);e.exports=function e(t,n,a,i,s){return t===n||(null==t||null==n||!o(t)&&!o(n)?t!=t&&n!=n:r(t,n,a,i,e,s))}},2492:(e,t,n)=>{var r=n(46384),o=n(67114),a=n(18351),i=n(16096),s=n(98882),l=n(1469),u=n(44144),c=n(36719),p=1,f="[object Arguments]",h="[object Array]",d="[object Object]",m=Object.prototype.hasOwnProperty;e.exports=function(e,t,n,g,y,v){var b=l(e),w=l(t),E=b?h:s(e),x=w?h:s(t),_=(E=E==f?d:E)==d,S=(x=x==f?d:x)==d,A=E==x;if(A&&u(e)){if(!u(t))return!1;b=!0,_=!1}if(A&&!_)return v||(v=new r),b||c(e)?o(e,t,n,g,y,v):a(e,t,E,n,g,y,v);if(!(n&p)){var C=_&&m.call(e,"__wrapped__"),k=S&&m.call(t,"__wrapped__");if(C||k){var O=C?e.value():e,j=k?t.value():t;return v||(v=new r),y(O,j,n,g,v)}}return!!A&&(v||(v=new r),i(e,t,n,g,y,v))}},25588:(e,t,n)=>{var r=n(98882),o=n(37005),a="[object Map]";e.exports=function(e){return o(e)&&r(e)==a}},2958:(e,t,n)=>{var r=n(46384),o=n(90939),a=1,i=2;e.exports=function(e,t,n,s){var l=n.length,u=l,c=!s;if(null==e)return!u;for(e=Object(e);l--;){var p=n[l];if(c&&p[2]?p[1]!==e[p[0]]:!(p[0]in e))return!1}for(;++l{var r=n(23560),o=n(15346),a=n(13218),i=n(80346),s=/^\[object .+?Constructor\]$/,l=Function.prototype,u=Object.prototype,c=l.toString,p=u.hasOwnProperty,f=RegExp("^"+c.call(p).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");e.exports=function(e){return!(!a(e)||o(e))&&(r(e)?f:s).test(i(e))}},29221:(e,t,n)=>{var r=n(98882),o=n(37005),a="[object Set]";e.exports=function(e){return o(e)&&r(e)==a}},38749:(e,t,n)=>{var r=n(44239),o=n(41780),a=n(37005),i={};i["[object Float32Array]"]=i["[object Float64Array]"]=i["[object Int8Array]"]=i["[object Int16Array]"]=i["[object Int32Array]"]=i["[object Uint8Array]"]=i["[object Uint8ClampedArray]"]=i["[object Uint16Array]"]=i["[object Uint32Array]"]=!0,i["[object Arguments]"]=i["[object Array]"]=i["[object ArrayBuffer]"]=i["[object Boolean]"]=i["[object DataView]"]=i["[object Date]"]=i["[object Error]"]=i["[object Function]"]=i["[object Map]"]=i["[object Number]"]=i["[object Object]"]=i["[object RegExp]"]=i["[object Set]"]=i["[object String]"]=i["[object WeakMap]"]=!1,e.exports=function(e){return a(e)&&o(e.length)&&!!i[r(e)]}},67206:(e,t,n)=>{var r=n(91573),o=n(16432),a=n(6557),i=n(1469),s=n(39601);e.exports=function(e){return"function"==typeof e?e:null==e?a:"object"==typeof e?i(e)?o(e[0],e[1]):r(e):s(e)}},280:(e,t,n)=>{var r=n(25726),o=n(86916),a=Object.prototype.hasOwnProperty;e.exports=function(e){if(!r(e))return o(e);var t=[];for(var n in Object(e))a.call(e,n)&&"constructor"!=n&&t.push(n);return t}},10313:(e,t,n)=>{var r=n(13218),o=n(25726),a=n(33498),i=Object.prototype.hasOwnProperty;e.exports=function(e){if(!r(e))return a(e);var t=o(e),n=[];for(var s in e)("constructor"!=s||!t&&i.call(e,s))&&n.push(s);return n}},91573:(e,t,n)=>{var r=n(2958),o=n(1499),a=n(42634);e.exports=function(e){var t=o(e);return 1==t.length&&t[0][2]?a(t[0][0],t[0][1]):function(n){return n===e||r(n,e,t)}}},16432:(e,t,n)=>{var r=n(90939),o=n(27361),a=n(79095),i=n(15403),s=n(89162),l=n(42634),u=n(40327),c=1,p=2;e.exports=function(e,t){return i(e)&&s(t)?l(u(e),t):function(n){var i=o(n,e);return void 0===i&&i===t?a(n,e):r(t,i,c|p)}}},42980:(e,t,n)=>{var r=n(46384),o=n(86556),a=n(28483),i=n(59783),s=n(13218),l=n(81704),u=n(36390);e.exports=function e(t,n,c,p,f){t!==n&&a(n,(function(a,l){if(f||(f=new r),s(a))i(t,n,l,c,e,p,f);else{var h=p?p(u(t,l),a,l+"",t,n,f):void 0;void 0===h&&(h=a),o(t,l,h)}}),l)}},59783:(e,t,n)=>{var r=n(86556),o=n(64626),a=n(77133),i=n(278),s=n(38517),l=n(35694),u=n(1469),c=n(29246),p=n(44144),f=n(23560),h=n(13218),d=n(68630),m=n(36719),g=n(36390),y=n(59881);e.exports=function(e,t,n,v,b,w,E){var x=g(e,n),_=g(t,n),S=E.get(_);if(S)r(e,n,S);else{var A=w?w(x,_,n+"",e,t,E):void 0,C=void 0===A;if(C){var k=u(_),O=!k&&p(_),j=!k&&!O&&m(_);A=_,k||O||j?u(x)?A=x:c(x)?A=i(x):O?(C=!1,A=o(_,!0)):j?(C=!1,A=a(_,!0)):A=[]:d(_)||l(_)?(A=x,l(x)?A=y(x):h(x)&&!f(x)||(A=s(_))):C=!1}C&&(E.set(_,A),b(A,_,v,w,E),E.delete(_)),r(e,n,A)}}},40371:e=>{e.exports=function(e){return function(t){return null==t?void 0:t[e]}}},79152:(e,t,n)=>{var r=n(97786);e.exports=function(e){return function(t){return r(t,e)}}},18674:e=>{e.exports=function(e){return function(t){return null==e?void 0:e[t]}}},10107:e=>{e.exports=function(e,t,n,r,o){return o(e,(function(e,o,a){n=r?(r=!1,e):t(n,e,o,a)})),n}},5976:(e,t,n)=>{var r=n(6557),o=n(45357),a=n(30061);e.exports=function(e,t){return a(o(e,t,r),e+"")}},10611:(e,t,n)=>{var r=n(34865),o=n(71811),a=n(65776),i=n(13218),s=n(40327);e.exports=function(e,t,n,l){if(!i(e))return e;for(var u=-1,c=(t=o(t,e)).length,p=c-1,f=e;null!=f&&++u{var r=n(75703),o=n(38777),a=n(6557),i=o?function(e,t){return o(e,"toString",{configurable:!0,enumerable:!1,value:r(t),writable:!0})}:a;e.exports=i},14259:e=>{e.exports=function(e,t,n){var r=-1,o=e.length;t<0&&(t=-t>o?0:o+t),(n=n>o?o:n)<0&&(n+=o),o=t>n?0:n-t>>>0,t>>>=0;for(var a=Array(o);++r{var r=n(89881);e.exports=function(e,t){var n;return r(e,(function(e,r,o){return!(n=t(e,r,o))})),!!n}},22545:e=>{e.exports=function(e,t){for(var n=-1,r=Array(e);++n{var r=n(62705),o=n(29932),a=n(1469),i=n(33448),s=1/0,l=r?r.prototype:void 0,u=l?l.toString:void 0;e.exports=function e(t){if("string"==typeof t)return t;if(a(t))return o(t,e)+"";if(i(t))return u?u.call(t):"";var n=t+"";return"0"==n&&1/t==-s?"-0":n}},27561:(e,t,n)=>{var r=n(67990),o=/^\s+/;e.exports=function(e){return e?e.slice(0,r(e)+1).replace(o,""):e}},7518:e=>{e.exports=function(e){return function(t){return e(t)}}},57406:(e,t,n)=>{var r=n(71811),o=n(10928),a=n(40292),i=n(40327);e.exports=function(e,t){return t=r(t,e),null==(e=a(e,t))||delete e[i(o(t))]}},1757:e=>{e.exports=function(e,t,n){for(var r=-1,o=e.length,a=t.length,i={};++r{e.exports=function(e,t){return e.has(t)}},71811:(e,t,n)=>{var r=n(1469),o=n(15403),a=n(55514),i=n(79833);e.exports=function(e,t){return r(e)?e:o(e,t)?[e]:a(i(e))}},40180:(e,t,n)=>{var r=n(14259);e.exports=function(e,t,n){var o=e.length;return n=void 0===n?o:n,!t&&n>=o?e:r(e,t,n)}},74318:(e,t,n)=>{var r=n(11149);e.exports=function(e){var t=new e.constructor(e.byteLength);return new r(t).set(new r(e)),t}},64626:(e,t,n)=>{e=n.nmd(e);var r=n(55639),o=t&&!t.nodeType&&t,a=o&&e&&!e.nodeType&&e,i=a&&a.exports===o?r.Buffer:void 0,s=i?i.allocUnsafe:void 0;e.exports=function(e,t){if(t)return e.slice();var n=e.length,r=s?s(n):new e.constructor(n);return e.copy(r),r}},57157:(e,t,n)=>{var r=n(74318);e.exports=function(e,t){var n=t?r(e.buffer):e.buffer;return new e.constructor(n,e.byteOffset,e.byteLength)}},93147:e=>{var t=/\w*$/;e.exports=function(e){var n=new e.constructor(e.source,t.exec(e));return n.lastIndex=e.lastIndex,n}},40419:(e,t,n)=>{var r=n(62705),o=r?r.prototype:void 0,a=o?o.valueOf:void 0;e.exports=function(e){return a?Object(a.call(e)):{}}},77133:(e,t,n)=>{var r=n(74318);e.exports=function(e,t){var n=t?r(e.buffer):e.buffer;return new e.constructor(n,e.byteOffset,e.length)}},278:e=>{e.exports=function(e,t){var n=-1,r=e.length;for(t||(t=Array(r));++n{var r=n(34865),o=n(89465);e.exports=function(e,t,n,a){var i=!n;n||(n={});for(var s=-1,l=t.length;++s{var r=n(98363),o=n(99551);e.exports=function(e,t){return r(e,o(e),t)}},1911:(e,t,n)=>{var r=n(98363),o=n(51442);e.exports=function(e,t){return r(e,o(e),t)}},14429:(e,t,n)=>{var r=n(55639)["__core-js_shared__"];e.exports=r},21463:(e,t,n)=>{var r=n(5976),o=n(16612);e.exports=function(e){return r((function(t,n){var r=-1,a=n.length,i=a>1?n[a-1]:void 0,s=a>2?n[2]:void 0;for(i=e.length>3&&"function"==typeof i?(a--,i):void 0,s&&o(n[0],n[1],s)&&(i=a<3?void 0:i,a=1),t=Object(t);++r{var r=n(98612);e.exports=function(e,t){return function(n,o){if(null==n)return n;if(!r(n))return e(n,o);for(var a=n.length,i=t?a:-1,s=Object(n);(t?i--:++i{e.exports=function(e){return function(t,n,r){for(var o=-1,a=Object(t),i=r(t),s=i.length;s--;){var l=i[e?s:++o];if(!1===n(a[l],l,a))break}return t}}},98805:(e,t,n)=>{var r=n(40180),o=n(62689),a=n(83140),i=n(79833);e.exports=function(e){return function(t){t=i(t);var n=o(t)?a(t):void 0,s=n?n[0]:t.charAt(0),l=n?r(n,1).join(""):t.slice(1);return s[e]()+l}}},35393:(e,t,n)=>{var r=n(62663),o=n(53816),a=n(58748),i=RegExp("['’]","g");e.exports=function(e){return function(t){return r(a(o(t).replace(i,"")),e,"")}}},67740:(e,t,n)=>{var r=n(67206),o=n(98612),a=n(3674);e.exports=function(e){return function(t,n,i){var s=Object(t);if(!o(t)){var l=r(n,3);t=a(t),n=function(e){return l(s[e],e,s)}}var u=e(t,n,i);return u>-1?s[l?t[u]:u]:void 0}}},60696:(e,t,n)=>{var r=n(68630);e.exports=function(e){return r(e)?void 0:e}},69389:(e,t,n)=>{var r=n(18674)({À:"A",Á:"A",Â:"A",Ã:"A",Ä:"A",Å:"A",à:"a",á:"a",â:"a",ã:"a",ä:"a",å:"a",Ç:"C",ç:"c",Ð:"D",ð:"d",È:"E",É:"E",Ê:"E",Ë:"E",è:"e",é:"e",ê:"e",ë:"e",Ì:"I",Í:"I",Î:"I",Ï:"I",ì:"i",í:"i",î:"i",ï:"i",Ñ:"N",ñ:"n",Ò:"O",Ó:"O",Ô:"O",Õ:"O",Ö:"O",Ø:"O",ò:"o",ó:"o",ô:"o",õ:"o",ö:"o",ø:"o",Ù:"U",Ú:"U",Û:"U",Ü:"U",ù:"u",ú:"u",û:"u",ü:"u",Ý:"Y",ý:"y",ÿ:"y",Æ:"Ae",æ:"ae",Þ:"Th",þ:"th",ß:"ss",Ā:"A",Ă:"A",Ą:"A",ā:"a",ă:"a",ą:"a",Ć:"C",Ĉ:"C",Ċ:"C",Č:"C",ć:"c",ĉ:"c",ċ:"c",č:"c",Ď:"D",Đ:"D",ď:"d",đ:"d",Ē:"E",Ĕ:"E",Ė:"E",Ę:"E",Ě:"E",ē:"e",ĕ:"e",ė:"e",ę:"e",ě:"e",Ĝ:"G",Ğ:"G",Ġ:"G",Ģ:"G",ĝ:"g",ğ:"g",ġ:"g",ģ:"g",Ĥ:"H",Ħ:"H",ĥ:"h",ħ:"h",Ĩ:"I",Ī:"I",Ĭ:"I",Į:"I",İ:"I",ĩ:"i",ī:"i",ĭ:"i",į:"i",ı:"i",Ĵ:"J",ĵ:"j",Ķ:"K",ķ:"k",ĸ:"k",Ĺ:"L",Ļ:"L",Ľ:"L",Ŀ:"L",Ł:"L",ĺ:"l",ļ:"l",ľ:"l",ŀ:"l",ł:"l",Ń:"N",Ņ:"N",Ň:"N",Ŋ:"N",ń:"n",ņ:"n",ň:"n",ŋ:"n",Ō:"O",Ŏ:"O",Ő:"O",ō:"o",ŏ:"o",ő:"o",Ŕ:"R",Ŗ:"R",Ř:"R",ŕ:"r",ŗ:"r",ř:"r",Ś:"S",Ŝ:"S",Ş:"S",Š:"S",ś:"s",ŝ:"s",ş:"s",š:"s",Ţ:"T",Ť:"T",Ŧ:"T",ţ:"t",ť:"t",ŧ:"t",Ũ:"U",Ū:"U",Ŭ:"U",Ů:"U",Ű:"U",Ų:"U",ũ:"u",ū:"u",ŭ:"u",ů:"u",ű:"u",ų:"u",Ŵ:"W",ŵ:"w",Ŷ:"Y",ŷ:"y",Ÿ:"Y",Ź:"Z",Ż:"Z",Ž:"Z",ź:"z",ż:"z",ž:"z",IJ:"IJ",ij:"ij",Œ:"Oe",œ:"oe",ʼn:"'n",ſ:"s"});e.exports=r},38777:(e,t,n)=>{var r=n(10852),o=function(){try{var e=r(Object,"defineProperty");return e({},"",{}),e}catch(e){}}();e.exports=o},67114:(e,t,n)=>{var r=n(88668),o=n(82908),a=n(74757),i=1,s=2;e.exports=function(e,t,n,l,u,c){var p=n&i,f=e.length,h=t.length;if(f!=h&&!(p&&h>f))return!1;var d=c.get(e),m=c.get(t);if(d&&m)return d==t&&m==e;var g=-1,y=!0,v=n&s?new r:void 0;for(c.set(e,t),c.set(t,e);++g{var r=n(62705),o=n(11149),a=n(77813),i=n(67114),s=n(68776),l=n(21814),u=1,c=2,p="[object Boolean]",f="[object Date]",h="[object Error]",d="[object Map]",m="[object Number]",g="[object RegExp]",y="[object Set]",v="[object String]",b="[object Symbol]",w="[object ArrayBuffer]",E="[object DataView]",x=r?r.prototype:void 0,_=x?x.valueOf:void 0;e.exports=function(e,t,n,r,x,S,A){switch(n){case E:if(e.byteLength!=t.byteLength||e.byteOffset!=t.byteOffset)return!1;e=e.buffer,t=t.buffer;case w:return!(e.byteLength!=t.byteLength||!S(new o(e),new o(t)));case p:case f:case m:return a(+e,+t);case h:return e.name==t.name&&e.message==t.message;case g:case v:return e==t+"";case d:var C=s;case y:var k=r&u;if(C||(C=l),e.size!=t.size&&!k)return!1;var O=A.get(e);if(O)return O==t;r|=c,A.set(e,t);var j=i(C(e),C(t),r,x,S,A);return A.delete(e),j;case b:if(_)return _.call(e)==_.call(t)}return!1}},16096:(e,t,n)=>{var r=n(58234),o=1,a=Object.prototype.hasOwnProperty;e.exports=function(e,t,n,i,s,l){var u=n&o,c=r(e),p=c.length;if(p!=r(t).length&&!u)return!1;for(var f=p;f--;){var h=c[f];if(!(u?h in t:a.call(t,h)))return!1}var d=l.get(e),m=l.get(t);if(d&&m)return d==t&&m==e;var g=!0;l.set(e,t),l.set(t,e);for(var y=u;++f{var r=n(85564),o=n(45357),a=n(30061);e.exports=function(e){return a(o(e,void 0,r),e+"")}},31957:(e,t,n)=>{var r="object"==typeof n.g&&n.g&&n.g.Object===Object&&n.g;e.exports=r},58234:(e,t,n)=>{var r=n(68866),o=n(99551),a=n(3674);e.exports=function(e){return r(e,a,o)}},46904:(e,t,n)=>{var r=n(68866),o=n(51442),a=n(81704);e.exports=function(e){return r(e,a,o)}},45050:(e,t,n)=>{var r=n(37019);e.exports=function(e,t){var n=e.__data__;return r(t)?n["string"==typeof t?"string":"hash"]:n.map}},1499:(e,t,n)=>{var r=n(89162),o=n(3674);e.exports=function(e){for(var t=o(e),n=t.length;n--;){var a=t[n],i=e[a];t[n]=[a,i,r(i)]}return t}},10852:(e,t,n)=>{var r=n(28458),o=n(47801);e.exports=function(e,t){var n=o(e,t);return r(n)?n:void 0}},85924:(e,t,n)=>{var r=n(5569)(Object.getPrototypeOf,Object);e.exports=r},89607:(e,t,n)=>{var r=n(62705),o=Object.prototype,a=o.hasOwnProperty,i=o.toString,s=r?r.toStringTag:void 0;e.exports=function(e){var t=a.call(e,s),n=e[s];try{e[s]=void 0;var r=!0}catch(e){}var o=i.call(e);return r&&(t?e[s]=n:delete e[s]),o}},99551:(e,t,n)=>{var r=n(34963),o=n(70479),a=Object.prototype.propertyIsEnumerable,i=Object.getOwnPropertySymbols,s=i?function(e){return null==e?[]:(e=Object(e),r(i(e),(function(t){return a.call(e,t)})))}:o;e.exports=s},51442:(e,t,n)=>{var r=n(62488),o=n(85924),a=n(99551),i=n(70479),s=Object.getOwnPropertySymbols?function(e){for(var t=[];e;)r(t,a(e)),e=o(e);return t}:i;e.exports=s},98882:(e,t,n)=>{var r=n(18552),o=n(57071),a=n(53818),i=n(58525),s=n(70577),l=n(44239),u=n(80346),c="[object Map]",p="[object Promise]",f="[object Set]",h="[object WeakMap]",d="[object DataView]",m=u(r),g=u(o),y=u(a),v=u(i),b=u(s),w=l;(r&&w(new r(new ArrayBuffer(1)))!=d||o&&w(new o)!=c||a&&w(a.resolve())!=p||i&&w(new i)!=f||s&&w(new s)!=h)&&(w=function(e){var t=l(e),n="[object Object]"==t?e.constructor:void 0,r=n?u(n):"";if(r)switch(r){case m:return d;case g:return c;case y:return p;case v:return f;case b:return h}return t}),e.exports=w},47801:e=>{e.exports=function(e,t){return null==e?void 0:e[t]}},222:(e,t,n)=>{var r=n(71811),o=n(35694),a=n(1469),i=n(65776),s=n(41780),l=n(40327);e.exports=function(e,t,n){for(var u=-1,c=(t=r(t,e)).length,p=!1;++u{var t=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\ufe0e\\ufe0f]");e.exports=function(e){return t.test(e)}},93157:e=>{var t=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;e.exports=function(e){return t.test(e)}},51789:(e,t,n)=>{var r=n(94536);e.exports=function(){this.__data__=r?r(null):{},this.size=0}},80401:e=>{e.exports=function(e){var t=this.has(e)&&delete this.__data__[e];return this.size-=t?1:0,t}},57667:(e,t,n)=>{var r=n(94536),o="__lodash_hash_undefined__",a=Object.prototype.hasOwnProperty;e.exports=function(e){var t=this.__data__;if(r){var n=t[e];return n===o?void 0:n}return a.call(t,e)?t[e]:void 0}},21327:(e,t,n)=>{var r=n(94536),o=Object.prototype.hasOwnProperty;e.exports=function(e){var t=this.__data__;return r?void 0!==t[e]:o.call(t,e)}},81866:(e,t,n)=>{var r=n(94536),o="__lodash_hash_undefined__";e.exports=function(e,t){var n=this.__data__;return this.size+=this.has(e)?0:1,n[e]=r&&void 0===t?o:t,this}},43824:e=>{var t=Object.prototype.hasOwnProperty;e.exports=function(e){var n=e.length,r=new e.constructor(n);return n&&"string"==typeof e[0]&&t.call(e,"index")&&(r.index=e.index,r.input=e.input),r}},29148:(e,t,n)=>{var r=n(74318),o=n(57157),a=n(93147),i=n(40419),s=n(77133),l="[object Boolean]",u="[object Date]",c="[object Map]",p="[object Number]",f="[object RegExp]",h="[object Set]",d="[object String]",m="[object Symbol]",g="[object ArrayBuffer]",y="[object DataView]",v="[object Float32Array]",b="[object Float64Array]",w="[object Int8Array]",E="[object Int16Array]",x="[object Int32Array]",_="[object Uint8Array]",S="[object Uint8ClampedArray]",A="[object Uint16Array]",C="[object Uint32Array]";e.exports=function(e,t,n){var k=e.constructor;switch(t){case g:return r(e);case l:case u:return new k(+e);case y:return o(e,n);case v:case b:case w:case E:case x:case _:case S:case A:case C:return s(e,n);case c:return new k;case p:case d:return new k(e);case f:return a(e);case h:return new k;case m:return i(e)}}},38517:(e,t,n)=>{var r=n(3118),o=n(85924),a=n(25726);e.exports=function(e){return"function"!=typeof e.constructor||a(e)?{}:r(o(e))}},37285:(e,t,n)=>{var r=n(62705),o=n(35694),a=n(1469),i=r?r.isConcatSpreadable:void 0;e.exports=function(e){return a(e)||o(e)||!!(i&&e&&e[i])}},65776:e=>{var t=9007199254740991,n=/^(?:0|[1-9]\d*)$/;e.exports=function(e,r){var o=typeof e;return!!(r=null==r?t:r)&&("number"==o||"symbol"!=o&&n.test(e))&&e>-1&&e%1==0&&e{var r=n(77813),o=n(98612),a=n(65776),i=n(13218);e.exports=function(e,t,n){if(!i(n))return!1;var s=typeof t;return!!("number"==s?o(n)&&a(t,n.length):"string"==s&&t in n)&&r(n[t],e)}},15403:(e,t,n)=>{var r=n(1469),o=n(33448),a=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,i=/^\w*$/;e.exports=function(e,t){if(r(e))return!1;var n=typeof e;return!("number"!=n&&"symbol"!=n&&"boolean"!=n&&null!=e&&!o(e))||(i.test(e)||!a.test(e)||null!=t&&e in Object(t))}},37019:e=>{e.exports=function(e){var t=typeof e;return"string"==t||"number"==t||"symbol"==t||"boolean"==t?"__proto__"!==e:null===e}},15346:(e,t,n)=>{var r,o=n(14429),a=(r=/[^.]+$/.exec(o&&o.keys&&o.keys.IE_PROTO||""))?"Symbol(src)_1."+r:"";e.exports=function(e){return!!a&&a in e}},25726:e=>{var t=Object.prototype;e.exports=function(e){var n=e&&e.constructor;return e===("function"==typeof n&&n.prototype||t)}},89162:(e,t,n)=>{var r=n(13218);e.exports=function(e){return e==e&&!r(e)}},27040:e=>{e.exports=function(){this.__data__=[],this.size=0}},14125:(e,t,n)=>{var r=n(18470),o=Array.prototype.splice;e.exports=function(e){var t=this.__data__,n=r(t,e);return!(n<0)&&(n==t.length-1?t.pop():o.call(t,n,1),--this.size,!0)}},82117:(e,t,n)=>{var r=n(18470);e.exports=function(e){var t=this.__data__,n=r(t,e);return n<0?void 0:t[n][1]}},67518:(e,t,n)=>{var r=n(18470);e.exports=function(e){return r(this.__data__,e)>-1}},54705:(e,t,n)=>{var r=n(18470);e.exports=function(e,t){var n=this.__data__,o=r(n,e);return o<0?(++this.size,n.push([e,t])):n[o][1]=t,this}},24785:(e,t,n)=>{var r=n(1989),o=n(38407),a=n(57071);e.exports=function(){this.size=0,this.__data__={hash:new r,map:new(a||o),string:new r}}},11285:(e,t,n)=>{var r=n(45050);e.exports=function(e){var t=r(this,e).delete(e);return this.size-=t?1:0,t}},96e3:(e,t,n)=>{var r=n(45050);e.exports=function(e){return r(this,e).get(e)}},49916:(e,t,n)=>{var r=n(45050);e.exports=function(e){return r(this,e).has(e)}},95265:(e,t,n)=>{var r=n(45050);e.exports=function(e,t){var n=r(this,e),o=n.size;return n.set(e,t),this.size+=n.size==o?0:1,this}},68776:e=>{e.exports=function(e){var t=-1,n=Array(e.size);return e.forEach((function(e,r){n[++t]=[r,e]})),n}},42634:e=>{e.exports=function(e,t){return function(n){return null!=n&&(n[e]===t&&(void 0!==t||e in Object(n)))}}},24523:(e,t,n)=>{var r=n(88306),o=500;e.exports=function(e){var t=r(e,(function(e){return n.size===o&&n.clear(),e})),n=t.cache;return t}},94536:(e,t,n)=>{var r=n(10852)(Object,"create");e.exports=r},86916:(e,t,n)=>{var r=n(5569)(Object.keys,Object);e.exports=r},33498:e=>{e.exports=function(e){var t=[];if(null!=e)for(var n in Object(e))t.push(n);return t}},31167:(e,t,n)=>{e=n.nmd(e);var r=n(31957),o=t&&!t.nodeType&&t,a=o&&e&&!e.nodeType&&e,i=a&&a.exports===o&&r.process,s=function(){try{var e=a&&a.require&&a.require("util").types;return e||i&&i.binding&&i.binding("util")}catch(e){}}();e.exports=s},2333:e=>{var t=Object.prototype.toString;e.exports=function(e){return t.call(e)}},5569:e=>{e.exports=function(e,t){return function(n){return e(t(n))}}},45357:(e,t,n)=>{var r=n(96874),o=Math.max;e.exports=function(e,t,n){return t=o(void 0===t?e.length-1:t,0),function(){for(var a=arguments,i=-1,s=o(a.length-t,0),l=Array(s);++i{var r=n(97786),o=n(14259);e.exports=function(e,t){return t.length<2?e:r(e,o(t,0,-1))}},55639:(e,t,n)=>{var r=n(31957),o="object"==typeof self&&self&&self.Object===Object&&self,a=r||o||Function("return this")();e.exports=a},36390:e=>{e.exports=function(e,t){if(("constructor"!==t||"function"!=typeof e[t])&&"__proto__"!=t)return e[t]}},90619:e=>{var t="__lodash_hash_undefined__";e.exports=function(e){return this.__data__.set(e,t),this}},72385:e=>{e.exports=function(e){return this.__data__.has(e)}},21814:e=>{e.exports=function(e){var t=-1,n=Array(e.size);return e.forEach((function(e){n[++t]=e})),n}},30061:(e,t,n)=>{var r=n(56560),o=n(21275)(r);e.exports=o},21275:e=>{var t=800,n=16,r=Date.now;e.exports=function(e){var o=0,a=0;return function(){var i=r(),s=n-(i-a);if(a=i,s>0){if(++o>=t)return arguments[0]}else o=0;return e.apply(void 0,arguments)}}},37465:(e,t,n)=>{var r=n(38407);e.exports=function(){this.__data__=new r,this.size=0}},63779:e=>{e.exports=function(e){var t=this.__data__,n=t.delete(e);return this.size=t.size,n}},67599:e=>{e.exports=function(e){return this.__data__.get(e)}},44758:e=>{e.exports=function(e){return this.__data__.has(e)}},34309:(e,t,n)=>{var r=n(38407),o=n(57071),a=n(83369),i=200;e.exports=function(e,t){var n=this.__data__;if(n instanceof r){var s=n.__data__;if(!o||s.length{var r=n(44286),o=n(62689),a=n(676);e.exports=function(e){return o(e)?a(e):r(e)}},55514:(e,t,n)=>{var r=n(24523),o=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,a=/\\(\\)?/g,i=r((function(e){var t=[];return 46===e.charCodeAt(0)&&t.push(""),e.replace(o,(function(e,n,r,o){t.push(r?o.replace(a,"$1"):n||e)})),t}));e.exports=i},40327:(e,t,n)=>{var r=n(33448),o=1/0;e.exports=function(e){if("string"==typeof e||r(e))return e;var t=e+"";return"0"==t&&1/e==-o?"-0":t}},80346:e=>{var t=Function.prototype.toString;e.exports=function(e){if(null!=e){try{return t.call(e)}catch(e){}try{return e+""}catch(e){}}return""}},67990:e=>{var t=/\s/;e.exports=function(e){for(var n=e.length;n--&&t.test(e.charAt(n)););return n}},676:e=>{var t="\\ud800-\\udfff",n="["+t+"]",r="[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]",o="\\ud83c[\\udffb-\\udfff]",a="[^"+t+"]",i="(?:\\ud83c[\\udde6-\\uddff]){2}",s="[\\ud800-\\udbff][\\udc00-\\udfff]",l="(?:"+r+"|"+o+")"+"?",u="[\\ufe0e\\ufe0f]?",c=u+l+("(?:\\u200d(?:"+[a,i,s].join("|")+")"+u+l+")*"),p="(?:"+[a+r+"?",r,i,s,n].join("|")+")",f=RegExp(o+"(?="+o+")|"+p+c,"g");e.exports=function(e){return e.match(f)||[]}},2757:e=>{var t="\\ud800-\\udfff",n="\\u2700-\\u27bf",r="a-z\\xdf-\\xf6\\xf8-\\xff",o="A-Z\\xc0-\\xd6\\xd8-\\xde",a="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",i="["+a+"]",s="\\d+",l="["+n+"]",u="["+r+"]",c="[^"+t+a+s+n+r+o+"]",p="(?:\\ud83c[\\udde6-\\uddff]){2}",f="[\\ud800-\\udbff][\\udc00-\\udfff]",h="["+o+"]",d="(?:"+u+"|"+c+")",m="(?:"+h+"|"+c+")",g="(?:['’](?:d|ll|m|re|s|t|ve))?",y="(?:['’](?:D|LL|M|RE|S|T|VE))?",v="(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?",b="[\\ufe0e\\ufe0f]?",w=b+v+("(?:\\u200d(?:"+["[^"+t+"]",p,f].join("|")+")"+b+v+")*"),E="(?:"+[l,p,f].join("|")+")"+w,x=RegExp([h+"?"+u+"+"+g+"(?="+[i,h,"$"].join("|")+")",m+"+"+y+"(?="+[i,h+d,"$"].join("|")+")",h+"?"+d+"+"+g,h+"+"+y,"\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",s,E].join("|"),"g");e.exports=function(e){return e.match(x)||[]}},68929:(e,t,n)=>{var r=n(48403),o=n(35393)((function(e,t,n){return t=t.toLowerCase(),e+(n?r(t):t)}));e.exports=o},48403:(e,t,n)=>{var r=n(79833),o=n(11700);e.exports=function(e){return o(r(e).toLowerCase())}},75703:e=>{e.exports=function(e){return function(){return e}}},23279:(e,t,n)=>{var r=n(13218),o=n(7771),a=n(14841),i="Expected a function",s=Math.max,l=Math.min;e.exports=function(e,t,n){var u,c,p,f,h,d,m=0,g=!1,y=!1,v=!0;if("function"!=typeof e)throw new TypeError(i);function b(t){var n=u,r=c;return u=c=void 0,m=t,f=e.apply(r,n)}function w(e){var n=e-d;return void 0===d||n>=t||n<0||y&&e-m>=p}function E(){var e=o();if(w(e))return x(e);h=setTimeout(E,function(e){var n=t-(e-d);return y?l(n,p-(e-m)):n}(e))}function x(e){return h=void 0,v&&u?b(e):(u=c=void 0,f)}function _(){var e=o(),n=w(e);if(u=arguments,c=this,d=e,n){if(void 0===h)return function(e){return m=e,h=setTimeout(E,t),g?b(e):f}(d);if(y)return clearTimeout(h),h=setTimeout(E,t),b(d)}return void 0===h&&(h=setTimeout(E,t)),f}return t=a(t)||0,r(n)&&(g=!!n.leading,p=(y="maxWait"in n)?s(a(n.maxWait)||0,t):p,v="trailing"in n?!!n.trailing:v),_.cancel=function(){void 0!==h&&clearTimeout(h),m=0,u=d=c=h=void 0},_.flush=function(){return void 0===h?f:x(o())},_}},53816:(e,t,n)=>{var r=n(69389),o=n(79833),a=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,i=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]","g");e.exports=function(e){return(e=o(e))&&e.replace(a,r).replace(i,"")}},77813:e=>{e.exports=function(e,t){return e===t||e!=e&&t!=t}},13311:(e,t,n)=>{var r=n(67740)(n(30998));e.exports=r},30998:(e,t,n)=>{var r=n(41848),o=n(67206),a=n(40554),i=Math.max;e.exports=function(e,t,n){var s=null==e?0:e.length;if(!s)return-1;var l=null==n?0:a(n);return l<0&&(l=i(s+l,0)),r(e,o(t,3),l)}},85564:(e,t,n)=>{var r=n(21078);e.exports=function(e){return(null==e?0:e.length)?r(e,1):[]}},27361:(e,t,n)=>{var r=n(97786);e.exports=function(e,t,n){var o=null==e?void 0:r(e,t);return void 0===o?n:o}},79095:(e,t,n)=>{var r=n(13),o=n(222);e.exports=function(e,t){return null!=e&&o(e,t,r)}},6557:e=>{e.exports=function(e){return e}},35694:(e,t,n)=>{var r=n(9454),o=n(37005),a=Object.prototype,i=a.hasOwnProperty,s=a.propertyIsEnumerable,l=r(function(){return arguments}())?r:function(e){return o(e)&&i.call(e,"callee")&&!s.call(e,"callee")};e.exports=l},1469:e=>{var t=Array.isArray;e.exports=t},98612:(e,t,n)=>{var r=n(23560),o=n(41780);e.exports=function(e){return null!=e&&o(e.length)&&!r(e)}},29246:(e,t,n)=>{var r=n(98612),o=n(37005);e.exports=function(e){return o(e)&&r(e)}},44144:(e,t,n)=>{e=n.nmd(e);var r=n(55639),o=n(95062),a=t&&!t.nodeType&&t,i=a&&e&&!e.nodeType&&e,s=i&&i.exports===a?r.Buffer:void 0,l=(s?s.isBuffer:void 0)||o;e.exports=l},41609:(e,t,n)=>{var r=n(280),o=n(98882),a=n(35694),i=n(1469),s=n(98612),l=n(44144),u=n(25726),c=n(36719),p="[object Map]",f="[object Set]",h=Object.prototype.hasOwnProperty;e.exports=function(e){if(null==e)return!0;if(s(e)&&(i(e)||"string"==typeof e||"function"==typeof e.splice||l(e)||c(e)||a(e)))return!e.length;var t=o(e);if(t==p||t==f)return!e.size;if(u(e))return!r(e).length;for(var n in e)if(h.call(e,n))return!1;return!0}},23560:(e,t,n)=>{var r=n(44239),o=n(13218),a="[object AsyncFunction]",i="[object Function]",s="[object GeneratorFunction]",l="[object Proxy]";e.exports=function(e){if(!o(e))return!1;var t=r(e);return t==i||t==s||t==a||t==l}},41780:e=>{var t=9007199254740991;e.exports=function(e){return"number"==typeof e&&e>-1&&e%1==0&&e<=t}},56688:(e,t,n)=>{var r=n(25588),o=n(7518),a=n(31167),i=a&&a.isMap,s=i?o(i):r;e.exports=s},13218:e=>{e.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},37005:e=>{e.exports=function(e){return null!=e&&"object"==typeof e}},68630:(e,t,n)=>{var r=n(44239),o=n(85924),a=n(37005),i="[object Object]",s=Function.prototype,l=Object.prototype,u=s.toString,c=l.hasOwnProperty,p=u.call(Object);e.exports=function(e){if(!a(e)||r(e)!=i)return!1;var t=o(e);if(null===t)return!0;var n=c.call(t,"constructor")&&t.constructor;return"function"==typeof n&&n instanceof n&&u.call(n)==p}},72928:(e,t,n)=>{var r=n(29221),o=n(7518),a=n(31167),i=a&&a.isSet,s=i?o(i):r;e.exports=s},47037:(e,t,n)=>{var r=n(44239),o=n(1469),a=n(37005),i="[object String]";e.exports=function(e){return"string"==typeof e||!o(e)&&a(e)&&r(e)==i}},33448:(e,t,n)=>{var r=n(44239),o=n(37005),a="[object Symbol]";e.exports=function(e){return"symbol"==typeof e||o(e)&&r(e)==a}},36719:(e,t,n)=>{var r=n(38749),o=n(7518),a=n(31167),i=a&&a.isTypedArray,s=i?o(i):r;e.exports=s},3674:(e,t,n)=>{var r=n(14636),o=n(280),a=n(98612);e.exports=function(e){return a(e)?r(e):o(e)}},81704:(e,t,n)=>{var r=n(14636),o=n(10313),a=n(98612);e.exports=function(e){return a(e)?r(e,!0):o(e)}},10928:e=>{e.exports=function(e){var t=null==e?0:e.length;return t?e[t-1]:void 0}},88306:(e,t,n)=>{var r=n(83369),o="Expected a function";function a(e,t){if("function"!=typeof e||null!=t&&"function"!=typeof t)throw new TypeError(o);var n=function(){var r=arguments,o=t?t.apply(this,r):r[0],a=n.cache;if(a.has(o))return a.get(o);var i=e.apply(this,r);return n.cache=a.set(o,i)||a,i};return n.cache=new(a.Cache||r),n}a.Cache=r,e.exports=a},82492:(e,t,n)=>{var r=n(42980),o=n(21463)((function(e,t,n){r(e,t,n)}));e.exports=o},7771:(e,t,n)=>{var r=n(55639);e.exports=function(){return r.Date.now()}},57557:(e,t,n)=>{var r=n(29932),o=n(85990),a=n(57406),i=n(71811),s=n(98363),l=n(60696),u=n(99021),c=n(46904),p=u((function(e,t){var n={};if(null==e)return n;var u=!1;t=r(t,(function(t){return t=i(t,e),u||(u=t.length>1),t})),s(e,c(e),n),u&&(n=o(n,7,l));for(var p=t.length;p--;)a(n,t[p]);return n}));e.exports=p},39601:(e,t,n)=>{var r=n(40371),o=n(79152),a=n(15403),i=n(40327);e.exports=function(e){return a(e)?r(i(e)):o(e)}},54061:(e,t,n)=>{var r=n(62663),o=n(89881),a=n(67206),i=n(10107),s=n(1469);e.exports=function(e,t,n){var l=s(e)?r:i,u=arguments.length<3;return l(e,a(t,4),n,u,o)}},36968:(e,t,n)=>{var r=n(10611);e.exports=function(e,t,n){return null==e?e:r(e,t,n)}},59704:(e,t,n)=>{var r=n(82908),o=n(67206),a=n(5076),i=n(1469),s=n(16612);e.exports=function(e,t,n){var l=i(e)?r:a;return n&&s(e,t,n)&&(t=void 0),l(e,o(t,3))}},70479:e=>{e.exports=function(){return[]}},95062:e=>{e.exports=function(){return!1}},18601:(e,t,n)=>{var r=n(14841),o=1/0,a=17976931348623157e292;e.exports=function(e){return e?(e=r(e))===o||e===-o?(e<0?-1:1)*a:e==e?e:0:0===e?e:0}},40554:(e,t,n)=>{var r=n(18601);e.exports=function(e){var t=r(e),n=t%1;return t==t?n?t-n:t:0}},7334:(e,t,n)=>{var r=n(79833);e.exports=function(e){return r(e).toLowerCase()}},14841:(e,t,n)=>{var r=n(27561),o=n(13218),a=n(33448),i=NaN,s=/^[-+]0x[0-9a-f]+$/i,l=/^0b[01]+$/i,u=/^0o[0-7]+$/i,c=parseInt;e.exports=function(e){if("number"==typeof e)return e;if(a(e))return i;if(o(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=o(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=r(e);var n=l.test(e);return n||u.test(e)?c(e.slice(2),n?2:8):s.test(e)?i:+e}},59881:(e,t,n)=>{var r=n(98363),o=n(81704);e.exports=function(e){return r(e,o(e))}},79833:(e,t,n)=>{var r=n(80531);e.exports=function(e){return null==e?"":r(e)}},11700:(e,t,n)=>{var r=n(98805)("toUpperCase");e.exports=r},58748:(e,t,n)=>{var r=n(49029),o=n(93157),a=n(79833),i=n(2757);e.exports=function(e,t,n){return e=a(e),void 0===(t=n?void 0:t)?o(e)?i(e):r(e):e.match(t)||[]}},7287:(e,t,n)=>{var r=n(34865),o=n(1757);e.exports=function(e,t){return o(e||[],t||[],r)}},96470:(e,t,n)=>{"use strict";var r=n(47802),o=n(21102);t.highlight=i,t.highlightAuto=function(e,t){var n,s,l,u,c=t||{},p=c.subset||r.listLanguages(),f=c.prefix,h=p.length,d=-1;null==f&&(f=a);if("string"!=typeof e)throw o("Expected `string` for value, got `%s`",e);s={relevance:0,language:null,value:[]},n={relevance:0,language:null,value:[]};for(;++ds.relevance&&(s=l),l.relevance>n.relevance&&(s=n,n=l));s.language&&(n.secondBest=s);return n},t.registerLanguage=function(e,t){r.registerLanguage(e,t)},t.listLanguages=function(){return r.listLanguages()},t.registerAlias=function(e,t){var n,o=e;t&&((o={})[e]=t);for(n in o)r.registerAliases(o[n],{languageName:n})},s.prototype.addText=function(e){var t,n,r=this.stack;if(""===e)return;t=r[r.length-1],(n=t.children[t.children.length-1])&&"text"===n.type?n.value+=e:t.children.push({type:"text",value:e})},s.prototype.addKeyword=function(e,t){this.openNode(t),this.addText(e),this.closeNode()},s.prototype.addSublanguage=function(e,t){var n=this.stack,r=n[n.length-1],o=e.rootNode.children,a=t?{type:"element",tagName:"span",properties:{className:[t]},children:o}:o;r.children=r.children.concat(a)},s.prototype.openNode=function(e){var t=this.stack,n=this.options.classPrefix+e,r=t[t.length-1],o={type:"element",tagName:"span",properties:{className:[n]},children:[]};r.children.push(o),t.push(o)},s.prototype.closeNode=function(){this.stack.pop()},s.prototype.closeAllNodes=l,s.prototype.finalize=l,s.prototype.toHTML=function(){return""};var a="hljs-";function i(e,t,n){var i,l=r.configure({}),u=(n||{}).prefix;if("string"!=typeof e)throw o("Expected `string` for name, got `%s`",e);if(!r.getLanguage(e))throw o("Unknown language: `%s` is not registered",e);if("string"!=typeof t)throw o("Expected `string` for value, got `%s`",t);if(null==u&&(u=a),r.configure({__emitter:s,classPrefix:u}),i=r.highlight(t,{language:e,ignoreIllegals:!0}),r.configure(l||{}),i.errorRaised)throw i.errorRaised;return{relevance:i.relevance,language:i.language,value:i.emitter.rootNode.children}}function s(e){this.options=e,this.rootNode={children:[]},this.stack=[this.rootNode]}function l(){}},27418:e=>{"use strict";var t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach((function(e){r[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(e){return!1}}()?Object.assign:function(e,o){for(var a,i,s=function(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}(e),l=1;l{var r="function"==typeof Map&&Map.prototype,o=Object.getOwnPropertyDescriptor&&r?Object.getOwnPropertyDescriptor(Map.prototype,"size"):null,a=r&&o&&"function"==typeof o.get?o.get:null,i=r&&Map.prototype.forEach,s="function"==typeof Set&&Set.prototype,l=Object.getOwnPropertyDescriptor&&s?Object.getOwnPropertyDescriptor(Set.prototype,"size"):null,u=s&&l&&"function"==typeof l.get?l.get:null,c=s&&Set.prototype.forEach,p="function"==typeof WeakMap&&WeakMap.prototype?WeakMap.prototype.has:null,f="function"==typeof WeakSet&&WeakSet.prototype?WeakSet.prototype.has:null,h="function"==typeof WeakRef&&WeakRef.prototype?WeakRef.prototype.deref:null,d=Boolean.prototype.valueOf,m=Object.prototype.toString,g=Function.prototype.toString,y=String.prototype.match,v=String.prototype.slice,b=String.prototype.replace,w=String.prototype.toUpperCase,E=String.prototype.toLowerCase,x=RegExp.prototype.test,_=Array.prototype.concat,S=Array.prototype.join,A=Array.prototype.slice,C=Math.floor,k="function"==typeof BigInt?BigInt.prototype.valueOf:null,O=Object.getOwnPropertySymbols,j="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?Symbol.prototype.toString:null,I="function"==typeof Symbol&&"object"==typeof Symbol.iterator,T="function"==typeof Symbol&&Symbol.toStringTag&&(typeof Symbol.toStringTag===I||"symbol")?Symbol.toStringTag:null,N=Object.prototype.propertyIsEnumerable,P=("function"==typeof Reflect?Reflect.getPrototypeOf:Object.getPrototypeOf)||([].__proto__===Array.prototype?function(e){return e.__proto__}:null);function R(e,t){if(e===1/0||e===-1/0||e!=e||e&&e>-1e3&&e<1e3||x.call(/e/,t))return t;var n=/[0-9](?=(?:[0-9]{3})+(?![0-9]))/g;if("number"==typeof e){var r=e<0?-C(-e):C(e);if(r!==e){var o=String(r),a=v.call(t,o.length+1);return b.call(o,n,"$&_")+"."+b.call(b.call(a,/([0-9]{3})/g,"$&_"),/_$/,"")}}return b.call(t,n,"$&_")}var M=n(24654),D=M.custom,L=q(D)?D:null;function B(e,t,n){var r="double"===(n.quoteStyle||t)?'"':"'";return r+e+r}function F(e){return b.call(String(e),/"/g,""")}function U(e){return!("[object Array]"!==W(e)||T&&"object"==typeof e&&T in e)}function z(e){return!("[object RegExp]"!==W(e)||T&&"object"==typeof e&&T in e)}function q(e){if(I)return e&&"object"==typeof e&&e instanceof Symbol;if("symbol"==typeof e)return!0;if(!e||"object"!=typeof e||!j)return!1;try{return j.call(e),!0}catch(e){}return!1}e.exports=function e(t,n,r,o){var s=n||{};if(V(s,"quoteStyle")&&"single"!==s.quoteStyle&&"double"!==s.quoteStyle)throw new TypeError('option "quoteStyle" must be "single" or "double"');if(V(s,"maxStringLength")&&("number"==typeof s.maxStringLength?s.maxStringLength<0&&s.maxStringLength!==1/0:null!==s.maxStringLength))throw new TypeError('option "maxStringLength", if provided, must be a positive integer, Infinity, or `null`');var l=!V(s,"customInspect")||s.customInspect;if("boolean"!=typeof l&&"symbol"!==l)throw new TypeError("option \"customInspect\", if provided, must be `true`, `false`, or `'symbol'`");if(V(s,"indent")&&null!==s.indent&&"\t"!==s.indent&&!(parseInt(s.indent,10)===s.indent&&s.indent>0))throw new TypeError('option "indent" must be "\\t", an integer > 0, or `null`');if(V(s,"numericSeparator")&&"boolean"!=typeof s.numericSeparator)throw new TypeError('option "numericSeparator", if provided, must be `true` or `false`');var m=s.numericSeparator;if(void 0===t)return"undefined";if(null===t)return"null";if("boolean"==typeof t)return t?"true":"false";if("string"==typeof t)return J(t,s);if("number"==typeof t){if(0===t)return 1/0/t>0?"0":"-0";var w=String(t);return m?R(t,w):w}if("bigint"==typeof t){var x=String(t)+"n";return m?R(t,x):x}var C=void 0===s.depth?5:s.depth;if(void 0===r&&(r=0),r>=C&&C>0&&"object"==typeof t)return U(t)?"[Array]":"[Object]";var O=function(e,t){var n;if("\t"===e.indent)n="\t";else{if(!("number"==typeof e.indent&&e.indent>0))return null;n=S.call(Array(e.indent+1)," ")}return{base:n,prev:S.call(Array(t+1),n)}}(s,r);if(void 0===o)o=[];else if(H(o,t)>=0)return"[Circular]";function D(t,n,a){if(n&&(o=A.call(o)).push(n),a){var i={depth:s.depth};return V(s,"quoteStyle")&&(i.quoteStyle=s.quoteStyle),e(t,i,r+1,o)}return e(t,s,r+1,o)}if("function"==typeof t&&!z(t)){var $=function(e){if(e.name)return e.name;var t=y.call(g.call(e),/^function\s*([\w$]+)/);if(t)return t[1];return null}(t),K=X(t,D);return"[Function"+($?": "+$:" (anonymous)")+"]"+(K.length>0?" { "+S.call(K,", ")+" }":"")}if(q(t)){var ee=I?b.call(String(t),/^(Symbol\(.*\))_[^)]*$/,"$1"):j.call(t);return"object"!=typeof t||I?ee:G(ee)}if(function(e){if(!e||"object"!=typeof e)return!1;if("undefined"!=typeof HTMLElement&&e instanceof HTMLElement)return!0;return"string"==typeof e.nodeName&&"function"==typeof e.getAttribute}(t)){for(var te="<"+E.call(String(t.nodeName)),ne=t.attributes||[],re=0;re"}if(U(t)){if(0===t.length)return"[]";var oe=X(t,D);return O&&!function(e){for(var t=0;t=0)return!1;return!0}(oe)?"["+Q(oe,O)+"]":"[ "+S.call(oe,", ")+" ]"}if(function(e){return!("[object Error]"!==W(e)||T&&"object"==typeof e&&T in e)}(t)){var ae=X(t,D);return"cause"in Error.prototype||!("cause"in t)||N.call(t,"cause")?0===ae.length?"["+String(t)+"]":"{ ["+String(t)+"] "+S.call(ae,", ")+" }":"{ ["+String(t)+"] "+S.call(_.call("[cause]: "+D(t.cause),ae),", ")+" }"}if("object"==typeof t&&l){if(L&&"function"==typeof t[L]&&M)return M(t,{depth:C-r});if("symbol"!==l&&"function"==typeof t.inspect)return t.inspect()}if(function(e){if(!a||!e||"object"!=typeof e)return!1;try{a.call(e);try{u.call(e)}catch(e){return!0}return e instanceof Map}catch(e){}return!1}(t)){var ie=[];return i&&i.call(t,(function(e,n){ie.push(D(n,t,!0)+" => "+D(e,t))})),Y("Map",a.call(t),ie,O)}if(function(e){if(!u||!e||"object"!=typeof e)return!1;try{u.call(e);try{a.call(e)}catch(e){return!0}return e instanceof Set}catch(e){}return!1}(t)){var se=[];return c&&c.call(t,(function(e){se.push(D(e,t))})),Y("Set",u.call(t),se,O)}if(function(e){if(!p||!e||"object"!=typeof e)return!1;try{p.call(e,p);try{f.call(e,f)}catch(e){return!0}return e instanceof WeakMap}catch(e){}return!1}(t))return Z("WeakMap");if(function(e){if(!f||!e||"object"!=typeof e)return!1;try{f.call(e,f);try{p.call(e,p)}catch(e){return!0}return e instanceof WeakSet}catch(e){}return!1}(t))return Z("WeakSet");if(function(e){if(!h||!e||"object"!=typeof e)return!1;try{return h.call(e),!0}catch(e){}return!1}(t))return Z("WeakRef");if(function(e){return!("[object Number]"!==W(e)||T&&"object"==typeof e&&T in e)}(t))return G(D(Number(t)));if(function(e){if(!e||"object"!=typeof e||!k)return!1;try{return k.call(e),!0}catch(e){}return!1}(t))return G(D(k.call(t)));if(function(e){return!("[object Boolean]"!==W(e)||T&&"object"==typeof e&&T in e)}(t))return G(d.call(t));if(function(e){return!("[object String]"!==W(e)||T&&"object"==typeof e&&T in e)}(t))return G(D(String(t)));if(!function(e){return!("[object Date]"!==W(e)||T&&"object"==typeof e&&T in e)}(t)&&!z(t)){var le=X(t,D),ue=P?P(t)===Object.prototype:t instanceof Object||t.constructor===Object,ce=t instanceof Object?"":"null prototype",pe=!ue&&T&&Object(t)===t&&T in t?v.call(W(t),8,-1):ce?"Object":"",fe=(ue||"function"!=typeof t.constructor?"":t.constructor.name?t.constructor.name+" ":"")+(pe||ce?"["+S.call(_.call([],pe||[],ce||[]),": ")+"] ":"");return 0===le.length?fe+"{}":O?fe+"{"+Q(le,O)+"}":fe+"{ "+S.call(le,", ")+" }"}return String(t)};var $=Object.prototype.hasOwnProperty||function(e){return e in this};function V(e,t){return $.call(e,t)}function W(e){return m.call(e)}function H(e,t){if(e.indexOf)return e.indexOf(t);for(var n=0,r=e.length;nt.maxStringLength){var n=e.length-t.maxStringLength,r="... "+n+" more character"+(n>1?"s":"");return J(v.call(e,0,t.maxStringLength),t)+r}return B(b.call(b.call(e,/(['\\])/g,"\\$1"),/[\x00-\x1f]/g,K),"single",t)}function K(e){var t=e.charCodeAt(0),n={8:"b",9:"t",10:"n",12:"f",13:"r"}[t];return n?"\\"+n:"\\x"+(t<16?"0":"")+w.call(t.toString(16))}function G(e){return"Object("+e+")"}function Z(e){return e+" { ? }"}function Y(e,t,n,r){return e+" ("+t+") {"+(r?Q(n,r):S.call(n,", "))+"}"}function Q(e,t){if(0===e.length)return"";var n="\n"+t.prev+t.base;return n+S.call(e,","+n)+"\n"+t.prev}function X(e,t){var n=U(e),r=[];if(n){r.length=e.length;for(var o=0;o{var t,n,r=e.exports={};function o(){throw new Error("setTimeout has not been defined")}function a(){throw new Error("clearTimeout has not been defined")}function i(e){if(t===setTimeout)return setTimeout(e,0);if((t===o||!t)&&setTimeout)return t=setTimeout,setTimeout(e,0);try{return t(e,0)}catch(n){try{return t.call(null,e,0)}catch(n){return t.call(this,e,0)}}}!function(){try{t="function"==typeof setTimeout?setTimeout:o}catch(e){t=o}try{n="function"==typeof clearTimeout?clearTimeout:a}catch(e){n=a}}();var s,l=[],u=!1,c=-1;function p(){u&&s&&(u=!1,s.length?l=s.concat(l):c=-1,l.length&&f())}function f(){if(!u){var e=i(p);u=!0;for(var t=l.length;t;){for(s=l,l=[];++c1)for(var n=1;n{"use strict";var r=n(50414);function o(){}function a(){}a.resetWarningCache=o,e.exports=function(){function e(e,t,n,o,a,i){if(i!==r){var s=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw s.name="Invariant Violation",s}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:a,resetWarningCache:o};return n.PropTypes=n,n}},45697:(e,t,n)=>{e.exports=n(92703)()},50414:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},55798:e=>{"use strict";var t=String.prototype.replace,n=/%20/g,r="RFC1738",o="RFC3986";e.exports={default:o,formatters:{RFC1738:function(e){return t.call(e,n,"+")},RFC3986:function(e){return String(e)}},RFC1738:r,RFC3986:o}},80129:(e,t,n)=>{"use strict";var r=n(58261),o=n(55235),a=n(55798);e.exports={formats:a,parse:o,stringify:r}},55235:(e,t,n)=>{"use strict";var r=n(12769),o=Object.prototype.hasOwnProperty,a=Array.isArray,i={allowDots:!1,allowPrototypes:!1,allowSparse:!1,arrayLimit:20,charset:"utf-8",charsetSentinel:!1,comma:!1,decoder:r.decode,delimiter:"&",depth:5,ignoreQueryPrefix:!1,interpretNumericEntities:!1,parameterLimit:1e3,parseArrays:!0,plainObjects:!1,strictNullHandling:!1},s=function(e){return e.replace(/&#(\d+);/g,(function(e,t){return String.fromCharCode(parseInt(t,10))}))},l=function(e,t){return e&&"string"==typeof e&&t.comma&&e.indexOf(",")>-1?e.split(","):e},u=function(e,t,n,r){if(e){var a=n.allowDots?e.replace(/\.([^.[]+)/g,"[$1]"):e,i=/(\[[^[\]]*])/g,s=n.depth>0&&/(\[[^[\]]*])/.exec(a),u=s?a.slice(0,s.index):a,c=[];if(u){if(!n.plainObjects&&o.call(Object.prototype,u)&&!n.allowPrototypes)return;c.push(u)}for(var p=0;n.depth>0&&null!==(s=i.exec(a))&&p=0;--a){var i,s=e[a];if("[]"===s&&n.parseArrays)i=[].concat(o);else{i=n.plainObjects?Object.create(null):{};var u="["===s.charAt(0)&&"]"===s.charAt(s.length-1)?s.slice(1,-1):s,c=parseInt(u,10);n.parseArrays||""!==u?!isNaN(c)&&s!==u&&String(c)===u&&c>=0&&n.parseArrays&&c<=n.arrayLimit?(i=[])[c]=o:"__proto__"!==u&&(i[u]=o):i={0:o}}o=i}return o}(c,t,n,r)}};e.exports=function(e,t){var n=function(e){if(!e)return i;if(null!==e.decoder&&void 0!==e.decoder&&"function"!=typeof e.decoder)throw new TypeError("Decoder has to be a function.");if(void 0!==e.charset&&"utf-8"!==e.charset&&"iso-8859-1"!==e.charset)throw new TypeError("The charset option must be either utf-8, iso-8859-1, or undefined");var t=void 0===e.charset?i.charset:e.charset;return{allowDots:void 0===e.allowDots?i.allowDots:!!e.allowDots,allowPrototypes:"boolean"==typeof e.allowPrototypes?e.allowPrototypes:i.allowPrototypes,allowSparse:"boolean"==typeof e.allowSparse?e.allowSparse:i.allowSparse,arrayLimit:"number"==typeof e.arrayLimit?e.arrayLimit:i.arrayLimit,charset:t,charsetSentinel:"boolean"==typeof e.charsetSentinel?e.charsetSentinel:i.charsetSentinel,comma:"boolean"==typeof e.comma?e.comma:i.comma,decoder:"function"==typeof e.decoder?e.decoder:i.decoder,delimiter:"string"==typeof e.delimiter||r.isRegExp(e.delimiter)?e.delimiter:i.delimiter,depth:"number"==typeof e.depth||!1===e.depth?+e.depth:i.depth,ignoreQueryPrefix:!0===e.ignoreQueryPrefix,interpretNumericEntities:"boolean"==typeof e.interpretNumericEntities?e.interpretNumericEntities:i.interpretNumericEntities,parameterLimit:"number"==typeof e.parameterLimit?e.parameterLimit:i.parameterLimit,parseArrays:!1!==e.parseArrays,plainObjects:"boolean"==typeof e.plainObjects?e.plainObjects:i.plainObjects,strictNullHandling:"boolean"==typeof e.strictNullHandling?e.strictNullHandling:i.strictNullHandling}}(t);if(""===e||null==e)return n.plainObjects?Object.create(null):{};for(var c="string"==typeof e?function(e,t){var n,u={},c=t.ignoreQueryPrefix?e.replace(/^\?/,""):e,p=t.parameterLimit===1/0?void 0:t.parameterLimit,f=c.split(t.delimiter,p),h=-1,d=t.charset;if(t.charsetSentinel)for(n=0;n-1&&(g=a(g)?[g]:g),o.call(u,m)?u[m]=r.combine(u[m],g):u[m]=g}return u}(e,n):e,p=n.plainObjects?Object.create(null):{},f=Object.keys(c),h=0;h{"use strict";var r=n(37478),o=n(12769),a=n(55798),i=Object.prototype.hasOwnProperty,s={brackets:function(e){return e+"[]"},comma:"comma",indices:function(e,t){return e+"["+t+"]"},repeat:function(e){return e}},l=Array.isArray,u=String.prototype.split,c=Array.prototype.push,p=function(e,t){c.apply(e,l(t)?t:[t])},f=Date.prototype.toISOString,h=a.default,d={addQueryPrefix:!1,allowDots:!1,charset:"utf-8",charsetSentinel:!1,delimiter:"&",encode:!0,encoder:o.encode,encodeValuesOnly:!1,format:h,formatter:a.formatters[h],indices:!1,serializeDate:function(e){return f.call(e)},skipNulls:!1,strictNullHandling:!1},m={},g=function e(t,n,a,i,s,c,f,h,g,y,v,b,w,E,x,_){for(var S,A=t,C=_,k=0,O=!1;void 0!==(C=C.get(m))&&!O;){var j=C.get(t);if(k+=1,void 0!==j){if(j===k)throw new RangeError("Cyclic object value");O=!0}void 0===C.get(m)&&(k=0)}if("function"==typeof h?A=h(n,A):A instanceof Date?A=v(A):"comma"===a&&l(A)&&(A=o.maybeMap(A,(function(e){return e instanceof Date?v(e):e}))),null===A){if(s)return f&&!E?f(n,d.encoder,x,"key",b):n;A=""}if("string"==typeof(S=A)||"number"==typeof S||"boolean"==typeof S||"symbol"==typeof S||"bigint"==typeof S||o.isBuffer(A)){if(f){var I=E?n:f(n,d.encoder,x,"key",b);if("comma"===a&&E){for(var T=u.call(String(A),","),N="",P=0;P0?A.join(",")||null:void 0}];else if(l(h))R=h;else{var D=Object.keys(A);R=g?D.sort(g):D}for(var L=i&&l(A)&&1===A.length?n+"[]":n,B=0;B0?E+w:""}},12769:(e,t,n)=>{"use strict";var r=n(55798),o=Object.prototype.hasOwnProperty,a=Array.isArray,i=function(){for(var e=[],t=0;t<256;++t)e.push("%"+((t<16?"0":"")+t.toString(16)).toUpperCase());return e}(),s=function(e,t){for(var n=t&&t.plainObjects?Object.create(null):{},r=0;r1;){var t=e.pop(),n=t.obj[t.prop];if(a(n)){for(var r=[],o=0;o=48&&c<=57||c>=65&&c<=90||c>=97&&c<=122||a===r.RFC1738&&(40===c||41===c)?l+=s.charAt(u):c<128?l+=i[c]:c<2048?l+=i[192|c>>6]+i[128|63&c]:c<55296||c>=57344?l+=i[224|c>>12]+i[128|c>>6&63]+i[128|63&c]:(u+=1,c=65536+((1023&c)<<10|1023&s.charCodeAt(u)),l+=i[240|c>>18]+i[128|c>>12&63]+i[128|c>>6&63]+i[128|63&c])}return l},isBuffer:function(e){return!(!e||"object"!=typeof e)&&!!(e.constructor&&e.constructor.isBuffer&&e.constructor.isBuffer(e))},isRegExp:function(e){return"[object RegExp]"===Object.prototype.toString.call(e)},maybeMap:function(e,t){if(a(e)){for(var n=[],r=0;r{"use strict";function t(e,t){return Object.prototype.hasOwnProperty.call(e,t)}e.exports=function(e,n,r,o){n=n||"&",r=r||"=";var a={};if("string"!=typeof e||0===e.length)return a;var i=/\+/g;e=e.split(n);var s=1e3;o&&"number"==typeof o.maxKeys&&(s=o.maxKeys);var l=e.length;s>0&&l>s&&(l=s);for(var u=0;u=0?(c=d.substr(0,m),p=d.substr(m+1)):(c=d,p=""),f=decodeURIComponent(c),h=decodeURIComponent(p),t(a,f)?Array.isArray(a[f])?a[f].push(h):a[f]=[a[f],h]:a[f]=h}return a}},12361:e=>{"use strict";var t=function(e){switch(typeof e){case"string":return e;case"boolean":return e?"true":"false";case"number":return isFinite(e)?e:"";default:return""}};e.exports=function(e,n,r,o){return n=n||"&",r=r||"=",null===e&&(e=void 0),"object"==typeof e?Object.keys(e).map((function(o){var a=encodeURIComponent(t(o))+r;return Array.isArray(e[o])?e[o].map((function(e){return a+encodeURIComponent(t(e))})).join(n):a+encodeURIComponent(t(e[o]))})).join(n):o?encodeURIComponent(t(o))+r+encodeURIComponent(t(e)):""}},17673:(e,t,n)=>{"use strict";t.decode=t.parse=n(62587),t.encode=t.stringify=n(12361)},57129:(e,t)=>{"use strict";var n,r=Object.prototype.hasOwnProperty;function o(e){try{return decodeURIComponent(e.replace(/\+/g," "))}catch(e){return null}}function a(e){try{return encodeURIComponent(e)}catch(e){return null}}t.stringify=function(e,t){t=t||"";var o,i,s=[];for(i in"string"!=typeof t&&(t="?"),e)if(r.call(e,i)){if((o=e[i])||null!==o&&o!==n&&!isNaN(o)||(o=""),i=a(i),o=a(o),null===i||null===o)continue;s.push(i+"="+o)}return s.length?t+s.join("&"):""},t.parse=function(e){for(var t,n=/([^=?#&]+)=?([^&]*)/g,r={};t=n.exec(e);){var a=o(t[1]),i=o(t[2]);null===a||null===i||a in r||(r[a]=i)}return r}},14419:(e,t,n)=>{const r=n(60697),o=n(69450),a=r.types;e.exports=class e{constructor(e,t){if(this._setDefaults(e),e instanceof RegExp)this.ignoreCase=e.ignoreCase,this.multiline=e.multiline,e=e.source;else{if("string"!=typeof e)throw new Error("Expected a regexp or string");this.ignoreCase=t&&-1!==t.indexOf("i"),this.multiline=t&&-1!==t.indexOf("m")}this.tokens=r(e)}_setDefaults(t){this.max=null!=t.max?t.max:null!=e.prototype.max?e.prototype.max:100,this.defaultRange=t.defaultRange?t.defaultRange:this.defaultRange.clone(),t.randInt&&(this.randInt=t.randInt)}gen(){return this._gen(this.tokens,[])}_gen(e,t){var n,r,o,i,s;switch(e.type){case a.ROOT:case a.GROUP:if(e.followedBy||e.notFollowedBy)return"";for(e.remember&&void 0===e.groupNumber&&(e.groupNumber=t.push(null)-1),r="",i=0,s=(n=e.options?this._randSelect(e.options):e.stack).length;i{"use strict";var r=n(34155),o=65536,a=4294967295;var i=n(89509).Buffer,s=n.g.crypto||n.g.msCrypto;s&&s.getRandomValues?e.exports=function(e,t){if(e>a)throw new RangeError("requested too many random bytes");var n=i.allocUnsafe(e);if(e>0)if(e>o)for(var l=0;l{"use strict";function r(e){return r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},r(e)}Object.defineProperty(t,"__esModule",{value:!0}),t.CopyToClipboard=void 0;var o=s(n(67294)),a=s(n(20640)),i=["text","onCopy","options","children"];function s(e){return e&&e.__esModule?e:{default:e}}function l(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function u(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function p(e,t){for(var n=0;n{"use strict";var r=n(74300).CopyToClipboard;r.CopyToClipboard=r,e.exports=r},53441:(e,t,n)=>{"use strict";function r(e){return r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},r(e)}Object.defineProperty(t,"__esModule",{value:!0}),t.DebounceInput=void 0;var o=s(n(67294)),a=s(n(91296)),i=["element","onChange","value","minLength","debounceTimeout","forceNotifyByEnter","forceNotifyOnBlur","onKeyDown","onBlur","inputRef"];function s(e){return e&&e.__esModule?e:{default:e}}function l(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n,r,o={},a=Object.keys(e);for(r=0;r=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function u(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function c(e){for(var t=1;t=r?t.notify(e):n.length>o.length&&t.notify(c(c({},e),{},{target:c(c({},e.target),{},{value:""})}))}))})),g(d(t),"onKeyDown",(function(e){"Enter"===e.key&&t.forceNotify(e);var n=t.props.onKeyDown;n&&(e.persist(),n(e))})),g(d(t),"onBlur",(function(e){t.forceNotify(e);var n=t.props.onBlur;n&&(e.persist(),n(e))})),g(d(t),"createNotifier",(function(e){if(e<0)t.notify=function(){return null};else if(0===e)t.notify=t.doNotify;else{var n=(0,a.default)((function(e){t.isDebouncing=!1,t.doNotify(e)}),e);t.notify=function(e){t.isDebouncing=!0,n(e)},t.flush=function(){return n.flush()},t.cancel=function(){t.isDebouncing=!1,n.cancel()}}})),g(d(t),"doNotify",(function(){t.props.onChange.apply(void 0,arguments)})),g(d(t),"forceNotify",(function(e){var n=t.props.debounceTimeout;if(t.isDebouncing||!(n>0)){t.cancel&&t.cancel();var r=t.state.value,o=t.props.minLength;r.length>=o?t.doNotify(e):t.doNotify(c(c({},e),{},{target:c(c({},e.target),{},{value:r})}))}})),t.isDebouncing=!1,t.state={value:void 0===e.value||null===e.value?"":e.value};var n=t.props.debounceTimeout;return t.createNotifier(n),t}return t=u,(n=[{key:"componentDidUpdate",value:function(e){if(!this.isDebouncing){var t=this.props,n=t.value,r=t.debounceTimeout,o=e.debounceTimeout,a=e.value,i=this.state.value;void 0!==n&&a!==n&&i!==n&&this.setState({value:n}),r!==o&&this.createNotifier(r)}}},{key:"componentWillUnmount",value:function(){this.flush&&this.flush()}},{key:"render",value:function(){var e,t,n=this.props,r=n.element,a=(n.onChange,n.value,n.minLength,n.debounceTimeout,n.forceNotifyByEnter),s=n.forceNotifyOnBlur,u=n.onKeyDown,p=n.onBlur,f=n.inputRef,h=l(n,i),d=this.state.value;e=a?{onKeyDown:this.onKeyDown}:u?{onKeyDown:u}:{},t=s?{onBlur:this.onBlur}:p?{onBlur:p}:{};var m=f?{ref:f}:{};return o.default.createElement(r,c(c(c(c({},h),{},{onChange:this.onChange,value:d},e),t),m))}}])&&p(t.prototype,n),r&&p(t,r),Object.defineProperty(t,"prototype",{writable:!1}),u}(o.default.PureComponent);t.DebounceInput=y,g(y,"defaultProps",{element:"input",type:"text",onKeyDown:void 0,onBlur:void 0,value:void 0,minLength:0,debounceTimeout:100,forceNotifyByEnter:!0,forceNotifyOnBlur:!0,inputRef:void 0})},775:(e,t,n)=>{"use strict";var r=n(53441).DebounceInput;r.DebounceInput=r,e.exports=r},64448:(e,t,n)=>{"use strict";var r=n(67294),o=n(27418),a=n(63840);function i(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n
\n {examplesForMediaType ? (\n
\n \n Examples\n \n \n oas3Actions.setActiveExamplesMember({\n name: key,\n pathMethod: [path, method],\n contextType: \"responses\",\n contextName: code\n })\n }\n showLabels={false}\n />\n
\n ) : null}\n \n ) : null}\n\n { example || schema ? (\n \n ) : null }\n\n { isOAS3 && examplesForMediaType ? (\n \n ) : null}\n\n { headers ? (\n \n ) : null}\n\n \n {isOAS3 ? \n { links ?\n links.toSeq().entrySeq().map(([key, link]) => {\n return \n })\n : No links}\n : null}\n \n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport const ResponseExtension = ({ xKey, xVal }) => {\n return
{ xKey }: { String(xVal) }
\n}\nResponseExtension.propTypes = {\n xKey: PropTypes.string,\n xVal: PropTypes.any\n}\n\nexport default ResponseExtension\n","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_xml_but_prettier_2ed4d5cb__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_lodash_toLower_c29ee2b0__[\"default\"] });","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport formatXml from \"xml-but-prettier\"\nimport toLower from \"lodash/toLower\"\nimport { extractFileNameFromContentDispositionHeader } from \"core/utils\"\nimport { getKnownSyntaxHighlighterLanguage } from \"core/utils/jsonParse\"\nimport win from \"core/window\"\n\nexport default class ResponseBody extends React.PureComponent {\n state = {\n parsedContent: null\n }\n\n static propTypes = {\n content: PropTypes.any.isRequired,\n contentType: PropTypes.string,\n getConfigs: PropTypes.func.isRequired,\n getComponent: PropTypes.func.isRequired,\n headers: PropTypes.object,\n url: PropTypes.string\n }\n\n updateParsedContent = (prevContent) => {\n const { content } = this.props\n\n if(prevContent === content) {\n return\n }\n\n if(content && content instanceof Blob) {\n var reader = new FileReader()\n reader.onload = () => {\n this.setState({\n parsedContent: reader.result\n })\n }\n reader.readAsText(content)\n } else {\n this.setState({\n parsedContent: content.toString()\n })\n }\n }\n\n componentDidMount() {\n this.updateParsedContent(null)\n }\n\n componentDidUpdate(prevProps) {\n this.updateParsedContent(prevProps.content)\n }\n\n render() {\n let { content, contentType, url, headers={}, getConfigs, getComponent } = this.props\n const { parsedContent } = this.state\n const HighlightCode = getComponent(\"highlightCode\")\n const downloadName = \"response_\" + new Date().getTime()\n let body, bodyEl\n url = url || \"\"\n\n if (\n /^application\\/octet-stream/i.test(contentType) ||\n (headers[\"Content-Disposition\"] && (/attachment/i).test(headers[\"Content-Disposition\"])) ||\n (headers[\"content-disposition\"] && (/attachment/i).test(headers[\"content-disposition\"])) ||\n (headers[\"Content-Description\"] && (/File Transfer/i).test(headers[\"Content-Description\"])) ||\n (headers[\"content-description\"] && (/File Transfer/i).test(headers[\"content-description\"]))) {\n // Download\n\n if (\"Blob\" in window) {\n let type = contentType || \"text/html\"\n let blob = (content instanceof Blob) ? content : new Blob([content], {type: type})\n let href = window.URL.createObjectURL(blob)\n let fileName = url.substr(url.lastIndexOf(\"/\") + 1)\n let download = [type, fileName, href].join(\":\")\n\n // Use filename from response header,\n // First check if filename is quoted (e.g. contains space), if no, fallback to not quoted check\n let disposition = headers[\"content-disposition\"] || headers[\"Content-Disposition\"]\n if (typeof disposition !== \"undefined\") {\n let responseFilename = extractFileNameFromContentDispositionHeader(disposition)\n if (responseFilename !== null) {\n download = responseFilename\n }\n }\n\n if(win.navigator && win.navigator.msSaveOrOpenBlob) {\n bodyEl = \n } else {\n bodyEl = \n }\n } else {\n bodyEl =
Download headers detected but your browser does not support downloading binary via XHR (Blob).
\n }\n\n // Anything else (CORS)\n } else if (/json/i.test(contentType)) {\n // JSON\n let language = null\n let testValueForJson = getKnownSyntaxHighlighterLanguage(content)\n if (testValueForJson) {\n language = \"json\"\n }\n try {\n body = JSON.stringify(JSON.parse(content), null, \" \")\n } catch (error) {\n body = \"can't parse JSON. Raw result:\\n\\n\" + content\n }\n\n bodyEl = \n\n // XML\n } else if (/xml/i.test(contentType)) {\n body = formatXml(content, {\n textNodesOnSameLine: true,\n indentor: \" \"\n })\n bodyEl = \n\n // HTML or Plain Text\n } else if (toLower(contentType) === \"text/html\" || /text\\/plain/.test(contentType)) {\n bodyEl = \n\n // CSV\n } else if (toLower(contentType) === \"text/csv\" || /text\\/csv/.test(contentType)) {\n bodyEl = \n\n // Image\n } else if (/^image\\//i.test(contentType)) {\n if(contentType.includes(\"svg\")) {\n bodyEl =
{ content }
\n } else {\n bodyEl = \n }\n\n // Audio\n } else if (/^audio\\//i.test(contentType)) {\n bodyEl =
\n } else if (typeof content === \"string\") {\n bodyEl = \n } else if ( content.size > 0 ) {\n // We don't know the contentType, but there was some content returned\n if(parsedContent) {\n // We were able to squeeze something out of content\n // in `updateParsedContent`, so let's display it\n bodyEl =
\n

\n Unrecognized response type; displaying content as text.\n

\n \n
\n\n } else {\n // Give up\n bodyEl =

\n Unrecognized response type; unable to display.\n

\n }\n } else {\n // We don't know the contentType and there was no content returned\n bodyEl = null\n }\n\n return ( !bodyEl ? null :
\n
Response body
\n { bodyEl }\n
\n )\n }\n}\n","import React, { Component } from \"react\"\nimport PropTypes from \"prop-types\"\nimport { Map, List } from \"immutable\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nexport default class Parameters extends Component {\n\n constructor(props) {\n super(props)\n this.state = {\n callbackVisible: false,\n parametersVisible: true,\n }\n }\n\n static propTypes = {\n parameters: ImPropTypes.list.isRequired,\n operation: PropTypes.object.isRequired,\n specActions: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n specSelectors: PropTypes.object.isRequired,\n oas3Actions: PropTypes.object.isRequired,\n oas3Selectors: PropTypes.object.isRequired,\n fn: PropTypes.object.isRequired,\n tryItOutEnabled: PropTypes.bool,\n allowTryItOut: PropTypes.bool,\n onTryoutClick: PropTypes.func,\n onResetClick: PropTypes.func,\n onCancelClick: PropTypes.func,\n onChangeKey: PropTypes.array,\n pathMethod: PropTypes.array.isRequired,\n getConfigs: PropTypes.func.isRequired,\n specPath: ImPropTypes.list.isRequired,\n }\n\n\n static defaultProps = {\n onTryoutClick: Function.prototype,\n onCancelClick: Function.prototype,\n tryItOutEnabled: false,\n allowTryItOut: true,\n onChangeKey: [],\n specPath: [],\n }\n\n onChange = (param, value, isXml) => {\n let {\n specActions: { changeParamByIdentity },\n onChangeKey,\n } = this.props\n\n changeParamByIdentity(onChangeKey, param, value, isXml)\n }\n\n onChangeConsumesWrapper = (val) => {\n let {\n specActions: { changeConsumesValue },\n onChangeKey,\n } = this.props\n\n changeConsumesValue(onChangeKey, val)\n }\n\n toggleTab = (tab) => {\n if (tab === \"parameters\") {\n return this.setState({\n parametersVisible: true,\n callbackVisible: false,\n })\n } else if (tab === \"callbacks\") {\n return this.setState({\n callbackVisible: true,\n parametersVisible: false,\n })\n }\n }\n \n onChangeMediaType = ({ value, pathMethod }) => {\n let { specActions, oas3Selectors, oas3Actions } = this.props\n const userHasEditedBody = oas3Selectors.hasUserEditedBody(...pathMethod)\n const shouldRetainRequestBodyValue = oas3Selectors.shouldRetainRequestBodyValue(...pathMethod)\n oas3Actions.setRequestContentType({ value, pathMethod })\n oas3Actions.initRequestBodyValidateError({ pathMethod })\n if (!userHasEditedBody) {\n if(!shouldRetainRequestBodyValue) {\n oas3Actions.setRequestBodyValue({ value: undefined, pathMethod })\n }\n specActions.clearResponse(...pathMethod)\n specActions.clearRequest(...pathMethod)\n specActions.clearValidateParams(pathMethod)\n }\n }\n\n render() {\n\n let {\n onTryoutClick,\n onResetClick,\n parameters,\n allowTryItOut,\n tryItOutEnabled,\n specPath,\n fn,\n getComponent,\n getConfigs,\n specSelectors,\n specActions,\n pathMethod,\n oas3Actions,\n oas3Selectors,\n operation,\n } = this.props\n\n const ParameterRow = getComponent(\"parameterRow\")\n const TryItOutButton = getComponent(\"TryItOutButton\")\n const ContentType = getComponent(\"contentType\")\n const Callbacks = getComponent(\"Callbacks\", true)\n const RequestBody = getComponent(\"RequestBody\", true)\n\n const isExecute = tryItOutEnabled && allowTryItOut\n const isOAS3 = specSelectors.isOAS3()\n\n\n const requestBody = operation.get(\"requestBody\")\n\n const groupedParametersArr = Object.values(parameters\n .reduce((acc, x) => {\n const key = x.get(\"in\")\n acc[key] ??= []\n acc[key].push(x)\n return acc\n }, {}))\n .reduce((acc, x) => acc.concat(x), [])\n\n const retainRequestBodyValueFlagForOperation = (f) => oas3Actions.setRetainRequestBodyValueFlag({ value: f, pathMethod })\n return (\n
\n
\n {isOAS3 ? (\n
\n
this.toggleTab(\"parameters\")}\n className={`tab-item ${this.state.parametersVisible && \"active\"}`}>\n

Parameters

\n
\n {operation.get(\"callbacks\") ?\n (\n
this.toggleTab(\"callbacks\")}\n className={`tab-item ${this.state.callbackVisible && \"active\"}`}>\n

Callbacks

\n
\n ) : null\n }\n
\n ) : (\n
\n

Parameters

\n
\n )}\n {allowTryItOut ? (\n onResetClick(pathMethod)}/>\n ) : null}\n
\n {this.state.parametersVisible ?
\n {!groupedParametersArr.length ?

No parameters

:\n
\n \n \n \n \n \n \n \n \n {\n groupedParametersArr.map((parameter, i) => (\n \n ))\n }\n \n
NameDescription
\n
\n }\n
: null}\n\n {this.state.callbackVisible ?
\n \n
: null}\n {\n isOAS3 && requestBody && this.state.parametersVisible &&\n
\n
\n

Request\n body

\n \n
\n
\n {\n this.props.oas3Actions.setActiveExamplesMember({\n name: key,\n pathMethod: this.props.pathMethod,\n contextType: \"requestBody\",\n contextName: \"requestBody\", // RBs are currently not stored per-mediaType\n })\n }\n }\n onChange={(value, path) => {\n if (path) {\n const lastValue = oas3Selectors.requestBodyValue(...pathMethod)\n const usableValue = Map.isMap(lastValue) ? lastValue : Map()\n return oas3Actions.setRequestBodyValue({\n pathMethod,\n value: usableValue.setIn(path, value),\n })\n }\n oas3Actions.setRequestBodyValue({ value, pathMethod })\n }}\n onChangeIncludeEmpty={(name, value) => {\n oas3Actions.setRequestBodyInclusion({\n pathMethod,\n value,\n name,\n })\n }}\n contentType={oas3Selectors.requestContentType(...pathMethod)} />\n
\n
\n }\n
\n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport const ParameterExt = ({ xKey, xVal }) => {\n return
{ xKey }: { String(xVal) }
\n}\nParameterExt.propTypes = {\n xKey: PropTypes.string,\n xVal: PropTypes.any\n}\n\nexport default ParameterExt\n","import React, { Component } from \"react\"\nimport cx from \"classnames\"\nimport PropTypes from \"prop-types\"\n\n\nconst noop = () => { }\n\nconst ParameterIncludeEmptyPropTypes = {\n isIncluded: PropTypes.bool.isRequired,\n isDisabled: PropTypes.bool.isRequired,\n isIncludedOptions: PropTypes.object,\n onChange: PropTypes.func.isRequired,\n}\n\nconst ParameterIncludeEmptyDefaultProps = {\n onChange: noop,\n isIncludedOptions: {},\n}\nexport default class ParameterIncludeEmpty extends Component {\n static propTypes = ParameterIncludeEmptyPropTypes\n static defaultProps = ParameterIncludeEmptyDefaultProps\n\n componentDidMount() {\n const { isIncludedOptions, onChange } = this.props\n const { shouldDispatchInit, defaultValue } = isIncludedOptions\n if (shouldDispatchInit) {\n onChange(defaultValue)\n }\n }\n\n onCheckboxChange = e => {\n const { onChange } = this.props\n onChange(e.target.checked)\n }\n\n render() {\n let { isIncluded, isDisabled } = this.props\n\n return (\n
\n \n
\n )\n }\n}\n","import React, { Component } from \"react\"\nimport { Map, List } from \"immutable\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport win from \"core/window\"\nimport { getSampleSchema, getExtensions, getCommonExtensions, numberToString, stringify, isEmptyValue } from \"core/utils\"\nimport getParameterSchema from \"../../helpers/get-parameter-schema.js\"\n\nexport default class ParameterRow extends Component {\n static propTypes = {\n onChange: PropTypes.func.isRequired,\n param: PropTypes.object.isRequired,\n rawParam: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n fn: PropTypes.object.isRequired,\n isExecute: PropTypes.bool,\n onChangeConsumes: PropTypes.func.isRequired,\n specSelectors: PropTypes.object.isRequired,\n specActions: PropTypes.object.isRequired,\n pathMethod: PropTypes.array.isRequired,\n getConfigs: PropTypes.func.isRequired,\n specPath: ImPropTypes.list.isRequired,\n oas3Actions: PropTypes.object.isRequired,\n oas3Selectors: PropTypes.object.isRequired,\n }\n\n constructor(props, context) {\n super(props, context)\n\n this.setDefaultValue()\n }\n\n UNSAFE_componentWillReceiveProps(props) {\n let { specSelectors, pathMethod, rawParam } = props\n let isOAS3 = specSelectors.isOAS3()\n\n let parameterWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || new Map()\n // fallback, if the meta lookup fails\n parameterWithMeta = parameterWithMeta.isEmpty() ? rawParam : parameterWithMeta\n\n let enumValue\n\n if(isOAS3) {\n let { schema } = getParameterSchema(parameterWithMeta, { isOAS3 })\n enumValue = schema ? schema.get(\"enum\") : undefined\n } else {\n enumValue = parameterWithMeta ? parameterWithMeta.get(\"enum\") : undefined\n }\n let paramValue = parameterWithMeta ? parameterWithMeta.get(\"value\") : undefined\n\n let value\n\n if ( paramValue !== undefined ) {\n value = paramValue\n } else if ( rawParam.get(\"required\") && enumValue && enumValue.size ) {\n value = enumValue.first()\n }\n\n if ( value !== undefined && value !== paramValue ) {\n this.onChangeWrapper(numberToString(value))\n }\n // todo: could check if schema here; if not, do not call. impact?\n this.setDefaultValue()\n }\n\n onChangeWrapper = (value, isXml = false) => {\n let { onChange, rawParam } = this.props\n let valueForUpstream\n\n // Coerce empty strings and empty Immutable objects to null\n if(value === \"\" || (value && value.size === 0)) {\n valueForUpstream = null\n } else {\n valueForUpstream = value\n }\n\n return onChange(rawParam, valueForUpstream, isXml)\n }\n\n _onExampleSelect = (key, /* { isSyntheticChange } = {} */) => {\n this.props.oas3Actions.setActiveExamplesMember({\n name: key,\n pathMethod: this.props.pathMethod,\n contextType: \"parameters\",\n contextName: this.getParamKey()\n })\n }\n\n onChangeIncludeEmpty = (newValue) => {\n let { specActions, param, pathMethod } = this.props\n const paramName = param.get(\"name\")\n const paramIn = param.get(\"in\")\n return specActions.updateEmptyParamInclusion(pathMethod, paramName, paramIn, newValue)\n }\n\n setDefaultValue = () => {\n let { specSelectors, pathMethod, rawParam, oas3Selectors } = this.props\n\n const paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || Map()\n const { schema } = getParameterSchema(paramWithMeta, { isOAS3: specSelectors.isOAS3() })\n const parameterMediaType = paramWithMeta\n .get(\"content\", Map())\n .keySeq()\n .first()\n\n // getSampleSchema could return null\n const generatedSampleValue = schema ? getSampleSchema(schema.toJS(), parameterMediaType, {\n\n includeWriteOnly: true\n }) : null\n\n if (!paramWithMeta || paramWithMeta.get(\"value\") !== undefined) {\n return\n }\n\n if( paramWithMeta.get(\"in\") !== \"body\" ) {\n let initialValue\n\n //// Find an initial value\n\n if (specSelectors.isSwagger2()) {\n initialValue =\n paramWithMeta.get(\"x-example\") !== undefined\n ? paramWithMeta.get(\"x-example\")\n : paramWithMeta.getIn([\"schema\", \"example\"]) !== undefined\n ? paramWithMeta.getIn([\"schema\", \"example\"])\n : (schema && schema.getIn([\"default\"]))\n } else if (specSelectors.isOAS3()) {\n const currentExampleKey = oas3Selectors.activeExamplesMember(...pathMethod, \"parameters\", this.getParamKey())\n initialValue =\n paramWithMeta.getIn([\"examples\", currentExampleKey, \"value\"]) !== undefined\n ? paramWithMeta.getIn([\"examples\", currentExampleKey, \"value\"])\n : paramWithMeta.getIn([\"content\", parameterMediaType, \"example\"]) !== undefined\n ? paramWithMeta.getIn([\"content\", parameterMediaType, \"example\"])\n : paramWithMeta.get(\"example\") !== undefined\n ? paramWithMeta.get(\"example\")\n : (schema && schema.get(\"example\")) !== undefined\n ? (schema && schema.get(\"example\"))\n : (schema && schema.get(\"default\")) !== undefined\n ? (schema && schema.get(\"default\"))\n : paramWithMeta.get(\"default\") // ensures support for `parameterMacro`\n }\n\n //// Process the initial value\n\n if(initialValue !== undefined && !List.isList(initialValue)) {\n // Stringify if it isn't a List\n initialValue = stringify(initialValue)\n }\n\n //// Dispatch the initial value\n\n if(initialValue !== undefined) {\n this.onChangeWrapper(initialValue)\n } else if(\n schema && schema.get(\"type\") === \"object\"\n && generatedSampleValue\n && !paramWithMeta.get(\"examples\")\n ) {\n // Object parameters get special treatment.. if the user doesn't set any\n // default or example values, we'll provide initial values generated from\n // the schema.\n // However, if `examples` exist for the parameter, we won't do anything,\n // so that the appropriate `examples` logic can take over.\n this.onChangeWrapper(\n List.isList(generatedSampleValue) ? (\n generatedSampleValue\n ) : (\n stringify(generatedSampleValue)\n )\n )\n }\n }\n }\n\n getParamKey() {\n const { param } = this.props\n\n if(!param) return null\n\n return `${param.get(\"name\")}-${param.get(\"in\")}`\n }\n\n render() {\n let {param, rawParam, getComponent, getConfigs, isExecute, fn, onChangeConsumes, specSelectors, pathMethod, specPath, oas3Selectors} = this.props\n\n let isOAS3 = specSelectors.isOAS3()\n\n const { showExtensions, showCommonExtensions } = getConfigs()\n\n if(!param) {\n param = rawParam\n }\n\n if(!rawParam) return null\n\n // const onChangeWrapper = (value) => onChange(param, value)\n const JsonSchemaForm = getComponent(\"JsonSchemaForm\")\n const ParamBody = getComponent(\"ParamBody\")\n let inType = param.get(\"in\")\n let bodyParam = inType !== \"body\" ? null\n : \n\n const ModelExample = getComponent(\"modelExample\")\n const Markdown = getComponent(\"Markdown\", true)\n const ParameterExt = getComponent(\"ParameterExt\")\n const ParameterIncludeEmpty = getComponent(\"ParameterIncludeEmpty\")\n const ExamplesSelectValueRetainer = getComponent(\"ExamplesSelectValueRetainer\")\n const Example = getComponent(\"Example\")\n\n let { schema } = getParameterSchema(param, { isOAS3 })\n let paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || Map()\n\n let format = schema ? schema.get(\"format\") : null\n let type = schema ? schema.get(\"type\") : null\n let itemType = schema ? schema.getIn([\"items\", \"type\"]) : null\n let isFormData = inType === \"formData\"\n let isFormDataSupported = \"FormData\" in win\n let required = param.get(\"required\")\n\n let value = paramWithMeta ? paramWithMeta.get(\"value\") : \"\"\n let commonExt = showCommonExtensions ? getCommonExtensions(schema) : null\n let extensions = showExtensions ? getExtensions(param) : null\n\n let paramItems // undefined\n let paramEnum // undefined\n let paramDefaultValue // undefined\n let paramExample // undefined\n let isDisplayParamEnum = false\n\n if ( param !== undefined && schema ) {\n paramItems = schema.get(\"items\")\n }\n\n if (paramItems !== undefined) {\n paramEnum = paramItems.get(\"enum\")\n paramDefaultValue = paramItems.get(\"default\")\n } else if (schema) {\n paramEnum = schema.get(\"enum\")\n }\n\n if ( paramEnum && paramEnum.size && paramEnum.size > 0) {\n isDisplayParamEnum = true\n }\n\n // Default and Example Value for readonly doc\n if ( param !== undefined ) {\n if (schema) {\n paramDefaultValue = schema.get(\"default\")\n }\n if (paramDefaultValue === undefined) {\n paramDefaultValue = param.get(\"default\")\n }\n paramExample = param.get(\"example\")\n if (paramExample === undefined) {\n paramExample = param.get(\"x-example\")\n }\n }\n\n return (\n \n \n
\n { param.get(\"name\") }\n { !required ? null :  * }\n
\n
\n { type }\n { itemType && `[${itemType}]` }\n { format && (${format})}\n
\n
\n { isOAS3 && param.get(\"deprecated\") ? \"deprecated\": null }\n
\n
({ param.get(\"in\") })
\n { !showCommonExtensions || !commonExt.size ? null : commonExt.entrySeq().map(([key, v]) => )}\n { !showExtensions || !extensions.size ? null : extensions.entrySeq().map(([key, v]) => )}\n \n\n \n { param.get(\"description\") ? : null }\n\n { (bodyParam || !isExecute) && isDisplayParamEnum ?\n Available values : \" + paramEnum.map(function(item) {\n return item\n }).toArray().join(\", \")}/>\n : null\n }\n\n { (bodyParam || !isExecute) && paramDefaultValue !== undefined ?\n Default value : \" + paramDefaultValue}/>\n : null\n }\n\n { (bodyParam || !isExecute) && paramExample !== undefined ?\n Example : \" + paramExample}/>\n : null\n }\n\n {(isFormData && !isFormDataSupported) &&
Error: your browser does not support FormData
}\n\n {\n isOAS3 && param.get(\"examples\") ? (\n
\n \n
\n ) : null\n }\n\n { bodyParam ? null\n : \n }\n\n\n {\n bodyParam && schema ? \n : null\n }\n\n {\n !bodyParam && isExecute && param.get(\"allowEmptyValue\") ?\n \n : null\n }\n\n {\n isOAS3 && param.get(\"examples\") ? (\n \n ) : null\n }\n\n \n\n \n )\n\n }\n\n}\n","import React, { Component } from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class Execute extends Component {\n\n static propTypes = {\n specSelectors: PropTypes.object.isRequired,\n specActions: PropTypes.object.isRequired,\n operation: PropTypes.object.isRequired,\n path: PropTypes.string.isRequired,\n method: PropTypes.string.isRequired,\n oas3Selectors: PropTypes.object.isRequired,\n oas3Actions: PropTypes.object.isRequired,\n onExecute: PropTypes.func,\n disabled: PropTypes.bool\n }\n\n handleValidateParameters = () => {\n let { specSelectors, specActions, path, method } = this.props\n specActions.validateParams([path, method])\n return specSelectors.validateBeforeExecute([path, method])\n }\n\n handleValidateRequestBody = () => {\n let { path, method, specSelectors, oas3Selectors, oas3Actions } = this.props\n let validationErrors = {\n missingBodyValue: false,\n missingRequiredKeys: []\n }\n // context: reset errors, then (re)validate\n oas3Actions.clearRequestBodyValidateError({ path, method })\n let oas3RequiredRequestBodyContentType = specSelectors.getOAS3RequiredRequestBodyContentType([path, method])\n let oas3RequestBodyValue = oas3Selectors.requestBodyValue(path, method)\n let oas3ValidateBeforeExecuteSuccess = oas3Selectors.validateBeforeExecute([path, method])\n let oas3RequestContentType = oas3Selectors.requestContentType(path, method)\n\n if (!oas3ValidateBeforeExecuteSuccess) {\n validationErrors.missingBodyValue = true\n oas3Actions.setRequestBodyValidateError({ path, method, validationErrors })\n return false\n }\n if (!oas3RequiredRequestBodyContentType) {\n return true\n }\n let missingRequiredKeys = oas3Selectors.validateShallowRequired({\n oas3RequiredRequestBodyContentType,\n oas3RequestContentType,\n oas3RequestBodyValue\n })\n if (!missingRequiredKeys || missingRequiredKeys.length < 1) {\n return true\n }\n missingRequiredKeys.forEach((missingKey) => {\n validationErrors.missingRequiredKeys.push(missingKey)\n })\n oas3Actions.setRequestBodyValidateError({ path, method, validationErrors })\n return false\n }\n\n handleValidationResultPass = () => {\n let { specActions, operation, path, method } = this.props\n if (this.props.onExecute) {\n // loading spinner\n this.props.onExecute()\n }\n specActions.execute({ operation, path, method })\n }\n\n handleValidationResultFail = () => {\n let { specActions, path, method } = this.props\n // deferred by 40ms, to give element class change time to settle.\n specActions.clearValidateParams([path, method])\n setTimeout(() => {\n specActions.validateParams([path, method])\n }, 40)\n }\n\n handleValidationResult = (isPass) => {\n if (isPass) {\n this.handleValidationResultPass()\n } else {\n this.handleValidationResultFail()\n }\n }\n\n onClick = () => {\n let paramsResult = this.handleValidateParameters()\n let requestBodyResult = this.handleValidateRequestBody()\n let isPass = paramsResult && requestBodyResult\n this.handleValidationResult(isPass)\n }\n\n onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue([this.props.path, this.props.method], val)\n\n render(){\n const { disabled } = this.props\n return (\n \n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport Im from \"immutable\"\n\nconst propClass = \"header-example\"\n\nexport default class Headers extends React.Component {\n static propTypes = {\n headers: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired\n }\n\n render() {\n let { headers, getComponent } = this.props\n\n const Property = getComponent(\"Property\")\n const Markdown = getComponent(\"Markdown\", true)\n\n if ( !headers || !headers.size )\n return null\n\n return (\n
\n

Headers:

\n \n \n \n \n \n \n \n \n \n {\n headers.entrySeq().map( ([ key, header ]) => {\n if(!Im.Map.isMap(header)) {\n return null\n }\n\n const description = header.get(\"description\")\n const type = header.getIn([\"schema\"]) ? header.getIn([\"schema\", \"type\"]) : header.getIn([\"type\"])\n const schemaExample = header.getIn([\"schema\", \"example\"])\n\n return (\n \n \n \n )\n }).toArray()\n }\n \n
NameDescriptionType
{ key }{\n !description ? null : \n }{ type } { schemaExample ? : null }
\n
\n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport { List } from \"immutable\"\n\nexport default class Errors extends React.Component {\n\n static propTypes = {\n editorActions: PropTypes.object,\n errSelectors: PropTypes.object.isRequired,\n layoutSelectors: PropTypes.object.isRequired,\n layoutActions: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n }\n\n render() {\n let { editorActions, errSelectors, layoutSelectors, layoutActions, getComponent } = this.props\n\n const Collapse = getComponent(\"Collapse\")\n\n if(editorActions && editorActions.jumpToLine) {\n var jumpToLine = editorActions.jumpToLine\n }\n\n let errors = errSelectors.allErrors()\n\n // all thrown errors, plus error-level everything else\n let allErrorsToDisplay = errors.filter(err => err.get(\"type\") === \"thrown\" ? true :err.get(\"level\") === \"error\")\n\n if(!allErrorsToDisplay || allErrorsToDisplay.count() < 1) {\n return null\n }\n\n let isVisible = layoutSelectors.isShown([\"errorPane\"], true)\n let toggleVisibility = () => layoutActions.show([\"errorPane\"], !isVisible)\n\n let sortedJSErrors = allErrorsToDisplay.sortBy(err => err.get(\"line\"))\n\n return (\n
\n        
\n

Errors

\n \n
\n \n
\n { sortedJSErrors.map((err, i) => {\n let type = err.get(\"type\")\n if(type === \"thrown\" || type === \"auth\") {\n return \n }\n if(type === \"spec\") {\n return \n }\n }) }\n
\n
\n
\n )\n }\n}\n\nconst ThrownErrorItem = ( { error, jumpToLine } ) => {\n if(!error) {\n return null\n }\n let errorLine = error.get(\"line\")\n\n return (\n
\n { !error ? null :\n
\n

{ (error.get(\"source\") && error.get(\"level\")) ?\n toTitleCase(error.get(\"source\")) + \" \" + error.get(\"level\") : \"\" }\n { error.get(\"path\") ? at {error.get(\"path\")}: null }

\n \n { error.get(\"message\") }\n \n
\n { errorLine && jumpToLine ? Jump to line { errorLine } : null }\n
\n
\n }\n
\n )\n }\n\nconst SpecErrorItem = ( { error, jumpToLine } ) => {\n let locationMessage = null\n\n if(error.get(\"path\")) {\n if(List.isList(error.get(\"path\"))) {\n locationMessage = at { error.get(\"path\").join(\".\") }\n } else {\n locationMessage = at { error.get(\"path\") }\n }\n } else if(error.get(\"line\") && !jumpToLine) {\n locationMessage = on line { error.get(\"line\") }\n }\n\n return (\n
\n { !error ? null :\n
\n

{ toTitleCase(error.get(\"source\")) + \" \" + error.get(\"level\") } { locationMessage }

\n { error.get(\"message\") }\n
\n { jumpToLine ? (\n Jump to line { error.get(\"line\") }\n ) : null }\n
\n
\n }\n
\n )\n }\n\nfunction toTitleCase(str) {\n return (str || \"\")\n .split(\" \")\n .map(substr => substr[0].toUpperCase() + substr.slice(1))\n .join(\" \")\n}\n\nThrownErrorItem.propTypes = {\n error: PropTypes.object.isRequired,\n jumpToLine: PropTypes.func\n}\n\nThrownErrorItem.defaultProps = {\n jumpToLine: null\n}\n\nSpecErrorItem.propTypes = {\n error: PropTypes.object.isRequired,\n jumpToLine: PropTypes.func\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport { fromJS } from \"immutable\"\n\nconst noop = ()=>{}\n\nexport default class ContentType extends React.Component {\n\n static propTypes = {\n ariaControls: PropTypes.string,\n contentTypes: PropTypes.oneOfType([ImPropTypes.list, ImPropTypes.set, ImPropTypes.seq]),\n controlId: PropTypes.string,\n value: PropTypes.string,\n onChange: PropTypes.func,\n className: PropTypes.string,\n ariaLabel: PropTypes.string\n }\n\n static defaultProps = {\n onChange: noop,\n value: null,\n contentTypes: fromJS([\"application/json\"]),\n }\n\n componentDidMount() {\n // Needed to populate the form, initially\n if(this.props.contentTypes) {\n this.props.onChange(this.props.contentTypes.first())\n }\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n if(!nextProps.contentTypes || !nextProps.contentTypes.size) {\n return\n }\n\n if(!nextProps.contentTypes.includes(nextProps.value)) {\n nextProps.onChange(nextProps.contentTypes.first())\n }\n }\n\n onChangeWrapper = e => this.props.onChange(e.target.value)\n\n render() {\n let { ariaControls, ariaLabel, className, contentTypes, controlId, value } = this.props\n\n if ( !contentTypes || !contentTypes.size )\n return null\n\n return (\n
\n \n
\n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nfunction xclass(...args) {\n return args.filter(a => !!a).join(\" \").trim()\n}\n\nexport class Container extends React.Component {\n render() {\n let { fullscreen, full, ...rest } = this.props\n // Normal element\n\n if(fullscreen)\n return
\n\n let containerClass = \"swagger-container\" + (full ? \"-full\" : \"\")\n return (\n
\n )\n }\n}\n\nContainer.propTypes = {\n fullscreen: PropTypes.bool,\n full: PropTypes.bool,\n className: PropTypes.string\n}\n\nconst DEVICES = {\n \"mobile\": \"\",\n \"tablet\": \"-tablet\",\n \"desktop\": \"-desktop\",\n \"large\": \"-hd\"\n}\n\nexport class Col extends React.Component {\n\n render() {\n const {\n hide,\n keepContents,\n /* we don't want these in the `rest` object that passes to the final component,\n since React now complains. So we extract them */\n /* eslint-disable no-unused-vars */\n mobile,\n tablet,\n desktop,\n large,\n /* eslint-enable no-unused-vars */\n ...rest\n } = this.props\n\n if(hide && !keepContents)\n return \n\n let classesAr = []\n\n for (let device in DEVICES) {\n if (!Object.prototype.hasOwnProperty.call(DEVICES, device)) {\n continue\n }\n let deviceClass = DEVICES[device]\n if(device in this.props) {\n let val = this.props[device]\n\n if(val < 1) {\n classesAr.push(\"none\" + deviceClass)\n continue\n }\n\n classesAr.push(\"block\" + deviceClass)\n classesAr.push(\"col-\" + val + deviceClass)\n }\n }\n\n if (hide) {\n classesAr.push(\"hidden\")\n }\n\n let classes = xclass(rest.className, ...classesAr)\n\n return (\n
\n )\n }\n\n}\n\nCol.propTypes = {\n hide: PropTypes.bool,\n keepContents: PropTypes.bool,\n mobile: PropTypes.number,\n tablet: PropTypes.number,\n desktop: PropTypes.number,\n large: PropTypes.number,\n className: PropTypes.string\n}\n\nexport class Row extends React.Component {\n\n render() {\n return
\n }\n\n}\n\nRow.propTypes = {\n className: PropTypes.string\n}\n\nexport class Button extends React.Component {\n\n static propTypes = {\n className: PropTypes.string\n }\n\n static defaultProps = {\n className: \"\"\n }\n\n render() {\n return
\n
\n {curlBlock}\n
\n
\n )\n }\n\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class Schemes extends React.Component {\n\n static propTypes = {\n specActions: PropTypes.object.isRequired,\n schemes: PropTypes.object.isRequired,\n currentScheme: PropTypes.string.isRequired,\n path: PropTypes.string,\n method: PropTypes.string,\n }\n\n UNSAFE_componentWillMount() {\n let { schemes } = this.props\n\n //fire 'change' event to set default 'value' of select\n this.setScheme(schemes.first())\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n if ( !this.props.currentScheme || !nextProps.schemes.includes(this.props.currentScheme) ) {\n // if we don't have a selected currentScheme or if our selected scheme is no longer an option,\n // then fire 'change' event and select the first scheme in the list of options\n this.setScheme(nextProps.schemes.first())\n }\n }\n\n onChange =( e ) => {\n this.setScheme( e.target.value )\n }\n\n setScheme = ( value ) => {\n let { path, method, specActions } = this.props\n\n specActions.setScheme( value, path, method )\n }\n\n render() {\n let { schemes, currentScheme } = this.props\n\n return (\n \n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class SchemesContainer extends React.Component {\n\n static propTypes = {\n specActions: PropTypes.object.isRequired,\n specSelectors: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired\n }\n\n render () {\n const {specActions, specSelectors, getComponent} = this.props\n\n const currentScheme = specSelectors.operationScheme()\n const schemes = specSelectors.schemes()\n\n const Schemes = getComponent(\"schemes\")\n\n const schemesArePresent = schemes && schemes.size\n\n return schemesArePresent ? (\n \n ) : null\n }\n}\n","import React, { Component } from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport Im from \"immutable\"\n\nexport default class ModelCollapse extends Component {\n static propTypes = {\n collapsedContent: PropTypes.any,\n expanded: PropTypes.bool,\n children: PropTypes.any,\n title: PropTypes.element,\n modelName: PropTypes.string,\n classes: PropTypes.string,\n onToggle: PropTypes.func,\n hideSelfOnExpand: PropTypes.bool,\n layoutActions: PropTypes.object,\n layoutSelectors: PropTypes.object.isRequired,\n specPath: ImPropTypes.list.isRequired,\n }\n\n static defaultProps = {\n collapsedContent: \"{...}\",\n expanded: false,\n title: null,\n onToggle: () => {},\n hideSelfOnExpand: false,\n specPath: Im.List([]),\n }\n\n constructor(props, context) {\n super(props, context)\n\n let { expanded, collapsedContent } = this.props\n\n this.state = {\n expanded : expanded,\n collapsedContent: collapsedContent || ModelCollapse.defaultProps.collapsedContent\n }\n }\n\n componentDidMount() {\n const { hideSelfOnExpand, expanded, modelName } = this.props\n if(hideSelfOnExpand && expanded) {\n // We just mounted pre-expanded, and we won't be going back..\n // So let's give our parent an `onToggle` call..\n // Since otherwise it will never be called.\n this.props.onToggle(modelName, expanded)\n }\n }\n\n UNSAFE_componentWillReceiveProps(nextProps){\n if(this.props.expanded !== nextProps.expanded){\n this.setState({expanded: nextProps.expanded})\n }\n }\n\n toggleCollapsed=()=>{\n if(this.props.onToggle){\n this.props.onToggle(this.props.modelName,!this.state.expanded)\n }\n\n this.setState({\n expanded: !this.state.expanded\n })\n }\n\n onLoad = (ref) => {\n if (ref && this.props.layoutSelectors) {\n const scrollToKey = this.props.layoutSelectors.getScrollToKey()\n\n if( Im.is(scrollToKey, this.props.specPath) ) this.toggleCollapsed()\n this.props.layoutActions.readyToScroll(this.props.specPath, ref.parentElement)\n }\n }\n\n render () {\n const { title, classes } = this.props\n\n if(this.state.expanded ) {\n if(this.props.hideSelfOnExpand) {\n return \n {this.props.children}\n \n }\n }\n\n return (\n \n \n\n { this.state.expanded && this.props.children }\n \n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport cx from \"classnames\"\nimport randomBytes from \"randombytes\"\n\nexport default class ModelExample extends React.Component {\n static propTypes = {\n getComponent: PropTypes.func.isRequired,\n specSelectors: PropTypes.object.isRequired,\n schema: PropTypes.object.isRequired,\n example: PropTypes.any.isRequired,\n isExecute: PropTypes.bool,\n getConfigs: PropTypes.func.isRequired,\n specPath: ImPropTypes.list.isRequired,\n includeReadOnly: PropTypes.bool,\n includeWriteOnly: PropTypes.bool,\n }\n\n constructor(props, context) {\n super(props, context)\n let { getConfigs, isExecute } = this.props\n let { defaultModelRendering } = getConfigs()\n\n let activeTab = defaultModelRendering\n\n if (defaultModelRendering !== \"example\" && defaultModelRendering !== \"model\") {\n activeTab = \"example\"\n }\n\n if(isExecute) {\n activeTab = \"example\"\n }\n\n this.state = {\n activeTab,\n }\n }\n\n activeTab = ( e ) => {\n let { target : { dataset : { name } } } = e\n\n this.setState({\n activeTab: name\n })\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n if (\n nextProps.isExecute &&\n !this.props.isExecute &&\n this.props.example\n ) {\n this.setState({ activeTab: \"example\" })\n }\n }\n\n render() {\n let { getComponent, specSelectors, schema, example, isExecute, getConfigs, specPath, includeReadOnly, includeWriteOnly } = this.props\n let { defaultModelExpandDepth } = getConfigs()\n const ModelWrapper = getComponent(\"ModelWrapper\")\n const HighlightCode = getComponent(\"highlightCode\")\n const exampleTabId = randomBytes(5).toString(\"base64\")\n const examplePanelId = randomBytes(5).toString(\"base64\")\n const modelTabId = randomBytes(5).toString(\"base64\")\n const modelPanelId = randomBytes(5).toString(\"base64\")\n\n let isOAS3 = specSelectors.isOAS3()\n\n return (\n
\n
    \n
  • \n \n {isExecute ? \"Edit Value\" : \"Example Value\"}\n \n
  • \n { schema && (\n
  • \n \n {isOAS3 ? \"Schema\" : \"Model\" }\n \n
  • \n )}\n
\n {this.state.activeTab === \"example\" && (\n \n {example ? example : (\n \n )}\n
\n )}\n\n {this.state.activeTab === \"model\" && (\n \n \n \n )}\n \n )\n }\n\n}\n","import React, { Component, } from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nexport default class ModelWrapper extends Component {\n\n static propTypes = {\n schema: PropTypes.object.isRequired,\n name: PropTypes.string,\n displayName: PropTypes.string,\n fullPath: PropTypes.array.isRequired,\n specPath: ImPropTypes.list.isRequired,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n specSelectors: PropTypes.object.isRequired,\n expandDepth: PropTypes.number,\n layoutActions: PropTypes.object,\n layoutSelectors: PropTypes.object.isRequired,\n includeReadOnly: PropTypes.bool,\n includeWriteOnly: PropTypes.bool,\n }\n\n onToggle = (name,isShown) => {\n // If this prop is present, we'll have deepLinking for it\n if(this.props.layoutActions) {\n this.props.layoutActions.show(this.props.fullPath, isShown)\n }\n }\n\n render(){\n let { getComponent, getConfigs } = this.props\n const Model = getComponent(\"Model\")\n\n let expanded\n if(this.props.layoutSelectors) {\n // If this is prop is present, we'll have deepLinking for it\n expanded = this.props.layoutSelectors.isShown(this.props.fullPath)\n }\n\n return
\n \n
\n }\n}\n","import React, { Component } from \"react\"\nimport Im, { Map } from \"immutable\"\nimport PropTypes from \"prop-types\"\n\nexport default class Models extends Component {\n static propTypes = {\n getComponent: PropTypes.func,\n specSelectors: PropTypes.object,\n specActions: PropTypes.object.isRequired,\n layoutSelectors: PropTypes.object,\n layoutActions: PropTypes.object,\n getConfigs: PropTypes.func.isRequired\n }\n\n getSchemaBasePath = () => {\n const isOAS3 = this.props.specSelectors.isOAS3()\n return isOAS3 ? [\"components\", \"schemas\"] : [\"definitions\"]\n }\n\n getCollapsedContent = () => {\n return \" \"\n }\n\n handleToggle = (name, isExpanded) => {\n const { layoutActions } = this.props\n layoutActions.show([...this.getSchemaBasePath(), name], isExpanded)\n if(isExpanded) {\n this.props.specActions.requestResolvedSubtree([...this.getSchemaBasePath(), name])\n }\n }\n\n onLoadModels = (ref) => {\n if (ref) {\n this.props.layoutActions.readyToScroll(this.getSchemaBasePath(), ref)\n }\n }\n\n onLoadModel = (ref) => {\n if (ref) {\n const name = ref.getAttribute(\"data-name\")\n this.props.layoutActions.readyToScroll([...this.getSchemaBasePath(), name], ref)\n }\n }\n\n render(){\n let { specSelectors, getComponent, layoutSelectors, layoutActions, getConfigs } = this.props\n let definitions = specSelectors.definitions()\n let { docExpansion, defaultModelsExpandDepth } = getConfigs()\n if (!definitions.size || defaultModelsExpandDepth < 0) return null\n\n const specPathBase = this.getSchemaBasePath()\n let showModels = layoutSelectors.isShown(specPathBase, defaultModelsExpandDepth > 0 && docExpansion !== \"none\")\n const isOAS3 = specSelectors.isOAS3()\n\n const ModelWrapper = getComponent(\"ModelWrapper\")\n const Collapse = getComponent(\"Collapse\")\n const ModelCollapse = getComponent(\"ModelCollapse\")\n const JumpToPath = getComponent(\"JumpToPath\", true)\n\n return
\n

\n layoutActions.show(specPathBase, !showModels)}\n >\n {isOAS3 ? \"Schemas\" : \"Models\"}\n \n \n \n \n

\n \n {\n definitions.entrySeq().map(([name])=>{\n\n const fullPath = [...specPathBase, name]\n const specPath = Im.List(fullPath)\n\n const schemaValue = specSelectors.specResolvedSubtree(fullPath)\n const rawSchemaValue = specSelectors.specJson().getIn(fullPath)\n\n const schema = Map.isMap(schemaValue) ? schemaValue : Im.Map()\n const rawSchema = Map.isMap(rawSchemaValue) ? rawSchemaValue : Im.Map()\n\n const displayName = schema.get(\"title\") || rawSchema.get(\"title\") || name\n const isShown = layoutSelectors.isShown(fullPath, false)\n\n if( isShown && (schema.size === 0 && rawSchema.size > 0) ) {\n // Firing an action in a container render is not great,\n // but it works for now.\n this.props.specActions.requestResolvedSubtree(fullPath)\n }\n\n const content = \n\n const title = \n \n {displayName}\n \n \n\n return
\n \n 0 && isShown }\n >{content}\n
\n }).toArray()\n }\n
\n
\n }\n}\n","import React from \"react\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nconst EnumModel = ({ value, getComponent }) => {\n let ModelCollapse = getComponent(\"ModelCollapse\")\n let collapsedContent = Array [ { value.count() } ]\n return \n Enum:
\n \n [ { value.join(\", \") } ]\n \n
\n}\nEnumModel.propTypes = {\n value: ImPropTypes.iterable,\n getComponent: ImPropTypes.func\n}\n\nexport default EnumModel","import React, { Component, } from \"react\"\nimport PropTypes from \"prop-types\"\nimport { List } from \"immutable\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport { sanitizeUrl } from \"core/utils\"\n\nconst braceOpen = \"{\"\nconst braceClose = \"}\"\nconst propClass = \"property\"\n\nexport default class ObjectModel extends Component {\n static propTypes = {\n schema: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n expanded: PropTypes.bool,\n onToggle: PropTypes.func,\n specSelectors: PropTypes.object.isRequired,\n name: PropTypes.string,\n displayName: PropTypes.string,\n isRef: PropTypes.bool,\n expandDepth: PropTypes.number,\n depth: PropTypes.number,\n specPath: ImPropTypes.list.isRequired,\n includeReadOnly: PropTypes.bool,\n includeWriteOnly: PropTypes.bool,\n }\n\n render(){\n let { schema, name, displayName, isRef, getComponent, getConfigs, depth, onToggle, expanded, specPath, ...otherProps } = this.props\n let { specSelectors,expandDepth, includeReadOnly, includeWriteOnly} = otherProps\n const { isOAS3 } = specSelectors\n\n if(!schema) {\n return null\n }\n\n const { showExtensions } = getConfigs()\n\n let description = schema.get(\"description\")\n let properties = schema.get(\"properties\")\n let additionalProperties = schema.get(\"additionalProperties\")\n let title = schema.get(\"title\") || displayName || name\n let requiredProperties = schema.get(\"required\")\n let infoProperties = schema\n .filter( ( v, key) => [\"maxProperties\", \"minProperties\", \"nullable\", \"example\"].indexOf(key) !== -1 )\n let deprecated = schema.get(\"deprecated\")\n let externalDocsUrl = schema.getIn([\"externalDocs\", \"url\"])\n let externalDocsDescription = schema.getIn([\"externalDocs\", \"description\"])\n\n const JumpToPath = getComponent(\"JumpToPath\", true)\n const Markdown = getComponent(\"Markdown\", true)\n const Model = getComponent(\"Model\")\n const ModelCollapse = getComponent(\"ModelCollapse\")\n const Property = getComponent(\"Property\")\n const Link = getComponent(\"Link\")\n\n const JumpToPathSection = () => {\n return \n }\n const collapsedContent = (\n { braceOpen }...{ braceClose }\n {\n isRef ? : \"\"\n }\n )\n\n const anyOf = specSelectors.isOAS3() ? schema.get(\"anyOf\") : null\n const oneOf = specSelectors.isOAS3() ? schema.get(\"oneOf\") : null\n const not = specSelectors.isOAS3() ? schema.get(\"not\") : null\n\n const titleEl = title && \n { isRef && schema.get(\"$$ref\") && { schema.get(\"$$ref\") } }\n { title }\n \n\n return \n \n\n { braceOpen }\n {\n !isRef ? null : \n }\n \n {\n \n {\n !description ? null : \n \n \n \n }\n {\n externalDocsUrl &&\n \n \n \n \n }\n {\n !deprecated ? null :\n \n \n \n \n }\n {\n !(properties && properties.size) ? null : properties.entrySeq().filter(\n ([, value]) => {\n return (!value.get(\"readOnly\") || includeReadOnly) &&\n (!value.get(\"writeOnly\") || includeWriteOnly)\n }\n ).map(\n ([key, value]) => {\n let isDeprecated = isOAS3() && value.get(\"deprecated\")\n let isRequired = List.isList(requiredProperties) && requiredProperties.contains(key)\n\n let classNames = [\"property-row\"]\n\n if (isDeprecated) {\n classNames.push(\"deprecated\")\n }\n\n if (isRequired) {\n classNames.push(\"required\")\n }\n\n return (\n \n \n )\n }).toArray()\n }\n {\n // empty row before extensions...\n !showExtensions ? null : \n }\n {\n !showExtensions ? null :\n schema.entrySeq().map(\n ([key, value]) => {\n if(key.slice(0,2) !== \"x-\") {\n return\n }\n\n const normalizedValue = !value ? null : value.toJS ? value.toJS() : value\n\n return (\n \n \n )\n }).toArray()\n }\n {\n !additionalProperties || !additionalProperties.size ? null\n : \n \n \n \n }\n {\n !anyOf ? null\n : \n \n \n \n }\n {\n !oneOf ? null\n : \n \n \n \n }\n {\n !not ? null\n : \n \n \n \n }\n
description:\n \n
\n externalDocs:\n \n {externalDocsDescription || externalDocsUrl}\n
\n deprecated:\n \n true\n
\n { key }{ isRequired && * }\n \n \n
 
\n { key }\n \n { JSON.stringify(normalizedValue) }\n
{ \"< * >:\" }\n \n
{ \"anyOf ->\" }\n {anyOf.map((schema, k) => {\n return
\n })}\n
{ \"oneOf ->\" }\n {oneOf.map((schema, k) => {\n return
\n })}\n
{ \"not ->\" }\n
\n \n
\n
\n }\n
\n { braceClose }\n \n {\n infoProperties.size ? infoProperties.entrySeq().map( ( [ key, v ] ) => ) : null\n }\n
\n }\n}\n","import React, { Component } from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport { sanitizeUrl } from \"core/utils\"\n\nconst propClass = \"property\"\n\nexport default class ArrayModel extends Component {\n static propTypes = {\n schema: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n specSelectors: PropTypes.object.isRequired,\n name: PropTypes.string,\n displayName: PropTypes.string,\n required: PropTypes.bool,\n expandDepth: PropTypes.number,\n specPath: ImPropTypes.list.isRequired,\n depth: PropTypes.number,\n includeReadOnly: PropTypes.bool,\n includeWriteOnly: PropTypes.bool,\n }\n\n render(){\n let { getComponent, getConfigs, schema, depth, expandDepth, name, displayName, specPath } = this.props\n let description = schema.get(\"description\")\n let items = schema.get(\"items\")\n let title = schema.get(\"title\") || displayName || name\n let properties = schema.filter( ( v, key) => [\"type\", \"items\", \"description\", \"$$ref\", \"externalDocs\"].indexOf(key) === -1 )\n let externalDocsUrl = schema.getIn([\"externalDocs\", \"url\"])\n let externalDocsDescription = schema.getIn([\"externalDocs\", \"description\"])\n\n\n const Markdown = getComponent(\"Markdown\", true)\n const ModelCollapse = getComponent(\"ModelCollapse\")\n const Model = getComponent(\"Model\")\n const Property = getComponent(\"Property\")\n const Link = getComponent(\"Link\")\n\n const titleEl = title &&\n \n { title }\n \n\n /*\n Note: we set `name={null}` in below because we don't want\n the name of the current Model passed (and displayed) as the name of the array element Model\n */\n\n return \n \n [\n {\n properties.size ? properties.entrySeq().map( ( [ key, v ] ) => ) : null\n }\n {\n !description ? (properties.size ?
: null) :\n \n }\n { externalDocsUrl &&\n
\n {externalDocsDescription || externalDocsUrl}\n
\n }\n \n \n \n ]\n
\n
\n }\n}\n","import React, { Component } from \"react\"\nimport PropTypes from \"prop-types\"\nimport { getExtensions, sanitizeUrl } from \"core/utils\"\n\nconst propClass = \"property primitive\"\n\nexport default class Primitive extends Component {\n static propTypes = {\n schema: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n name: PropTypes.string,\n displayName: PropTypes.string,\n depth: PropTypes.number,\n expandDepth: PropTypes.number\n }\n\n render() {\n let { schema, getComponent, getConfigs, name, displayName, depth, expandDepth } = this.props\n\n const { showExtensions } = getConfigs()\n\n if (!schema || !schema.get) {\n // don't render if schema isn't correctly formed\n return
\n }\n\n let type = schema.get(\"type\")\n let format = schema.get(\"format\")\n let xml = schema.get(\"xml\")\n let enumArray = schema.get(\"enum\")\n let title = schema.get(\"title\") || displayName || name\n let description = schema.get(\"description\")\n let extensions = getExtensions(schema)\n let properties = schema\n .filter((_, key) => [\"enum\", \"type\", \"format\", \"description\", \"$$ref\", \"externalDocs\"].indexOf(key) === -1)\n .filterNot((_, key) => extensions.has(key))\n let externalDocsUrl = schema.getIn([\"externalDocs\", \"url\"])\n let externalDocsDescription = schema.getIn([\"externalDocs\", \"description\"])\n\n const Markdown = getComponent(\"Markdown\", true)\n const EnumModel = getComponent(\"EnumModel\")\n const Property = getComponent(\"Property\")\n const ModelCollapse = getComponent(\"ModelCollapse\")\n const Link = getComponent(\"Link\")\n\n const titleEl = title &&\n \n {title}\n \n\n return \n \n \n {name && depth > 1 && {title}}\n {type}\n {format && (${format})}\n {\n properties.size ? properties.entrySeq().map(([key, v]) => ) : null\n }\n {\n showExtensions && extensions.size ? extensions.entrySeq().map(([key, v]) => ) : null\n }\n {\n !description ? null :\n \n }\n {\n externalDocsUrl &&\n
\n {externalDocsDescription || externalDocsUrl}\n
\n }\n {\n xml && xml.size ? (
xml:\n {\n xml.entrySeq().map(([key, v]) =>
   {key}: {String(v)}
).toArray()\n }\n
) : null\n }\n {\n enumArray && \n }\n
\n
\n
\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport const Property = ({ propKey, propVal, propClass }) => {\n return (\n \n
{ propKey }: { String(propVal) }
\n )\n}\nProperty.propTypes = {\n propKey: PropTypes.string,\n propVal: PropTypes.any,\n propClass: PropTypes.string\n}\n\nexport default Property\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class TryItOutButton extends React.Component {\n\n static propTypes = {\n onTryoutClick: PropTypes.func,\n onResetClick: PropTypes.func,\n onCancelClick: PropTypes.func,\n enabled: PropTypes.bool, // Try it out is enabled, ie: the user has access to the form\n hasUserEditedBody: PropTypes.bool, // Try it out is enabled, ie: the user has access to the form\n isOAS3: PropTypes.bool, // Try it out is enabled, ie: the user has access to the form\n }\n\n static defaultProps = {\n onTryoutClick: Function.prototype,\n onCancelClick: Function.prototype,\n onResetClick: Function.prototype,\n enabled: false,\n hasUserEditedBody: false,\n isOAS3: false,\n }\n\n render() {\n const { onTryoutClick, onCancelClick, onResetClick, enabled, hasUserEditedBody, isOAS3 } = this.props\n\n const showReset = isOAS3 && hasUserEditedBody\n return (\n
\n {\n enabled ? \n : \n\n }\n {\n showReset && \n }\n
\n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class VersionPragmaFilter extends React.PureComponent {\n static propTypes = {\n isSwagger2: PropTypes.bool.isRequired,\n isOAS3: PropTypes.bool.isRequired,\n bypass: PropTypes.bool,\n alsoShow: PropTypes.element,\n children: PropTypes.any,\n }\n\n static defaultProps = {\n alsoShow: null,\n children: null,\n bypass: false,\n }\n\n render() {\n const { bypass, isSwagger2, isOAS3, alsoShow } = this.props\n\n if(bypass) {\n return
{ this.props.children }
\n }\n\n if(isSwagger2 && isOAS3) {\n return
\n {alsoShow}\n
\n
\n

Unable to render this definition

\n

swagger and openapi fields cannot be present in the same Swagger or OpenAPI definition. Please remove one of the fields.

\n

Supported version fields are swagger: {\"\\\"2.0\\\"\"} and those that match openapi: 3.0.n (for example, openapi: 3.0.0).

\n
\n
\n
\n }\n\n if(!isSwagger2 && !isOAS3) {\n return
\n {alsoShow}\n
\n
\n

Unable to render this definition

\n

The provided definition does not specify a valid version field.

\n

Please indicate a valid Swagger or OpenAPI version field. Supported version fields are swagger: {\"\\\"2.0\\\"\"} and those that match openapi: 3.0.n (for example, openapi: 3.0.0).

\n
\n
\n
\n }\n\n return
{ this.props.children }
\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nconst VersionStamp = ({ version }) => {\n return
 { version } 
\n}\n\nVersionStamp.propTypes = {\n version: PropTypes.string.isRequired\n}\n\nexport default VersionStamp\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport const DeepLink = ({ enabled, path, text }) => {\n return (\n e.preventDefault() : null}\n href={enabled ? `#/${path}` : null}>\n {text}\n \n )\n}\nDeepLink.propTypes = {\n enabled: PropTypes.bool,\n isShown: PropTypes.bool,\n path: PropTypes.string,\n text: PropTypes.node\n}\n\nexport default DeepLink\n","import React from \"react\"\nconst SvgAssets = () =>\n
\n \n \n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n \n \n\n \n \n
\n\nexport default SvgAssets\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class BaseLayout extends React.Component {\n\n static propTypes = {\n errSelectors: PropTypes.object.isRequired,\n errActions: PropTypes.object.isRequired,\n specSelectors: PropTypes.object.isRequired,\n oas3Selectors: PropTypes.object.isRequired,\n oas3Actions: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired\n }\n\n render() {\n let {errSelectors, specSelectors, getComponent} = this.props\n\n let SvgAssets = getComponent(\"SvgAssets\")\n let InfoContainer = getComponent(\"InfoContainer\", true)\n let VersionPragmaFilter = getComponent(\"VersionPragmaFilter\")\n let Operations = getComponent(\"operations\", true)\n let Models = getComponent(\"Models\", true)\n let Row = getComponent(\"Row\")\n let Col = getComponent(\"Col\")\n let Errors = getComponent(\"errors\", true)\n\n const ServersContainer = getComponent(\"ServersContainer\", true)\n const SchemesContainer = getComponent(\"SchemesContainer\", true)\n const AuthorizeBtnContainer = getComponent(\"AuthorizeBtnContainer\", true)\n const FilterContainer = getComponent(\"FilterContainer\", true)\n let isSwagger2 = specSelectors.isSwagger2()\n let isOAS3 = specSelectors.isOAS3()\n\n const isSpecEmpty = !specSelectors.specStr()\n\n const loadingStatus = specSelectors.loadingStatus()\n\n let loadingMessage = null\n\n if(loadingStatus === \"loading\") {\n loadingMessage =
\n
\n
\n
\n
\n }\n\n if(loadingStatus === \"failed\") {\n loadingMessage =
\n
\n

Failed to load API definition.

\n \n
\n
\n }\n\n if (loadingStatus === \"failedConfig\") {\n const lastErr = errSelectors.lastError()\n const lastErrMsg = lastErr ? lastErr.get(\"message\") : \"\"\n loadingMessage =
\n
\n

Failed to load remote configuration.

\n

{lastErrMsg}

\n
\n
\n }\n\n if(!loadingMessage && isSpecEmpty) {\n loadingMessage =

No API definition provided.

\n }\n\n if(loadingMessage) {\n return
\n
\n {loadingMessage}\n
\n
\n }\n\n const servers = specSelectors.servers()\n const schemes = specSelectors.schemes()\n\n const hasServers = servers && servers.size\n const hasSchemes = schemes && schemes.size\n const hasSecurityDefinitions = !!specSelectors.securityDefinitions()\n\n return (\n
\n \n }>\n \n \n \n \n \n \n\n {hasServers || hasSchemes || hasSecurityDefinitions ? (\n
\n \n {hasServers ? () : null}\n {hasSchemes ? () : null}\n {hasSecurityDefinitions ? () : null}\n \n
\n ) : null}\n\n \n\n \n \n \n \n \n \n \n \n \n \n
\n
\n )\n }\n}\n","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_react_debounce_input_7ed3e068__[\"default\"] });","import React, { PureComponent, Component } from \"react\"\nimport PropTypes from \"prop-types\"\nimport { List, fromJS } from \"immutable\"\nimport cx from \"classnames\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport DebounceInput from \"react-debounce-input\"\nimport { stringify, getSampleSchema } from \"core/utils\"\n//import \"less/json-schema-form\"\n\nconst noop = ()=> {}\nconst JsonSchemaPropShape = {\n getComponent: PropTypes.func.isRequired,\n value: PropTypes.any,\n onChange: PropTypes.func,\n keyName: PropTypes.any,\n fn: PropTypes.object.isRequired,\n schema: PropTypes.object,\n errors: ImPropTypes.list,\n required: PropTypes.bool,\n dispatchInitialValue: PropTypes.bool,\n description: PropTypes.any,\n disabled: PropTypes.bool,\n}\n\nconst JsonSchemaDefaultProps = {\n value: \"\",\n onChange: noop,\n schema: {},\n keyName: \"\",\n required: false,\n errors: List()\n}\n\nexport class JsonSchemaForm extends Component {\n\n static propTypes = JsonSchemaPropShape\n static defaultProps = JsonSchemaDefaultProps\n\n componentDidMount() {\n const { dispatchInitialValue, value, onChange } = this.props\n if(dispatchInitialValue) {\n onChange(value)\n } else if(dispatchInitialValue === false) {\n onChange(\"\")\n }\n }\n\n render() {\n let { schema, errors, value, onChange, getComponent, fn, disabled } = this.props\n const format = schema && schema.get ? schema.get(\"format\") : null\n const type = schema && schema.get ? schema.get(\"type\") : null\n\n let getComponentSilently = (name) => getComponent(name, false, { failSilently: true })\n let Comp = type ? format ?\n getComponentSilently(`JsonSchema_${type}_${format}`) :\n getComponentSilently(`JsonSchema_${type}`) :\n getComponent(\"JsonSchema_string\")\n if (!Comp) {\n Comp = getComponent(\"JsonSchema_string\")\n }\n return \n }\n}\n\nexport class JsonSchema_string extends Component {\n static propTypes = JsonSchemaPropShape\n static defaultProps = JsonSchemaDefaultProps\n onChange = (e) => {\n const value = this.props.schema && this.props.schema.get(\"type\") === \"file\" ? e.target.files[0] : e.target.value\n this.props.onChange(value, this.props.keyName)\n }\n onEnumChange = (val) => this.props.onChange(val)\n render() {\n let { getComponent, value, schema, errors, required, description, disabled } = this.props\n const enumValue = schema && schema.get ? schema.get(\"enum\") : null\n const format = schema && schema.get ? schema.get(\"format\") : null\n const type = schema && schema.get ? schema.get(\"type\") : null\n const schemaIn = schema && schema.get ? schema.get(\"in\") : null\n if (!value) {\n value = \"\" // value should not be null; this fixes a Debounce error\n }\n errors = errors.toJS ? errors.toJS() : []\n\n if ( enumValue ) {\n const Select = getComponent(\"Select\")\n return (\n )\n }\n else {\n return (\n \n )\n }\n }\n}\n\nexport class JsonSchema_array extends PureComponent {\n\n static propTypes = JsonSchemaPropShape\n static defaultProps = JsonSchemaDefaultProps\n\n constructor(props, context) {\n super(props, context)\n this.state = { value: valueOrEmptyList(props.value), schema: props.schema}\n }\n\n UNSAFE_componentWillReceiveProps(props) {\n const value = valueOrEmptyList(props.value)\n if(value !== this.state.value)\n this.setState({ value })\n\n if(props.schema !== this.state.schema)\n this.setState({ schema: props.schema })\n }\n\n onChange = () => {\n this.props.onChange(this.state.value)\n }\n\n onItemChange = (itemVal, i) => {\n this.setState(({ value }) => ({\n value: value.set(i, itemVal)\n }), this.onChange)\n }\n\n removeItem = (i) => {\n this.setState(({ value }) => ({\n value: value.delete(i)\n }), this.onChange)\n }\n\n addItem = () => {\n let newValue = valueOrEmptyList(this.state.value)\n this.setState(() => ({\n value: newValue.push(getSampleSchema(this.state.schema.get(\"items\"), false, {\n includeWriteOnly: true\n }))\n }), this.onChange)\n }\n\n onEnumChange = (value) => {\n this.setState(() => ({\n value: value\n }), this.onChange)\n }\n\n render() {\n let { getComponent, required, schema, errors, fn, disabled } = this.props\n\n errors = errors.toJS ? errors.toJS() : Array.isArray(errors) ? errors : []\n const arrayErrors = errors.filter(e => typeof e === \"string\")\n const needsRemoveError = errors.filter(e => e.needRemove !== undefined)\n .map(e => e.error)\n const value = this.state.value // expect Im List\n const shouldRenderValue =\n value && value.count && value.count() > 0 ? true : false\n const schemaItemsEnum = schema.getIn([\"items\", \"enum\"])\n const schemaItemsType = schema.getIn([\"items\", \"type\"])\n const schemaItemsFormat = schema.getIn([\"items\", \"format\"])\n const schemaItemsSchema = schema.get(\"items\")\n let ArrayItemsComponent\n let isArrayItemText = false\n let isArrayItemFile = (schemaItemsType === \"file\" || (schemaItemsType === \"string\" && schemaItemsFormat === \"binary\")) ? true : false\n if (schemaItemsType && schemaItemsFormat) {\n ArrayItemsComponent = getComponent(`JsonSchema_${schemaItemsType}_${schemaItemsFormat}`)\n } else if (schemaItemsType === \"boolean\" || schemaItemsType === \"array\" || schemaItemsType === \"object\") {\n ArrayItemsComponent = getComponent(`JsonSchema_${schemaItemsType}`)\n }\n // if ArrayItemsComponent not assigned or does not exist,\n // use default schemaItemsType === \"string\" & JsonSchemaArrayItemText component\n if (!ArrayItemsComponent && !isArrayItemFile) {\n isArrayItemText = true\n }\n\n if ( schemaItemsEnum ) {\n const Select = getComponent(\"Select\")\n return ()\n }\n}\n\nexport class JsonSchema_boolean extends Component {\n static propTypes = JsonSchemaPropShape\n static defaultProps = JsonSchemaDefaultProps\n\n onEnumChange = (val) => this.props.onChange(val)\n render() {\n let { getComponent, value, errors, schema, required, disabled } = this.props\n errors = errors.toJS ? errors.toJS() : []\n let enumValue = schema && schema.get ? schema.get(\"enum\") : null\n let allowEmptyValue = !enumValue || !required\n let booleanValue = !enumValue && [\"true\", \"false\"]\n const Select = getComponent(\"Select\")\n\n return ( + } + + { + errors.valueSeq().map( (error, key) => { + return + } ) + } + + ) + } +} diff --git a/frontend/web/api-doc/src/core/components/auth/auth-item.jsx b/frontend/web/api-doc/src/core/components/auth/auth-item.jsx new file mode 100644 index 0000000..ab58b00 --- /dev/null +++ b/frontend/web/api-doc/src/core/components/auth/auth-item.jsx @@ -0,0 +1,56 @@ +import React from "react" +import PropTypes from "prop-types" +import ImPropTypes from "react-immutable-proptypes" + +export default class Auths extends React.Component { + static propTypes = { + authorized: ImPropTypes.orderedMap.isRequired, + schema: ImPropTypes.orderedMap.isRequired, + name: PropTypes.string.isRequired, + getComponent: PropTypes.func.isRequired, + onAuthChange: PropTypes.func.isRequired, + errSelectors: PropTypes.object.isRequired, + } + + render() { + let { + schema, + name, + getComponent, + onAuthChange, + authorized, + errSelectors + } = this.props + const ApiKeyAuth = getComponent("apiKeyAuth") + const BasicAuth = getComponent("basicAuth") + + let authEl + + const type = schema.get("type") + + switch(type) { + case "apiKey": authEl = + break + case "basic": authEl = + break + default: authEl =
Unknown security definition type { type }
+ } + + return (
+ { authEl } +
) + } + +} diff --git a/frontend/web/api-doc/src/core/components/auth/authorization-popup.jsx b/frontend/web/api-doc/src/core/components/auth/authorization-popup.jsx new file mode 100644 index 0000000..14406c8 --- /dev/null +++ b/frontend/web/api-doc/src/core/components/auth/authorization-popup.jsx @@ -0,0 +1,60 @@ +import React from "react" +import PropTypes from "prop-types" + +export default class AuthorizationPopup extends React.Component { + close =() => { + let { authActions } = this.props + + authActions.showDefinitions(false) + } + + render() { + let { authSelectors, authActions, getComponent, errSelectors, specSelectors, fn: { AST = {} } } = this.props + let definitions = authSelectors.shownDefinitions() + const Auths = getComponent("auths") + + return ( +
+
+
+
+
+
+

Available authorizations

+ +
+
+ + { + definitions.valueSeq().map(( definition, key ) => { + return + }) + } +
+
+
+
+
+ ) + } + + static propTypes = { + fn: PropTypes.object.isRequired, + getComponent: PropTypes.func.isRequired, + authSelectors: PropTypes.object.isRequired, + specSelectors: PropTypes.object.isRequired, + errSelectors: PropTypes.object.isRequired, + authActions: PropTypes.object.isRequired, + } +} diff --git a/frontend/web/api-doc/src/core/components/auth/authorize-btn.jsx b/frontend/web/api-doc/src/core/components/auth/authorize-btn.jsx new file mode 100644 index 0000000..57a1b2b --- /dev/null +++ b/frontend/web/api-doc/src/core/components/auth/authorize-btn.jsx @@ -0,0 +1,30 @@ +import React from "react" +import PropTypes from "prop-types" + +export default class AuthorizeBtn extends React.Component { + static propTypes = { + onClick: PropTypes.func, + isAuthorized: PropTypes.bool, + showPopup: PropTypes.bool, + getComponent: PropTypes.func.isRequired + } + + render() { + let { isAuthorized, showPopup, onClick, getComponent } = this.props + + //must be moved out of button component + const AuthorizationPopup = getComponent("authorizationPopup", true) + + return ( +
+ + { showPopup && } +
+ ) + } +} diff --git a/frontend/web/api-doc/src/core/components/auth/authorize-operation-btn.jsx b/frontend/web/api-doc/src/core/components/auth/authorize-operation-btn.jsx new file mode 100644 index 0000000..19de588 --- /dev/null +++ b/frontend/web/api-doc/src/core/components/auth/authorize-operation-btn.jsx @@ -0,0 +1,33 @@ +import React from "react" +import PropTypes from "prop-types" + +export default class AuthorizeOperationBtn extends React.Component { + static propTypes = { + isAuthorized: PropTypes.bool.isRequired, + onClick: PropTypes.func + } + + onClick =(e) => { + e.stopPropagation() + let { onClick } = this.props + + if(onClick) { + onClick() + } + } + + render() { + let { isAuthorized } = this.props + + return ( + + + ) + } +} diff --git a/frontend/web/api-doc/src/core/components/auth/auths.jsx b/frontend/web/api-doc/src/core/components/auth/auths.jsx new file mode 100644 index 0000000..c2a51ea --- /dev/null +++ b/frontend/web/api-doc/src/core/components/auth/auths.jsx @@ -0,0 +1,123 @@ +import React from "react" +import PropTypes from "prop-types" +import ImPropTypes from "react-immutable-proptypes" + +export default class Auths extends React.Component { + static propTypes = { + definitions: ImPropTypes.iterable.isRequired, + getComponent: PropTypes.func.isRequired, + authSelectors: PropTypes.object.isRequired, + authActions: PropTypes.object.isRequired, + errSelectors: PropTypes.object.isRequired, + specSelectors: PropTypes.object.isRequired + } + + constructor(props, context) { + super(props, context) + + this.state = {} + } + + onAuthChange =(auth) => { + let { name } = auth + + this.setState({ [name]: auth }) + } + + submitAuth =(e) => { + e.preventDefault() + + let { authActions } = this.props + authActions.authorizeWithPersistOption(this.state) + } + + logoutClick =(e) => { + e.preventDefault() + + let { authActions, definitions } = this.props + let auths = definitions.map( (val, key) => { + return key + }).toArray() + + this.setState(auths.reduce((prev, auth) => { + prev[auth] = "" + return prev + }, {})) + + authActions.logoutWithPersistOption(auths) + } + + close =(e) => { + e.preventDefault() + let { authActions } = this.props + + authActions.showDefinitions(false) + } + + render() { + let { definitions, getComponent, authSelectors, errSelectors } = this.props + const AuthItem = getComponent("AuthItem") + const Oauth2 = getComponent("oauth2", true) + const Button = getComponent("Button") + + let authorized = authSelectors.authorized() + + let authorizedAuth = definitions.filter( (definition, key) => { + return !!authorized.get(key) + }) + + let nonOauthDefinitions = definitions.filter( schema => schema.get("type") !== "oauth2") + let oauthDefinitions = definitions.filter( schema => schema.get("type") === "oauth2") + + return ( +
+ { + !!nonOauthDefinitions.size &&
+ { + nonOauthDefinitions.map( (schema, name) => { + return + }).toArray() + } +
+ { + nonOauthDefinitions.size === authorizedAuth.size ? + : + } + +
+ + } + + { + oauthDefinitions && oauthDefinitions.size ?
+
+

Scopes are used to grant an application different levels of access to data on behalf of the end user. Each API may declare one or more scopes.

+

API requires the following scopes. Select which ones you want to grant to Swagger UI.

+
+ { + definitions.filter( schema => schema.get("type") === "oauth2") + .map( (schema, name) =>{ + return (
+ +
) + } + ).toArray() + } +
: null + } + +
+ ) + } + +} diff --git a/frontend/web/api-doc/src/core/components/auth/basic-auth.jsx b/frontend/web/api-doc/src/core/components/auth/basic-auth.jsx new file mode 100644 index 0000000..baa5268 --- /dev/null +++ b/frontend/web/api-doc/src/core/components/auth/basic-auth.jsx @@ -0,0 +1,94 @@ +import React from "react" +import PropTypes from "prop-types" +import ImPropTypes from "react-immutable-proptypes" + +export default class BasicAuth extends React.Component { + static propTypes = { + authorized: ImPropTypes.map, + schema: ImPropTypes.map, + getComponent: PropTypes.func.isRequired, + onChange: PropTypes.func.isRequired, + name: PropTypes.string.isRequired, + errSelectors: PropTypes.object.isRequired, + } + + constructor(props, context) { + super(props, context) + let { schema, name } = this.props + + let value = this.getValue() + let username = value.username + + this.state = { + name: name, + schema: schema, + value: !username ? {} : { + username: username + } + } + } + + getValue () { + let { authorized, name } = this.props + + return authorized && authorized.getIn([name, "value"]) || {} + } + + onChange =(e) => { + let { onChange } = this.props + let { value, name } = e.target + + let newValue = this.state.value + newValue[name] = value + + this.setState({ value: newValue }) + + onChange(this.state) + } + + render() { + let { schema, getComponent, name, errSelectors } = this.props + const Input = getComponent("Input") + const Row = getComponent("Row") + const Col = getComponent("Col") + const AuthError = getComponent("authError") + const JumpToPath = getComponent("JumpToPath", true) + const Markdown = getComponent("Markdown", true) + let username = this.getValue().username + let errors = errSelectors.allErrors().filter( err => err.get("authId") === name) + + return ( +
+

Basic authorization

+ { username &&
Authorized
} + + + + + + { + username ? { username } + : + } + + + + { + username ? ****** + : + } + + { + errors.valueSeq().map( (error, key) => { + return + } ) + } +
+ ) + } + +} diff --git a/frontend/web/api-doc/src/core/components/auth/error.jsx b/frontend/web/api-doc/src/core/components/auth/error.jsx new file mode 100644 index 0000000..bad1d41 --- /dev/null +++ b/frontend/web/api-doc/src/core/components/auth/error.jsx @@ -0,0 +1,24 @@ +import React from "react" +import PropTypes from "prop-types" + +export default class AuthError extends React.Component { + + static propTypes = { + error: PropTypes.object.isRequired + } + + render() { + let { error } = this.props + + let level = error.get("level") + let message = error.get("message") + let source = error.get("source") + + return ( +
+ { source } { level } + { message } +
+ ) + } +} diff --git a/frontend/web/api-doc/src/core/components/auth/oauth2.jsx b/frontend/web/api-doc/src/core/components/auth/oauth2.jsx new file mode 100644 index 0000000..487004c --- /dev/null +++ b/frontend/web/api-doc/src/core/components/auth/oauth2.jsx @@ -0,0 +1,281 @@ +import React from "react" +import PropTypes from "prop-types" +import oauth2Authorize from "core/oauth2-authorize" + +export default class Oauth2 extends React.Component { + static propTypes = { + name: PropTypes.string, + authorized: PropTypes.object, + getComponent: PropTypes.func.isRequired, + schema: PropTypes.object.isRequired, + authSelectors: PropTypes.object.isRequired, + authActions: PropTypes.object.isRequired, + errSelectors: PropTypes.object.isRequired, + oas3Selectors: PropTypes.object.isRequired, + specSelectors: PropTypes.object.isRequired, + errActions: PropTypes.object.isRequired, + getConfigs: PropTypes.any + } + + constructor(props, context) { + super(props, context) + let { name, schema, authorized, authSelectors } = this.props + let auth = authorized && authorized.get(name) + let authConfigs = authSelectors.getConfigs() || {} + let username = auth && auth.get("username") || "" + let clientId = auth && auth.get("clientId") || authConfigs.clientId || "" + let clientSecret = auth && auth.get("clientSecret") || authConfigs.clientSecret || "" + let passwordType = auth && auth.get("passwordType") || "basic" + let scopes = auth && auth.get("scopes") || authConfigs.scopes || [] + if (typeof scopes === "string") { + scopes = scopes.split(authConfigs.scopeSeparator || " ") + } + + this.state = { + appName: authConfigs.appName, + name: name, + schema: schema, + scopes: scopes, + clientId: clientId, + clientSecret: clientSecret, + username: username, + password: "", + passwordType: passwordType + } + } + + close = (e) => { + e.preventDefault() + let { authActions } = this.props + + authActions.showDefinitions(false) + } + + authorize =() => { + let { authActions, errActions, getConfigs, authSelectors, oas3Selectors } = this.props + let configs = getConfigs() + let authConfigs = authSelectors.getConfigs() + + errActions.clear({authId: name,type: "auth", source: "auth"}) + oauth2Authorize({ + auth: this.state, + currentServer: oas3Selectors.serverEffectiveValue(oas3Selectors.selectedServer()), + authActions, + errActions, + configs, + authConfigs + }) + } + + onScopeChange =(e) => { + let { target } = e + let { checked } = target + let scope = target.dataset.value + + if ( checked && this.state.scopes.indexOf(scope) === -1 ) { + let newScopes = this.state.scopes.concat([scope]) + this.setState({ scopes: newScopes }) + } else if ( !checked && this.state.scopes.indexOf(scope) > -1) { + this.setState({ scopes: this.state.scopes.filter((val) => val !== scope) }) + } + } + + onInputChange =(e) => { + let { target : { dataset : { name }, value } } = e + let state = { + [name]: value + } + + this.setState(state) + } + + selectScopes =(e) => { + if (e.target.dataset.all) { + this.setState({ + scopes: Array.from((this.props.schema.get("allowedScopes") || this.props.schema.get("scopes")).keys()) + }) + } else { + this.setState({ scopes: [] }) + } + } + + logout =(e) => { + e.preventDefault() + let { authActions, errActions, name } = this.props + + errActions.clear({authId: name, type: "auth", source: "auth"}) + authActions.logoutWithPersistOption([ name ]) + } + + render() { + let { + schema, getComponent, authSelectors, errSelectors, name, specSelectors + } = this.props + const Input = getComponent("Input") + const Row = getComponent("Row") + const Col = getComponent("Col") + const Button = getComponent("Button") + const AuthError = getComponent("authError") + const JumpToPath = getComponent("JumpToPath", true) + const Markdown = getComponent("Markdown", true) + const InitializedInput = getComponent("InitializedInput") + + const { isOAS3 } = specSelectors + + let oidcUrl = isOAS3() ? schema.get("openIdConnectUrl") : null + + // Auth type consts + const AUTH_FLOW_IMPLICIT = "implicit" + const AUTH_FLOW_PASSWORD = "password" + const AUTH_FLOW_ACCESS_CODE = isOAS3() ? (oidcUrl ? "authorization_code" : "authorizationCode") : "accessCode" + const AUTH_FLOW_APPLICATION = isOAS3() ? (oidcUrl ? "client_credentials" : "clientCredentials") : "application" + + let authConfigs = authSelectors.getConfigs() || {} + let isPkceCodeGrant = !!authConfigs.usePkceWithAuthorizationCodeGrant + + let flow = schema.get("flow") + let flowToDisplay = flow === AUTH_FLOW_ACCESS_CODE && isPkceCodeGrant ? flow + " with PKCE" : flow + let scopes = schema.get("allowedScopes") || schema.get("scopes") + let authorizedAuth = authSelectors.authorized().get(name) + let isAuthorized = !!authorizedAuth + let errors = errSelectors.allErrors().filter( err => err.get("authId") === name) + let isValid = !errors.filter( err => err.get("source") === "validation").size + let description = schema.get("description") + + return ( +
+

{name} (OAuth2, { flowToDisplay })

+ { !this.state.appName ? null :
Application: { this.state.appName }
} + { description && } + + { isAuthorized &&
Authorized
} + + { oidcUrl &&

OpenID Connect URL: { oidcUrl }

} + { ( flow === AUTH_FLOW_IMPLICIT || flow === AUTH_FLOW_ACCESS_CODE ) &&

Authorization URL: { schema.get("authorizationUrl") }

} + { ( flow === AUTH_FLOW_PASSWORD || flow === AUTH_FLOW_ACCESS_CODE || flow === AUTH_FLOW_APPLICATION ) &&

Token URL: { schema.get("tokenUrl") }

} +

Flow: { flowToDisplay }

+ + { + flow !== AUTH_FLOW_PASSWORD ? null + : + + + { + isAuthorized ? { this.state.username } + : + + + } + + { + + } + + + { + isAuthorized ? ****** + : + + + } + + + + { + isAuthorized ? { this.state.passwordType } + : + + + } + + + } + { + ( flow === AUTH_FLOW_APPLICATION || flow === AUTH_FLOW_IMPLICIT || flow === AUTH_FLOW_ACCESS_CODE || flow === AUTH_FLOW_PASSWORD ) && + ( !isAuthorized || isAuthorized && this.state.clientId) && + + { + isAuthorized ? ****** + : + + + } + + } + + { + ( (flow === AUTH_FLOW_APPLICATION || flow === AUTH_FLOW_ACCESS_CODE || flow === AUTH_FLOW_PASSWORD) && + + { + isAuthorized ? ****** + : + + + } + + + )} + + { + !isAuthorized && scopes && scopes.size ?
+

+ Scopes: + select all + select none +

+ { scopes.map((description, name) => { + return ( + +
+ + +
+
+ ) + }).toArray() + } +
: null + } + + { + errors.valueSeq().map( (error, key) => { + return + } ) + } +
+ { isValid && + ( isAuthorized ? + : + ) + } + +
+ +
+ ) + } +} diff --git a/frontend/web/api-doc/src/core/components/clear.jsx b/frontend/web/api-doc/src/core/components/clear.jsx new file mode 100644 index 0000000..d4e0ec8 --- /dev/null +++ b/frontend/web/api-doc/src/core/components/clear.jsx @@ -0,0 +1,25 @@ +import React, { Component } from "react" +import PropTypes from "prop-types" + +export default class Clear extends Component { + + onClick =() => { + let { specActions, path, method } = this.props + specActions.clearResponse( path, method ) + specActions.clearRequest( path, method ) + } + + render(){ + return ( + + ) + } + + static propTypes = { + specActions: PropTypes.object.isRequired, + path: PropTypes.string.isRequired, + method: PropTypes.string.isRequired, + } +} diff --git a/frontend/web/api-doc/src/core/components/content-type.jsx b/frontend/web/api-doc/src/core/components/content-type.jsx new file mode 100644 index 0000000..45b63a2 --- /dev/null +++ b/frontend/web/api-doc/src/core/components/content-type.jsx @@ -0,0 +1,61 @@ +import React from "react" +import PropTypes from "prop-types" +import ImPropTypes from "react-immutable-proptypes" +import { fromJS } from "immutable" + +const noop = ()=>{} + +export default class ContentType extends React.Component { + + static propTypes = { + ariaControls: PropTypes.string, + contentTypes: PropTypes.oneOfType([ImPropTypes.list, ImPropTypes.set, ImPropTypes.seq]), + controlId: PropTypes.string, + value: PropTypes.string, + onChange: PropTypes.func, + className: PropTypes.string, + ariaLabel: PropTypes.string + } + + static defaultProps = { + onChange: noop, + value: null, + contentTypes: fromJS(["application/json"]), + } + + componentDidMount() { + // Needed to populate the form, initially + if(this.props.contentTypes) { + this.props.onChange(this.props.contentTypes.first()) + } + } + + UNSAFE_componentWillReceiveProps(nextProps) { + if(!nextProps.contentTypes || !nextProps.contentTypes.size) { + return + } + + if(!nextProps.contentTypes.includes(nextProps.value)) { + nextProps.onChange(nextProps.contentTypes.first()) + } + } + + onChangeWrapper = e => this.props.onChange(e.target.value) + + render() { + let { ariaControls, ariaLabel, className, contentTypes, controlId, value } = this.props + + if ( !contentTypes || !contentTypes.size ) + return null + + return ( +
+ +
+ ) + } +} diff --git a/frontend/web/api-doc/src/core/components/copy-to-clipboard-btn.jsx b/frontend/web/api-doc/src/core/components/copy-to-clipboard-btn.jsx new file mode 100644 index 0000000..6702518 --- /dev/null +++ b/frontend/web/api-doc/src/core/components/copy-to-clipboard-btn.jsx @@ -0,0 +1,26 @@ +import React from "react" +import { CopyToClipboard } from "react-copy-to-clipboard" +import PropTypes from "prop-types" + +/** + * @param {{ textToCopy: string }} props + * @returns {JSX.Element} + * @constructor + */ +export default class CopyToClipboardBtn extends React.Component { + render() { + return ( +
+ + + + + +
+ ) + } + + static propTypes = { + textToCopy: PropTypes.string.isRequired, + } +} diff --git a/frontend/web/api-doc/src/core/components/curl.jsx b/frontend/web/api-doc/src/core/components/curl.jsx new file mode 100644 index 0000000..eefce02 --- /dev/null +++ b/frontend/web/api-doc/src/core/components/curl.jsx @@ -0,0 +1,44 @@ +import React from "react" +import PropTypes from "prop-types" +import { CopyToClipboard } from "react-copy-to-clipboard" +import {SyntaxHighlighter, getStyle} from "core/syntax-highlighting" +import get from "lodash/get" +import { requestSnippetGenerator_curl_bash } from "../plugins/request-snippets/fn" + +export default class Curl extends React.Component { + static propTypes = { + getConfigs: PropTypes.func.isRequired, + request: PropTypes.object.isRequired + } + + render() { + let { request, getConfigs } = this.props + let curl = requestSnippetGenerator_curl_bash(request) + + const config = getConfigs() + + const curlBlock = get(config, "syntaxHighlight.activated") + ? + {curl} + + : + + + return ( +
+

Curl

+
+
+
+ {curlBlock} +
+
+ ) + } + +} diff --git a/frontend/web/api-doc/src/core/components/debug.jsx b/frontend/web/api-doc/src/core/components/debug.jsx new file mode 100644 index 0000000..a8b2d6c --- /dev/null +++ b/frontend/web/api-doc/src/core/components/debug.jsx @@ -0,0 +1,50 @@ +import React from "react" +import PropTypes from "prop-types" +import ObjectInspector from "react-inspector" + +export default class Debug extends React.Component { + + constructor() { + super() + this.state = { + jsonDumpOpen: false + } + this.toggleJsonDump = (e) => { + e.preventDefault() + this.setState({jsonDumpOpen: !this.state.jsonDumpOpen}) + } + } + + plusOrMinus(bool) { + return bool ? "-" : "+" + } + + render() { + + let { getState, getComponent } = this.props + + window.props = this.props + + const Collapse = getComponent("Collapse") + + return ( + + ) + } + +} + +Debug.propTypes = { + getState: PropTypes.func.isRequired, + getComponent: PropTypes.func.isRequired, +} diff --git a/frontend/web/api-doc/src/core/components/deep-link.jsx b/frontend/web/api-doc/src/core/components/deep-link.jsx new file mode 100644 index 0000000..33e1ef4 --- /dev/null +++ b/frontend/web/api-doc/src/core/components/deep-link.jsx @@ -0,0 +1,20 @@ +import React from "react" +import PropTypes from "prop-types" + +export const DeepLink = ({ enabled, path, text }) => { + return ( + e.preventDefault() : null} + href={enabled ? `#/${path}` : null}> + {text} + + ) +} +DeepLink.propTypes = { + enabled: PropTypes.bool, + isShown: PropTypes.bool, + path: PropTypes.string, + text: PropTypes.node +} + +export default DeepLink diff --git a/frontend/web/api-doc/src/core/components/enum-model.jsx b/frontend/web/api-doc/src/core/components/enum-model.jsx new file mode 100644 index 0000000..78af10d --- /dev/null +++ b/frontend/web/api-doc/src/core/components/enum-model.jsx @@ -0,0 +1,19 @@ +import React from "react" +import ImPropTypes from "react-immutable-proptypes" + +const EnumModel = ({ value, getComponent }) => { + let ModelCollapse = getComponent("ModelCollapse") + let collapsedContent = Array [ { value.count() } ] + return + Enum:
+ + [ { value.join(", ") } ] + +
+} +EnumModel.propTypes = { + value: ImPropTypes.iterable, + getComponent: ImPropTypes.func +} + +export default EnumModel \ No newline at end of file diff --git a/frontend/web/api-doc/src/core/components/errors.jsx b/frontend/web/api-doc/src/core/components/errors.jsx new file mode 100644 index 0000000..2b99e33 --- /dev/null +++ b/frontend/web/api-doc/src/core/components/errors.jsx @@ -0,0 +1,136 @@ +import React from "react" +import PropTypes from "prop-types" +import { List } from "immutable" + +export default class Errors extends React.Component { + + static propTypes = { + editorActions: PropTypes.object, + errSelectors: PropTypes.object.isRequired, + layoutSelectors: PropTypes.object.isRequired, + layoutActions: PropTypes.object.isRequired, + getComponent: PropTypes.func.isRequired, + } + + render() { + let { editorActions, errSelectors, layoutSelectors, layoutActions, getComponent } = this.props + + const Collapse = getComponent("Collapse") + + if(editorActions && editorActions.jumpToLine) { + var jumpToLine = editorActions.jumpToLine + } + + let errors = errSelectors.allErrors() + + // all thrown errors, plus error-level everything else + let allErrorsToDisplay = errors.filter(err => err.get("type") === "thrown" ? true :err.get("level") === "error") + + if(!allErrorsToDisplay || allErrorsToDisplay.count() < 1) { + return null + } + + let isVisible = layoutSelectors.isShown(["errorPane"], true) + let toggleVisibility = () => layoutActions.show(["errorPane"], !isVisible) + + let sortedJSErrors = allErrorsToDisplay.sortBy(err => err.get("line")) + + return ( +
+        
+

Errors

+ +
+ +
+ { sortedJSErrors.map((err, i) => { + let type = err.get("type") + if(type === "thrown" || type === "auth") { + return + } + if(type === "spec") { + return + } + }) } +
+
+
+ ) + } +} + +const ThrownErrorItem = ( { error, jumpToLine } ) => { + if(!error) { + return null + } + let errorLine = error.get("line") + + return ( +
+ { !error ? null : +
+

{ (error.get("source") && error.get("level")) ? + toTitleCase(error.get("source")) + " " + error.get("level") : "" } + { error.get("path") ? at {error.get("path")}: null }

+ + { error.get("message") } + +
+ { errorLine && jumpToLine ? Jump to line { errorLine } : null } +
+
+ } +
+ ) + } + +const SpecErrorItem = ( { error, jumpToLine } ) => { + let locationMessage = null + + if(error.get("path")) { + if(List.isList(error.get("path"))) { + locationMessage = at { error.get("path").join(".") } + } else { + locationMessage = at { error.get("path") } + } + } else if(error.get("line") && !jumpToLine) { + locationMessage = on line { error.get("line") } + } + + return ( +
+ { !error ? null : +
+

{ toTitleCase(error.get("source")) + " " + error.get("level") } { locationMessage }

+ { error.get("message") } +
+ { jumpToLine ? ( + Jump to line { error.get("line") } + ) : null } +
+
+ } +
+ ) + } + +function toTitleCase(str) { + return (str || "") + .split(" ") + .map(substr => substr[0].toUpperCase() + substr.slice(1)) + .join(" ") +} + +ThrownErrorItem.propTypes = { + error: PropTypes.object.isRequired, + jumpToLine: PropTypes.func +} + +ThrownErrorItem.defaultProps = { + jumpToLine: null +} + +SpecErrorItem.propTypes = { + error: PropTypes.object.isRequired, + jumpToLine: PropTypes.func +} diff --git a/frontend/web/api-doc/src/core/components/example.jsx b/frontend/web/api-doc/src/core/components/example.jsx new file mode 100644 index 0000000..ad6ad8c --- /dev/null +++ b/frontend/web/api-doc/src/core/components/example.jsx @@ -0,0 +1,43 @@ +/** + * @prettier + */ + +import React from "react" +import PropTypes from "prop-types" +import ImPropTypes from "react-immutable-proptypes" +import { stringify } from "core/utils" + +export default function Example(props) { + const { example, showValue, getComponent, getConfigs } = props + + const Markdown = getComponent("Markdown", true) + const HighlightCode = getComponent("highlightCode") + + if(!example) return null + + return ( +
+ {example.get("description") ? ( +
+
Example Description
+

+ +

+
+ ) : null} + {showValue && example.has("value") ? ( +
+
Example Value
+ +
+ ) : null} +
+ ) +} + +Example.propTypes = { + example: ImPropTypes.map.isRequired, + showValue: PropTypes.bool, + getComponent: PropTypes.func.isRequired, + getConfigs: PropTypes.func.getConfigs, +} diff --git a/frontend/web/api-doc/src/core/components/examples-select-value-retainer.jsx b/frontend/web/api-doc/src/core/components/examples-select-value-retainer.jsx new file mode 100644 index 0000000..b51bc1a --- /dev/null +++ b/frontend/web/api-doc/src/core/components/examples-select-value-retainer.jsx @@ -0,0 +1,258 @@ +/** + * @prettier + */ +import React from "react" +import { Map, List } from "immutable" +import PropTypes from "prop-types" +import ImPropTypes from "react-immutable-proptypes" + +import { stringify } from "core/utils" + +// This stateful component lets us avoid writing competing values (user +// modifications vs example values) into global state, and the mess that comes +// with that: tracking which of the two values are currently used for +// Try-It-Out, which example a modified value came from, etc... +// +// The solution here is to retain the last user-modified value in +// ExamplesSelectValueRetainer's component state, so that our global state can stay +// clean, always simply being the source of truth for what value should be both +// displayed to the user and used as a value during request execution. +// +// This approach/tradeoff was chosen in order to encapsulate the particular +// logic of Examples within the Examples component tree, and to avoid +// regressions within our current implementation elsewhere (non-Examples +// definitions, OpenAPI 2.0, etc). A future refactor to global state might make +// this component unnecessary. +// +// TL;DR: this is not our usual approach, but the choice was made consciously. + +// Note that `currentNamespace` isn't currently used anywhere! + +const stringifyUnlessList = input => + List.isList(input) ? input : stringify(input) + +export default class ExamplesSelectValueRetainer extends React.PureComponent { + static propTypes = { + examples: ImPropTypes.map, + onSelect: PropTypes.func, + updateValue: PropTypes.func, // mechanism to update upstream value + userHasEditedBody: PropTypes.bool, + getComponent: PropTypes.func.isRequired, + currentUserInputValue: PropTypes.any, + currentKey: PropTypes.string, + currentNamespace: PropTypes.string, + setRetainRequestBodyValueFlag: PropTypes.func.isRequired, + // (also proxies props for Examples) + } + + static defaultProps = { + userHasEditedBody: false, + examples: Map({}), + currentNamespace: "__DEFAULT__NAMESPACE__", + setRetainRequestBodyValueFlag: () => { + // NOOP + }, + onSelect: (...args) => + console.log( // eslint-disable-line no-console + "ExamplesSelectValueRetainer: no `onSelect` function was provided", + ...args + ), + updateValue: (...args) => + console.log( // eslint-disable-line no-console + "ExamplesSelectValueRetainer: no `updateValue` function was provided", + ...args + ), + } + + constructor(props) { + super(props) + + const valueFromExample = this._getCurrentExampleValue() + + this.state = { + // user edited: last value that came from the world around us, and didn't + // match the current example's value + // internal: last value that came from user selecting an Example + [props.currentNamespace]: Map({ + lastUserEditedValue: this.props.currentUserInputValue, + lastDownstreamValue: valueFromExample, + isModifiedValueSelected: + // valueFromExample !== undefined && + this.props.userHasEditedBody || + this.props.currentUserInputValue !== valueFromExample, + }), + } + } + + componentWillUnmount() { + this.props.setRetainRequestBodyValueFlag(false) + } + + _getStateForCurrentNamespace = () => { + const { currentNamespace } = this.props + + return (this.state[currentNamespace] || Map()).toObject() + } + + _setStateForCurrentNamespace = obj => { + const { currentNamespace } = this.props + + return this._setStateForNamespace(currentNamespace, obj) + } + + _setStateForNamespace = (namespace, obj) => { + const oldStateForNamespace = this.state[namespace] || Map() + const newStateForNamespace = oldStateForNamespace.mergeDeep(obj) + return this.setState({ + [namespace]: newStateForNamespace, + }) + } + + _isCurrentUserInputSameAsExampleValue = () => { + const { currentUserInputValue } = this.props + + const valueFromExample = this._getCurrentExampleValue() + + return valueFromExample === currentUserInputValue + } + + _getValueForExample = (exampleKey, props) => { + // props are accepted so that this can be used in UNSAFE_componentWillReceiveProps, + // which has access to `nextProps` + const { examples } = props || this.props + return stringifyUnlessList( + (examples || Map({})).getIn([exampleKey, "value"]) + ) + } + + _getCurrentExampleValue = props => { + // props are accepted so that this can be used in UNSAFE_componentWillReceiveProps, + // which has access to `nextProps` + const { currentKey } = props || this.props + return this._getValueForExample(currentKey, props || this.props) + } + + _onExamplesSelect = (key, { isSyntheticChange } = {}, ...otherArgs) => { + const { + onSelect, + updateValue, + currentUserInputValue, + userHasEditedBody, + } = this.props + const { lastUserEditedValue } = this._getStateForCurrentNamespace() + + const valueFromExample = this._getValueForExample(key) + + if (key === "__MODIFIED__VALUE__") { + updateValue(stringifyUnlessList(lastUserEditedValue)) + return this._setStateForCurrentNamespace({ + isModifiedValueSelected: true, + }) + } + + if (typeof onSelect === "function") { + onSelect(key, { isSyntheticChange }, ...otherArgs) + } + + this._setStateForCurrentNamespace({ + lastDownstreamValue: valueFromExample, + isModifiedValueSelected: + (isSyntheticChange && userHasEditedBody) || + (!!currentUserInputValue && currentUserInputValue !== valueFromExample), + }) + + // we never want to send up value updates from synthetic changes + if (isSyntheticChange) return + + if (typeof updateValue === "function") { + updateValue(stringifyUnlessList(valueFromExample)) + } + } + + UNSAFE_componentWillReceiveProps(nextProps) { + // update `lastUserEditedValue` as new currentUserInput values come in + + const { + currentUserInputValue: newValue, + examples, + onSelect, + userHasEditedBody, + } = nextProps + + const { + lastUserEditedValue, + lastDownstreamValue, + } = this._getStateForCurrentNamespace() + + const valueFromCurrentExample = this._getValueForExample( + nextProps.currentKey, + nextProps + ) + + const examplesMatchingNewValue = examples.filter( + (example) => + example.get("value") === newValue || + // sometimes data is stored as a string (e.g. in Request Bodies), so + // let's check against a stringified version of our example too + stringify(example.get("value")) === newValue + ) + + if (examplesMatchingNewValue.size) { + let key + if(examplesMatchingNewValue.has(nextProps.currentKey)) + { + key = nextProps.currentKey + } else { + key = examplesMatchingNewValue.keySeq().first() + } + onSelect(key, { + isSyntheticChange: true, + }) + } else if ( + newValue !== this.props.currentUserInputValue && // value has changed + newValue !== lastUserEditedValue && // value isn't already tracked + newValue !== lastDownstreamValue // value isn't what we've seen on the other side + ) { + this.props.setRetainRequestBodyValueFlag(true) + this._setStateForNamespace(nextProps.currentNamespace, { + lastUserEditedValue: nextProps.currentUserInputValue, + isModifiedValueSelected: + userHasEditedBody || newValue !== valueFromCurrentExample, + }) + } + } + + render() { + const { + currentUserInputValue, + examples, + currentKey, + getComponent, + userHasEditedBody, + } = this.props + const { + lastDownstreamValue, + lastUserEditedValue, + isModifiedValueSelected, + } = this._getStateForCurrentNamespace() + + const ExamplesSelect = getComponent("ExamplesSelect") + + return ( + + ) + } +} diff --git a/frontend/web/api-doc/src/core/components/examples-select.jsx b/frontend/web/api-doc/src/core/components/examples-select.jsx new file mode 100644 index 0000000..7a37d3c --- /dev/null +++ b/frontend/web/api-doc/src/core/components/examples-select.jsx @@ -0,0 +1,139 @@ +/** + * @prettier + */ + +import React from "react" +import Im from "immutable" +import PropTypes from "prop-types" +import ImPropTypes from "react-immutable-proptypes" + +export default class ExamplesSelect extends React.PureComponent { + static propTypes = { + examples: ImPropTypes.map.isRequired, + onSelect: PropTypes.func, + currentExampleKey: PropTypes.string, + isModifiedValueAvailable: PropTypes.bool, + isValueModified: PropTypes.bool, + showLabels: PropTypes.bool, + } + + static defaultProps = { + examples: Im.Map({}), + onSelect: (...args) => + console.log( // eslint-disable-line no-console + // FIXME: remove before merging to master... + `DEBUG: ExamplesSelect was not given an onSelect callback`, + ...args + ), + currentExampleKey: null, + showLabels: true, + } + + _onSelect = (key, { isSyntheticChange = false } = {}) => { + if (typeof this.props.onSelect === "function") { + this.props.onSelect(key, { + isSyntheticChange, + }) + } + } + + _onDomSelect = e => { + if (typeof this.props.onSelect === "function") { + const element = e.target.selectedOptions[0] + const key = element.getAttribute("value") + + this._onSelect(key, { + isSyntheticChange: false, + }) + } + } + + getCurrentExample = () => { + const { examples, currentExampleKey } = this.props + + const currentExamplePerProps = examples.get(currentExampleKey) + + const firstExamplesKey = examples.keySeq().first() + const firstExample = examples.get(firstExamplesKey) + + return currentExamplePerProps || firstExample || Map({}) + } + + componentDidMount() { + // this is the not-so-great part of ExamplesSelect... here we're + // artificially kicking off an onSelect event in order to set a default + // value in state. the consumer has the option to avoid this by checking + // `isSyntheticEvent`, but we should really be doing this in a selector. + // TODO: clean this up + // FIXME: should this only trigger if `currentExamplesKey` is nullish? + const { onSelect, examples } = this.props + + if (typeof onSelect === "function") { + const firstExample = examples.first() + const firstExampleKey = examples.keyOf(firstExample) + + this._onSelect(firstExampleKey, { + isSyntheticChange: true, + }) + } + } + + UNSAFE_componentWillReceiveProps(nextProps) { + const { currentExampleKey, examples } = nextProps + if (examples !== this.props.examples && !examples.has(currentExampleKey)) { + // examples have changed from under us, and the currentExampleKey is no longer + // valid. + const firstExample = examples.first() + const firstExampleKey = examples.keyOf(firstExample) + + this._onSelect(firstExampleKey, { + isSyntheticChange: true, + }) + } + } + + render() { + const { + examples, + currentExampleKey, + isValueModified, + isModifiedValueAvailable, + showLabels, + } = this.props + + return ( +
+ { + showLabels ? ( + Examples: + ) : null + } + +
+ ) + } +} diff --git a/frontend/web/api-doc/src/core/components/execute.jsx b/frontend/web/api-doc/src/core/components/execute.jsx new file mode 100644 index 0000000..e6b3e1b --- /dev/null +++ b/frontend/web/api-doc/src/core/components/execute.jsx @@ -0,0 +1,103 @@ +import React, { Component } from "react" +import PropTypes from "prop-types" + +export default class Execute extends Component { + + static propTypes = { + specSelectors: PropTypes.object.isRequired, + specActions: PropTypes.object.isRequired, + operation: PropTypes.object.isRequired, + path: PropTypes.string.isRequired, + method: PropTypes.string.isRequired, + oas3Selectors: PropTypes.object.isRequired, + oas3Actions: PropTypes.object.isRequired, + onExecute: PropTypes.func, + disabled: PropTypes.bool + } + + handleValidateParameters = () => { + let { specSelectors, specActions, path, method } = this.props + specActions.validateParams([path, method]) + return specSelectors.validateBeforeExecute([path, method]) + } + + handleValidateRequestBody = () => { + let { path, method, specSelectors, oas3Selectors, oas3Actions } = this.props + let validationErrors = { + missingBodyValue: false, + missingRequiredKeys: [] + } + // context: reset errors, then (re)validate + oas3Actions.clearRequestBodyValidateError({ path, method }) + let oas3RequiredRequestBodyContentType = specSelectors.getOAS3RequiredRequestBodyContentType([path, method]) + let oas3RequestBodyValue = oas3Selectors.requestBodyValue(path, method) + let oas3ValidateBeforeExecuteSuccess = oas3Selectors.validateBeforeExecute([path, method]) + let oas3RequestContentType = oas3Selectors.requestContentType(path, method) + + if (!oas3ValidateBeforeExecuteSuccess) { + validationErrors.missingBodyValue = true + oas3Actions.setRequestBodyValidateError({ path, method, validationErrors }) + return false + } + if (!oas3RequiredRequestBodyContentType) { + return true + } + let missingRequiredKeys = oas3Selectors.validateShallowRequired({ + oas3RequiredRequestBodyContentType, + oas3RequestContentType, + oas3RequestBodyValue + }) + if (!missingRequiredKeys || missingRequiredKeys.length < 1) { + return true + } + missingRequiredKeys.forEach((missingKey) => { + validationErrors.missingRequiredKeys.push(missingKey) + }) + oas3Actions.setRequestBodyValidateError({ path, method, validationErrors }) + return false + } + + handleValidationResultPass = () => { + let { specActions, operation, path, method } = this.props + if (this.props.onExecute) { + // loading spinner + this.props.onExecute() + } + specActions.execute({ operation, path, method }) + } + + handleValidationResultFail = () => { + let { specActions, path, method } = this.props + // deferred by 40ms, to give element class change time to settle. + specActions.clearValidateParams([path, method]) + setTimeout(() => { + specActions.validateParams([path, method]) + }, 40) + } + + handleValidationResult = (isPass) => { + if (isPass) { + this.handleValidationResultPass() + } else { + this.handleValidationResultFail() + } + } + + onClick = () => { + let paramsResult = this.handleValidateParameters() + let requestBodyResult = this.handleValidateRequestBody() + let isPass = paramsResult && requestBodyResult + this.handleValidationResult(isPass) + } + + onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue([this.props.path, this.props.method], val) + + render(){ + const { disabled } = this.props + return ( + + ) + } +} diff --git a/frontend/web/api-doc/src/core/components/footer.jsx b/frontend/web/api-doc/src/core/components/footer.jsx new file mode 100644 index 0000000..7cdc5a7 --- /dev/null +++ b/frontend/web/api-doc/src/core/components/footer.jsx @@ -0,0 +1,9 @@ +import React from "react" + +export default class Footer extends React.Component { + render() { + return ( +
+ ) + } +} diff --git a/frontend/web/api-doc/src/core/components/headers.jsx b/frontend/web/api-doc/src/core/components/headers.jsx new file mode 100644 index 0000000..c717b61 --- /dev/null +++ b/frontend/web/api-doc/src/core/components/headers.jsx @@ -0,0 +1,58 @@ +import React from "react" +import PropTypes from "prop-types" +import Im from "immutable" + +const propClass = "header-example" + +export default class Headers extends React.Component { + static propTypes = { + headers: PropTypes.object.isRequired, + getComponent: PropTypes.func.isRequired + } + + render() { + let { headers, getComponent } = this.props + + const Property = getComponent("Property") + const Markdown = getComponent("Markdown", true) + + if ( !headers || !headers.size ) + return null + + return ( +
+

Headers:

+ + + + + + + + + + { + headers.entrySeq().map( ([ key, header ]) => { + if(!Im.Map.isMap(header)) { + return null + } + + const description = header.get("description") + const type = header.getIn(["schema"]) ? header.getIn(["schema", "type"]) : header.getIn(["type"]) + const schemaExample = header.getIn(["schema", "example"]) + + return ( + + + + ) + }).toArray() + } + +
NameDescriptionType
{ key }{ + !description ? null : + }{ type } { schemaExample ? : null }
+
+ ) + } +} diff --git a/frontend/web/api-doc/src/core/components/highlight-code.jsx b/frontend/web/api-doc/src/core/components/highlight-code.jsx new file mode 100644 index 0000000..d2a5e2c --- /dev/null +++ b/frontend/web/api-doc/src/core/components/highlight-code.jsx @@ -0,0 +1,89 @@ +import React, { useRef, useEffect } from "react" +import PropTypes from "prop-types" +import cx from "classnames" +import {SyntaxHighlighter, getStyle} from "core/syntax-highlighting" +import get from "lodash/get" +import isFunction from "lodash/isFunction" +import saveAs from "js-file-download" +import { CopyToClipboard } from "react-copy-to-clipboard" + +const HighlightCode = ({value, fileName, className, downloadable, getConfigs, canCopy, language}) => { + const config = isFunction(getConfigs) ? getConfigs() : null + const canSyntaxHighlight = get(config, "syntaxHighlight") !== false && get(config, "syntaxHighlight.activated", true) + const rootRef = useRef(null) + + useEffect(() => { + const childNodes = Array + .from(rootRef.current.childNodes) + .filter(node => !!node.nodeType && node.classList.contains("microlight")) + + // eslint-disable-next-line no-use-before-define + childNodes.forEach(node => node.addEventListener("mousewheel", handlePreventYScrollingBeyondElement, { passive: false })) + + return () => { + // eslint-disable-next-line no-use-before-define + childNodes.forEach(node => node.removeEventListener("mousewheel", handlePreventYScrollingBeyondElement)) + } + }, [value, className, language]) + + const handleDownload = () => { + saveAs(value, fileName) + } + + const handlePreventYScrollingBeyondElement = (e) => { + const { target, deltaY } = e + const { scrollHeight: contentHeight, offsetHeight: visibleHeight, scrollTop } = target + const scrollOffset = visibleHeight + scrollTop + const isElementScrollable = contentHeight > visibleHeight + const isScrollingPastTop = scrollTop === 0 && deltaY < 0 + const isScrollingPastBottom = scrollOffset >= contentHeight && deltaY > 0 + + if (isElementScrollable && (isScrollingPastTop || isScrollingPastBottom)) { + e.preventDefault() + } + } + + return ( +
+ {!downloadable ? null : +
+ Download +
+ } + + {canCopy && ( +
+
+ )} + + {canSyntaxHighlight + ? + {value} + + :
{value}
+ } + +
+ ) +} + +HighlightCode.propTypes = { + value: PropTypes.string.isRequired, + getConfigs: PropTypes.func.isRequired, + className: PropTypes.string, + downloadable: PropTypes.bool, + fileName: PropTypes.string, + language: PropTypes.string, + canCopy: PropTypes.bool +} + +HighlightCode.defaultProps = { + fileName: "response.txt" +} + +export default HighlightCode diff --git a/frontend/web/api-doc/src/core/components/info.jsx b/frontend/web/api-doc/src/core/components/info.jsx new file mode 100644 index 0000000..43465a4 --- /dev/null +++ b/frontend/web/api-doc/src/core/components/info.jsx @@ -0,0 +1,166 @@ +import React from "react" +import PropTypes from "prop-types" +import ImPropTypes from "react-immutable-proptypes" +import { sanitizeUrl } from "core/utils" +import { safeBuildUrl } from "core/utils/url" + + +export class InfoBasePath extends React.Component { + static propTypes = { + host: PropTypes.string, + basePath: PropTypes.string + } + + render() { + let { host, basePath } = this.props + + return ( +
+        [ Base URL: {host}{basePath} ]
+      
+ ) + } +} + + +class Contact extends React.Component { + static propTypes = { + data: PropTypes.object, + getComponent: PropTypes.func.isRequired, + specSelectors: PropTypes.object.isRequired, + selectedServer: PropTypes.string, + url: PropTypes.string.isRequired, + } + + render(){ + let { data, getComponent, selectedServer, url: specUrl} = this.props + let name = data.get("name") || "the developer" + let url = safeBuildUrl(data.get("url"), specUrl, {selectedServer}) + let email = data.get("email") + + const Link = getComponent("Link") + + return ( +
+ { url &&
{ name } - Website
} + { email && + + { url ? `Send email to ${name}` : `Contact ${name}`} + + } +
+ ) + } +} + +class License extends React.Component { + static propTypes = { + license: PropTypes.object, + getComponent: PropTypes.func.isRequired, + specSelectors: PropTypes.object.isRequired, + selectedServer: PropTypes.string, + url: PropTypes.string.isRequired, + } + + render(){ + let { license, getComponent, selectedServer, url: specUrl } = this.props + + const Link = getComponent("Link") + let name = license.get("name") || "License" + let url = safeBuildUrl(license.get("url"), specUrl, {selectedServer}) + + return ( +
+ { + url ? { name } + : { name } + } +
+ ) + } +} + +export class InfoUrl extends React.PureComponent { + static propTypes = { + url: PropTypes.string.isRequired, + getComponent: PropTypes.func.isRequired + } + + + render() { + const { url, getComponent } = this.props + + const Link = getComponent("Link") + + return { url } + } +} + +export default class Info extends React.Component { + static propTypes = { + info: PropTypes.object, + url: PropTypes.string, + host: PropTypes.string, + basePath: PropTypes.string, + externalDocs: ImPropTypes.map, + getComponent: PropTypes.func.isRequired, + oas3selectors: PropTypes.func, + selectedServer: PropTypes.string, + } + + render() { + let { info, url, host, basePath, getComponent, externalDocs, selectedServer, url: specUrl } = this.props + let version = info.get("version") + let description = info.get("description") + let title = info.get("title") + let termsOfServiceUrl = safeBuildUrl(info.get("termsOfService"), specUrl, {selectedServer}) + let contact = info.get("contact") + let license = info.get("license") + let rawExternalDocsUrl = externalDocs && externalDocs.get("url") + let externalDocsUrl = safeBuildUrl(rawExternalDocsUrl, specUrl, {selectedServer}) + let externalDocsDescription = externalDocs && externalDocs.get("description") + + const Markdown = getComponent("Markdown", true) + const Link = getComponent("Link") + const VersionStamp = getComponent("VersionStamp") + const InfoUrl = getComponent("InfoUrl") + const InfoBasePath = getComponent("InfoBasePath") + + return ( +
+
+

{ title } + { version && } +

+ { host || basePath ? : null } + { url && } +
+ +
+ +
+ + { + termsOfServiceUrl &&
+ Terms of service +
+ } + + {contact && contact.size ? : null } + {license && license.size ? : null } + { externalDocsUrl ? + {externalDocsDescription || externalDocsUrl} + : null } + +
+ ) + } + +} + +Info.propTypes = { + title: PropTypes.any, + description: PropTypes.any, + version: PropTypes.any, + url: PropTypes.string +} diff --git a/frontend/web/api-doc/src/core/components/initialized-input.jsx b/frontend/web/api-doc/src/core/components/initialized-input.jsx new file mode 100644 index 0000000..3b31686 --- /dev/null +++ b/frontend/web/api-doc/src/core/components/initialized-input.jsx @@ -0,0 +1,36 @@ +// This component provides an interface that feels like an uncontrolled input +// to consumers, while providing a `defaultValue` interface that initializes +// the input's value using JavaScript value property APIs instead of React's +// vanilla[0] implementation that uses HTML value attributes. +// +// This is useful in situations where we don't want to surface an input's value +// into the HTML/CSS-exposed side of the DOM, for example to avoid sequential +// input chaining attacks[1]. +// +// [0]: https://github.com/facebook/react/blob/baff5cc2f69d30589a5dc65b089e47765437294b/fixtures/dom/src/components/fixtures/text-inputs/README.md +// [1]: https://github.com/d0nutptr/sic + +import React from "react" +import PropTypes from "prop-types" + +export default class InitializedInput extends React.Component { + componentDidMount() { + // Set the element's `value` property (*not* the `value` attribute) + // once, on mount, if an `initialValue` is provided. + if(this.props.initialValue) { + this.inputRef.value = this.props.initialValue + } + } + + render() { + // Filter out `value` and `defaultValue`, since we have our own + // `initialValue` interface that we provide. + // eslint-disable-next-line no-unused-vars, react/prop-types + const { value, defaultValue, initialValue, ...otherProps } = this.props + return this.inputRef = c} /> + } +} + +InitializedInput.propTypes = { + initialValue: PropTypes.string +} diff --git a/frontend/web/api-doc/src/core/components/jump-to-path.jsx b/frontend/web/api-doc/src/core/components/jump-to-path.jsx new file mode 100644 index 0000000..4919bab --- /dev/null +++ b/frontend/web/api-doc/src/core/components/jump-to-path.jsx @@ -0,0 +1,9 @@ +import React from "react" + +// Nothing by default- component can be overridden by another plugin. + +export default class JumpToPath extends React.Component { + render() { + return null + } +} diff --git a/frontend/web/api-doc/src/core/components/layout-utils.jsx b/frontend/web/api-doc/src/core/components/layout-utils.jsx new file mode 100644 index 0000000..a887fbe --- /dev/null +++ b/frontend/web/api-doc/src/core/components/layout-utils.jsx @@ -0,0 +1,263 @@ +import React from "react" +import PropTypes from "prop-types" + +function xclass(...args) { + return args.filter(a => !!a).join(" ").trim() +} + +export class Container extends React.Component { + render() { + let { fullscreen, full, ...rest } = this.props + // Normal element + + if(fullscreen) + return
+ + let containerClass = "swagger-container" + (full ? "-full" : "") + return ( +
+ ) + } +} + +Container.propTypes = { + fullscreen: PropTypes.bool, + full: PropTypes.bool, + className: PropTypes.string +} + +const DEVICES = { + "mobile": "", + "tablet": "-tablet", + "desktop": "-desktop", + "large": "-hd" +} + +export class Col extends React.Component { + + render() { + const { + hide, + keepContents, + /* we don't want these in the `rest` object that passes to the final component, + since React now complains. So we extract them */ + /* eslint-disable no-unused-vars */ + mobile, + tablet, + desktop, + large, + /* eslint-enable no-unused-vars */ + ...rest + } = this.props + + if(hide && !keepContents) + return + + let classesAr = [] + + for (let device in DEVICES) { + if (!Object.prototype.hasOwnProperty.call(DEVICES, device)) { + continue + } + let deviceClass = DEVICES[device] + if(device in this.props) { + let val = this.props[device] + + if(val < 1) { + classesAr.push("none" + deviceClass) + continue + } + + classesAr.push("block" + deviceClass) + classesAr.push("col-" + val + deviceClass) + } + } + + if (hide) { + classesAr.push("hidden") + } + + let classes = xclass(rest.className, ...classesAr) + + return ( +
+ ) + } + +} + +Col.propTypes = { + hide: PropTypes.bool, + keepContents: PropTypes.bool, + mobile: PropTypes.number, + tablet: PropTypes.number, + desktop: PropTypes.number, + large: PropTypes.number, + className: PropTypes.string +} + +export class Row extends React.Component { + + render() { + return
+ } + +} + +Row.propTypes = { + className: PropTypes.string +} + +export class Button extends React.Component { + + static propTypes = { + className: PropTypes.string + } + + static defaultProps = { + className: "" + } + + render() { + return +
+ { + isExpanded &&
+
+ { + snippetGenerators.entrySeq().map(([key, gen]) => { + return (
handleGenChange(key)}> +

{gen.get("title")}

+
) + }) + } +
+
+ +
+
+ {SnippetComponent} +
+
+ } + + ) +} + +RequestSnippets.propTypes = { + request: PropTypes.object.isRequired, + requestSnippetsSelectors: PropTypes.object.isRequired, + getConfigs: PropTypes.object.isRequired, + requestSnippetsActions: PropTypes.object, +} + +export default RequestSnippets diff --git a/frontend/web/api-doc/src/core/plugins/request-snippets/selectors.js b/frontend/web/api-doc/src/core/plugins/request-snippets/selectors.js new file mode 100644 index 0000000..baec3f8 --- /dev/null +++ b/frontend/web/api-doc/src/core/plugins/request-snippets/selectors.js @@ -0,0 +1,45 @@ +import { createSelector } from "reselect" +import { Map } from "immutable" + +const state = state => state || Map() + +export const getGenerators = createSelector( + state, + state => { + const languageKeys = state + .get("languages") + const generators = state + .get("generators", Map()) + if(!languageKeys || languageKeys.isEmpty()) { + return generators + } + return generators + .filter((v, key) => languageKeys.includes(key)) + } +) + +export const getSnippetGenerators = (state) => ({ fn }) => { + const getGenFn = (key) => fn[`requestSnippetGenerator_${key}`] + return getGenerators(state) + .map((gen, key) => { + const genFn = getGenFn(key) + if(typeof genFn !== "function") { + return null + } + + return gen.set("fn", genFn) + }) + .filter(v => v) +} + +export const getActiveLanguage = createSelector( + state, + state => state + .get("activeLanguage") +) + +export const getDefaultExpanded = createSelector( + state, + state => state + .get("defaultExpanded") +) diff --git a/frontend/web/api-doc/src/core/plugins/safe-render/components/error-boundary.jsx b/frontend/web/api-doc/src/core/plugins/safe-render/components/error-boundary.jsx new file mode 100644 index 0000000..e365eaa --- /dev/null +++ b/frontend/web/api-doc/src/core/plugins/safe-render/components/error-boundary.jsx @@ -0,0 +1,50 @@ +import PropTypes from "prop-types" +import React, { Component } from "react" + +import { componentDidCatch } from "../fn" +import Fallback from "./fallback" + +export class ErrorBoundary extends Component { + static getDerivedStateFromError(error) { + return { hasError: true, error } + } + + constructor(...args) { + super(...args) + this.state = { hasError: false, error: null } + } + + componentDidCatch(error, errorInfo) { + this.props.fn.componentDidCatch(error, errorInfo) + } + + render() { + const { getComponent, targetName, children } = this.props + + if (this.state.hasError) { + const FallbackComponent = getComponent("Fallback") + return + } + + return children + } +} +ErrorBoundary.propTypes = { + targetName: PropTypes.string, + getComponent: PropTypes.func, + fn: PropTypes.object, + children: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.node), + PropTypes.node, + ]) +} +ErrorBoundary.defaultProps = { + targetName: "this component", + getComponent: () => Fallback, + fn: { + componentDidCatch, + }, + children: null, +} + +export default ErrorBoundary diff --git a/frontend/web/api-doc/src/core/plugins/safe-render/components/fallback.jsx b/frontend/web/api-doc/src/core/plugins/safe-render/components/fallback.jsx new file mode 100644 index 0000000..0b020da --- /dev/null +++ b/frontend/web/api-doc/src/core/plugins/safe-render/components/fallback.jsx @@ -0,0 +1,13 @@ +import React from "react" +import PropTypes from "prop-types" + +const Fallback = ({ name }) => ( +
+ 😱 Could not render { name === "t" ? "this component" : name }, see the console. +
+) +Fallback.propTypes = { + name: PropTypes.string.isRequired, +} + +export default Fallback diff --git a/frontend/web/api-doc/src/core/plugins/safe-render/fn.jsx b/frontend/web/api-doc/src/core/plugins/safe-render/fn.jsx new file mode 100644 index 0000000..70bc523 --- /dev/null +++ b/frontend/web/api-doc/src/core/plugins/safe-render/fn.jsx @@ -0,0 +1,32 @@ +import React, { Component } from "react" + +export const componentDidCatch = console.error + +const isClassComponent = component => component.prototype && component.prototype.isReactComponent + +export const withErrorBoundary = (getSystem) => (WrappedComponent) => { + const { getComponent, fn } = getSystem() + const ErrorBoundary = getComponent("ErrorBoundary") + const targetName = fn.getDisplayName(WrappedComponent) + + class WithErrorBoundary extends Component { + render() { + return ( + + + + ) + } + } + WithErrorBoundary.displayName = `WithErrorBoundary(${targetName})` + if (isClassComponent(WrappedComponent)) { + /** + * We need to handle case of class components defining a `mapStateToProps` public method. + * Components with `mapStateToProps` public method cannot be wrapped. + */ + WithErrorBoundary.prototype.mapStateToProps = WrappedComponent.prototype.mapStateToProps + } + + return WithErrorBoundary +} + diff --git a/frontend/web/api-doc/src/core/plugins/safe-render/index.js b/frontend/web/api-doc/src/core/plugins/safe-render/index.js new file mode 100644 index 0000000..c45f6a0 --- /dev/null +++ b/frontend/web/api-doc/src/core/plugins/safe-render/index.js @@ -0,0 +1,42 @@ +import zipObject from "lodash/zipObject" + +import ErrorBoundary from "./components/error-boundary" +import Fallback from "./components/fallback" +import { componentDidCatch, withErrorBoundary } from "./fn" + +const safeRenderPlugin = ({componentList = [], fullOverride = false} = {}) => ({ getSystem }) => { + const defaultComponentList = [ + "App", + "BaseLayout", + "VersionPragmaFilter", + "InfoContainer", + "ServersContainer", + "SchemesContainer", + "AuthorizeBtnContainer", + "FilterContainer", + "Operations", + "OperationContainer", + "parameters", + "responses", + "OperationServers", + "Models", + "ModelWrapper", + ] + const mergedComponentList = fullOverride ? componentList : [...defaultComponentList, ...componentList] + const wrapFactory = (Original, { fn }) => fn.withErrorBoundary(Original) + const wrapComponents = zipObject(mergedComponentList, Array(mergedComponentList.length).fill(wrapFactory)) + + return { + fn: { + componentDidCatch, + withErrorBoundary: withErrorBoundary(getSystem), + }, + components: { + ErrorBoundary, + Fallback, + }, + wrapComponents, + } +} + +export default safeRenderPlugin diff --git a/frontend/web/api-doc/src/core/plugins/samples/fn.js b/frontend/web/api-doc/src/core/plugins/samples/fn.js new file mode 100644 index 0000000..a140dc4 --- /dev/null +++ b/frontend/web/api-doc/src/core/plugins/samples/fn.js @@ -0,0 +1,636 @@ +import XML from "xml" +import RandExp from "randexp" +import isEmpty from "lodash/isEmpty" +import { objectify, isFunc, normalizeArray, deeplyStripKey } from "core/utils" + +import memoizeN from "../../../helpers/memoizeN" + +const generateStringFromRegex = (pattern) => { + try { + const randexp = new RandExp(pattern) + return randexp.gen() + } catch (e) { + // Invalid regex should not cause a crash (regex syntax varies across languages) + return "string" + } +} + +const primitives = { + "string": (schema) => schema.pattern ? generateStringFromRegex(schema.pattern) : "string", + "string_email": () => "user@example.com", + "string_date-time": () => new Date().toISOString(), + "string_date": () => new Date().toISOString().substring(0, 10), + "string_uuid": () => "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "string_hostname": () => "example.com", + "string_ipv4": () => "198.51.100.42", + "string_ipv6": () => "2001:0db8:5b96:0000:0000:426f:8e17:642a", + "number": () => 0, + "number_float": () => 0.0, + "integer": () => 0, + "boolean": (schema) => typeof schema.default === "boolean" ? schema.default : true +} + +const primitive = (schema) => { + schema = objectify(schema) + let { type, format } = schema + + let fn = primitives[`${type}_${format}`] || primitives[type] + + if(isFunc(fn)) + return fn(schema) + + return "Unknown Type: " + schema.type +} + +// do a couple of quick sanity tests to ensure the value +// looks like a $$ref that swagger-client generates. +const sanitizeRef = (value) => deeplyStripKey(value, "$$ref", (val) => + typeof val === "string" && val.indexOf("#") > -1) + +const objectContracts = ["maxProperties", "minProperties"] +const arrayContracts = ["minItems", "maxItems"] +const numberContracts = [ + "minimum", + "maximum", + "exclusiveMinimum", + "exclusiveMaximum" +] +const stringContracts = ["minLength", "maxLength"] + +const liftSampleHelper = (oldSchema, target, config = {}) => { + const setIfNotDefinedInTarget = (key) => { + if(target[key] === undefined && oldSchema[key] !== undefined) { + target[key] = oldSchema[key] + } + } + + [ + "example", + "default", + "enum", + "xml", + "type", + ...objectContracts, + ...arrayContracts, + ...numberContracts, + ...stringContracts, + ].forEach(key => setIfNotDefinedInTarget(key)) + + if(oldSchema.required !== undefined && Array.isArray(oldSchema.required)) { + if(target.required === undefined || !target.required.length) { + target.required = [] + } + oldSchema.required.forEach(key => { + if(target.required.includes(key)) { + return + } + target.required.push(key) + }) + } + if(oldSchema.properties) { + if(!target.properties) { + target.properties = {} + } + let props = objectify(oldSchema.properties) + for (let propName in props) { + if (!Object.prototype.hasOwnProperty.call(props, propName)) { + continue + } + if ( props[propName] && props[propName].deprecated ) { + continue + } + if ( props[propName] && props[propName].readOnly && !config.includeReadOnly ) { + continue + } + if ( props[propName] && props[propName].writeOnly && !config.includeWriteOnly ) { + continue + } + if(!target.properties[propName]) { + target.properties[propName] = props[propName] + if(!oldSchema.required && Array.isArray(oldSchema.required) && oldSchema.required.indexOf(propName) !== -1) { + if(!target.required) { + target.required = [propName] + } else { + target.required.push(propName) + } + } + } + } + } + if(oldSchema.items) { + if(!target.items) { + target.items = {} + } + target.items = liftSampleHelper(oldSchema.items, target.items, config) + } + + return target +} + +export const sampleFromSchemaGeneric = (schema, config={}, exampleOverride = undefined, respectXML = false) => { + if(schema && isFunc(schema.toJS)) + schema = schema.toJS() + let usePlainValue = exampleOverride !== undefined || schema && schema.example !== undefined || schema && schema.default !== undefined + // first check if there is the need of combining this schema with others required by allOf + const hasOneOf = !usePlainValue && schema && schema.oneOf && schema.oneOf.length > 0 + const hasAnyOf = !usePlainValue && schema && schema.anyOf && schema.anyOf.length > 0 + if(!usePlainValue && (hasOneOf || hasAnyOf)) { + const schemaToAdd = objectify(hasOneOf + ? schema.oneOf[0] + : schema.anyOf[0] + ) + liftSampleHelper(schemaToAdd, schema, config) + if(!schema.xml && schemaToAdd.xml) { + schema.xml = schemaToAdd.xml + } + if(schema.example !== undefined && schemaToAdd.example !== undefined) { + usePlainValue = true + } else if(schemaToAdd.properties) { + if(!schema.properties) { + schema.properties = {} + } + let props = objectify(schemaToAdd.properties) + for (let propName in props) { + if (!Object.prototype.hasOwnProperty.call(props, propName)) { + continue + } + if ( props[propName] && props[propName].deprecated ) { + continue + } + if ( props[propName] && props[propName].readOnly && !config.includeReadOnly ) { + continue + } + if ( props[propName] && props[propName].writeOnly && !config.includeWriteOnly ) { + continue + } + if(!schema.properties[propName]) { + schema.properties[propName] = props[propName] + if(!schemaToAdd.required && Array.isArray(schemaToAdd.required) && schemaToAdd.required.indexOf(propName) !== -1) { + if(!schema.required) { + schema.required = [propName] + } else { + schema.required.push(propName) + } + } + } + } + } + } + const _attr = {} + let { xml, type, example, properties, additionalProperties, items } = schema || {} + let { includeReadOnly, includeWriteOnly } = config + xml = xml || {} + let { name, prefix, namespace } = xml + let displayName + let res = {} + + // set xml naming and attributes + if(respectXML) { + name = name || "notagname" + // add prefix to name if exists + displayName = (prefix ? prefix + ":" : "") + name + if ( namespace ) { + //add prefix to namespace if exists + let namespacePrefix = prefix ? ( "xmlns:" + prefix ) : "xmlns" + _attr[namespacePrefix] = namespace + } + } + + // init xml default response sample obj + if(respectXML) { + res[displayName] = [] + } + + const schemaHasAny = (keys) => keys.some(key => Object.prototype.hasOwnProperty.call(schema, key)) + // try recover missing type + if(schema && !type) { + if(properties || additionalProperties || schemaHasAny(objectContracts)) { + type = "object" + } else if(items || schemaHasAny(arrayContracts)) { + type = "array" + } else if(schemaHasAny(numberContracts)) { + type = "number" + schema.type = "number" + } else if(!usePlainValue && !schema.enum){ + // implicit cover schemaHasAny(stringContracts) or A schema without a type matches any data type is: + // components: + // schemas: + // AnyValue: + // anyOf: + // - type: string + // - type: number + // - type: integer + // - type: boolean + // - type: array + // items: {} + // - type: object + // + // which would resolve to type: string + type = "string" + schema.type = "string" + } + } + + const handleMinMaxItems = (sampleArray) => { + if (schema?.maxItems !== null && schema?.maxItems !== undefined) { + sampleArray = sampleArray.slice(0, schema?.maxItems) + } + if (schema?.minItems !== null && schema?.minItems !== undefined) { + let i = 0 + while (sampleArray.length < schema?.minItems) { + sampleArray.push(sampleArray[i++ % sampleArray.length]) + } + } + return sampleArray + } + + // add to result helper init for xml or json + const props = objectify(properties) + let addPropertyToResult + let propertyAddedCounter = 0 + + const hasExceededMaxProperties = () => schema + && schema.maxProperties !== null && schema.maxProperties !== undefined + && propertyAddedCounter >= schema.maxProperties + + const requiredPropertiesToAdd = () => { + if(!schema || !schema.required) { + return 0 + } + let addedCount = 0 + if(respectXML) { + schema.required.forEach(key => addedCount += + res[key] === undefined + ? 0 + : 1 + ) + } else { + schema.required.forEach(key => addedCount += + res[displayName]?.find(x => x[key] !== undefined) === undefined + ? 0 + : 1 + ) + } + return schema.required.length - addedCount + } + + const isOptionalProperty = (propName) => { + if(!schema || !schema.required || !schema.required.length) { + return true + } + return !schema.required.includes(propName) + } + + const canAddProperty = (propName) => { + if(!schema || schema.maxProperties === null || schema.maxProperties === undefined) { + return true + } + if(hasExceededMaxProperties()) { + return false + } + if(!isOptionalProperty(propName)) { + return true + } + return (schema.maxProperties - propertyAddedCounter - requiredPropertiesToAdd()) > 0 + } + + if(respectXML) { + addPropertyToResult = (propName, overrideE = undefined) => { + if(schema && props[propName]) { + // case it is an xml attribute + props[propName].xml = props[propName].xml || {} + + if (props[propName].xml.attribute) { + const enumAttrVal = Array.isArray(props[propName].enum) + ? props[propName].enum[0] + : undefined + const attrExample = props[propName].example + const attrDefault = props[propName].default + + if(attrExample !== undefined) { + _attr[props[propName].xml.name || propName] = attrExample + } else if(attrDefault !== undefined) { + _attr[props[propName].xml.name || propName] = attrDefault + } else if(enumAttrVal !== undefined) { + _attr[props[propName].xml.name || propName] = enumAttrVal + } else { + _attr[props[propName].xml.name || propName] = primitive(props[propName]) + } + + return + } + props[propName].xml.name = props[propName].xml.name || propName + } else if(!props[propName] && additionalProperties !== false) { + // case only additionalProperty that is not defined in schema + props[propName] = { + xml: { + name: propName + } + } + } + + let t = sampleFromSchemaGeneric(schema && props[propName] || undefined, config, overrideE, respectXML) + if(!canAddProperty(propName)) { + return + } + + propertyAddedCounter++ + if (Array.isArray(t)) { + res[displayName] = res[displayName].concat(t) + } else { + res[displayName].push(t) + } + } + } else { + addPropertyToResult = (propName, overrideE) => { + if(!canAddProperty(propName)) { + return + } + if(Object.prototype.hasOwnProperty.call(schema, "discriminator") && + schema.discriminator && + Object.prototype.hasOwnProperty.call(schema.discriminator, "mapping") && + schema.discriminator.mapping && + Object.prototype.hasOwnProperty.call(schema, "$$ref") && + schema.$$ref && + schema.discriminator.propertyName === propName) { + for (let pair in schema.discriminator.mapping){ + if (schema.$$ref.search(schema.discriminator.mapping[pair]) !== -1) { + res[propName] = pair + break + } + } + } else { + res[propName] = sampleFromSchemaGeneric(props[propName], config, overrideE, respectXML) + } + propertyAddedCounter++ + } + } + + // check for plain value and if found use it to generate sample from it + if(usePlainValue) { + let sample + if(exampleOverride !== undefined) { + sample = sanitizeRef(exampleOverride) + } else if(example !== undefined) { + sample = sanitizeRef(example) + } else { + sample = sanitizeRef(schema.default) + } + + // if json just return + if(!respectXML) { + // spacial case yaml parser can not know about + if(typeof sample === "number" && type === "string") { + return `${sample}` + } + // return if sample does not need any parsing + if(typeof sample !== "string" || type === "string") { + return sample + } + // check if sample is parsable or just a plain string + try { + return JSON.parse(sample) + } catch(e) { + // sample is just plain string return it + return sample + } + } + + // recover missing type + if(!schema) { + type = Array.isArray(sample) ? "array" : typeof sample + } + + // generate xml sample recursively for array case + if(type === "array") { + if (!Array.isArray(sample)) { + if(typeof sample === "string") { + return sample + } + sample = [sample] + } + const itemSchema = schema + ? schema.items + : undefined + if(itemSchema) { + itemSchema.xml = itemSchema.xml || xml || {} + itemSchema.xml.name = itemSchema.xml.name || xml.name + } + let itemSamples = sample + .map(s => sampleFromSchemaGeneric(itemSchema, config, s, respectXML)) + itemSamples = handleMinMaxItems(itemSamples) + if(xml.wrapped) { + res[displayName] = itemSamples + if (!isEmpty(_attr)) { + res[displayName].push({_attr: _attr}) + } + } + else { + res = itemSamples + } + return res + } + + // generate xml sample recursively for object case + if(type === "object") { + // case literal example + if(typeof sample === "string") { + return sample + } + for (let propName in sample) { + if (!Object.prototype.hasOwnProperty.call(sample, propName)) { + continue + } + if (schema && props[propName] && props[propName].readOnly && !includeReadOnly) { + continue + } + if (schema && props[propName] && props[propName].writeOnly && !includeWriteOnly) { + continue + } + if (schema && props[propName] && props[propName].xml && props[propName].xml.attribute) { + _attr[props[propName].xml.name || propName] = sample[propName] + continue + } + addPropertyToResult(propName, sample[propName]) + } + if (!isEmpty(_attr)) { + res[displayName].push({_attr: _attr}) + } + + return res + } + + res[displayName] = !isEmpty(_attr) ? [{_attr: _attr}, sample] : sample + return res + } + + // use schema to generate sample + + if(type === "object") { + for (let propName in props) { + if (!Object.prototype.hasOwnProperty.call(props, propName)) { + continue + } + if ( props[propName] && props[propName].deprecated ) { + continue + } + if ( props[propName] && props[propName].readOnly && !includeReadOnly ) { + continue + } + if ( props[propName] && props[propName].writeOnly && !includeWriteOnly ) { + continue + } + addPropertyToResult(propName) + } + if (respectXML && _attr) { + res[displayName].push({_attr: _attr}) + } + + if(hasExceededMaxProperties()) { + return res + } + + if ( additionalProperties === true ) { + if(respectXML) { + res[displayName].push({additionalProp: "Anything can be here"}) + } else { + res.additionalProp1 = {} + } + propertyAddedCounter++ + } else if ( additionalProperties ) { + const additionalProps = objectify(additionalProperties) + const additionalPropSample = sampleFromSchemaGeneric(additionalProps, config, undefined, respectXML) + + if(respectXML && additionalProps.xml && additionalProps.xml.name && additionalProps.xml.name !== "notagname") + { + res[displayName].push(additionalPropSample) + } else { + const toGenerateCount = schema.minProperties !== null && schema.minProperties !== undefined && propertyAddedCounter < schema.minProperties + ? schema.minProperties - propertyAddedCounter + : 3 + for (let i = 1; i <= toGenerateCount; i++) { + if(hasExceededMaxProperties()) { + return res + } + if(respectXML) { + const temp = {} + temp["additionalProp" + i] = additionalPropSample["notagname"] + res[displayName].push(temp) + } else { + res["additionalProp" + i] = additionalPropSample + } + propertyAddedCounter++ + } + } + } + return res + } + + if(type === "array") { + if (!items) { + return + } + + let sampleArray + if(respectXML) { + items.xml = items.xml || schema?.xml || {} + items.xml.name = items.xml.name || xml.name + } + + if(Array.isArray(items.anyOf)) { + sampleArray = items.anyOf.map(i => sampleFromSchemaGeneric(liftSampleHelper(items, i, config), config, undefined, respectXML)) + } else if(Array.isArray(items.oneOf)) { + sampleArray = items.oneOf.map(i => sampleFromSchemaGeneric(liftSampleHelper(items, i, config), config, undefined, respectXML)) + } else if(!respectXML || respectXML && xml.wrapped) { + sampleArray = [sampleFromSchemaGeneric(items, config, undefined, respectXML)] + } else { + return sampleFromSchemaGeneric(items, config, undefined, respectXML) + } + sampleArray = handleMinMaxItems(sampleArray) + if(respectXML && xml.wrapped) { + res[displayName] = sampleArray + if (!isEmpty(_attr)) { + res[displayName].push({_attr: _attr}) + } + return res + } + return sampleArray + } + + let value + if (schema && Array.isArray(schema.enum)) { + //display enum first value + value = normalizeArray(schema.enum)[0] + } else if(schema) { + // display schema default + value = primitive(schema) + if(typeof value === "number") { + let min = schema.minimum + if(min !== undefined && min !== null) { + if(schema.exclusiveMinimum) { + min++ + } + value = min + } + let max = schema.maximum + if(max !== undefined && max !== null) { + if(schema.exclusiveMaximum) { + max-- + } + value = max + } + } + if(typeof value === "string") { + if (schema.maxLength !== null && schema.maxLength !== undefined) { + value = value.slice(0, schema.maxLength) + } + if (schema.minLength !== null && schema.minLength !== undefined) { + let i = 0 + while (value.length < schema.minLength) { + value += value[i++ % value.length] + } + } + } + } else { + return + } + if (type === "file") { + return + } + + if(respectXML) { + res[displayName] = !isEmpty(_attr) ? [{_attr: _attr}, value] : value + return res + } + + return value +} + +export const inferSchema = (thing) => { + if(thing.schema) + thing = thing.schema + + if(thing.properties) { + thing.type = "object" + } + + return thing // Hopefully this will have something schema like in it... `type` for example +} + +export const createXMLExample = (schema, config, o) => { + const json = sampleFromSchemaGeneric(schema, config, o, true) + if (!json) { return } + if(typeof json === "string") { + return json + } + return XML(json, { declaration: true, indent: "\t" }) +} + +export const sampleFromSchema = (schema, config, o) => + sampleFromSchemaGeneric(schema, config, o, false) + +const resolver = (arg1, arg2, arg3) => [arg1, JSON.stringify(arg2), JSON.stringify(arg3)] + +export const memoizedCreateXMLExample = memoizeN(createXMLExample, resolver) + +export const memoizedSampleFromSchema = memoizeN(sampleFromSchema, resolver) diff --git a/frontend/web/api-doc/src/core/plugins/samples/index.js b/frontend/web/api-doc/src/core/plugins/samples/index.js new file mode 100644 index 0000000..96c1223 --- /dev/null +++ b/frontend/web/api-doc/src/core/plugins/samples/index.js @@ -0,0 +1,5 @@ +import * as fn from "./fn" + +export default function () { + return { fn } +} diff --git a/frontend/web/api-doc/src/core/plugins/spec/actions.js b/frontend/web/api-doc/src/core/plugins/spec/actions.js new file mode 100644 index 0000000..b220d49 --- /dev/null +++ b/frontend/web/api-doc/src/core/plugins/spec/actions.js @@ -0,0 +1,530 @@ +import YAML, { JSON_SCHEMA } from "js-yaml" +import { Map } from "immutable" +import parseUrl from "url-parse" +import { serializeError } from "serialize-error" +import isString from "lodash/isString" +import debounce from "lodash/debounce" +import set from "lodash/set" +import { paramToValue, isEmptyValue } from "core/utils" + +// Actions conform to FSA (flux-standard-actions) +// {type: string,payload: Any|Error, meta: obj, error: bool} + +export const UPDATE_SPEC = "spec_update_spec" +export const UPDATE_URL = "spec_update_url" +export const UPDATE_JSON = "spec_update_json" +export const UPDATE_PARAM = "spec_update_param" +export const UPDATE_EMPTY_PARAM_INCLUSION = "spec_update_empty_param_inclusion" +export const VALIDATE_PARAMS = "spec_validate_param" +export const SET_RESPONSE = "spec_set_response" +export const SET_REQUEST = "spec_set_request" +export const SET_MUTATED_REQUEST = "spec_set_mutated_request" +export const LOG_REQUEST = "spec_log_request" +export const CLEAR_RESPONSE = "spec_clear_response" +export const CLEAR_REQUEST = "spec_clear_request" +export const CLEAR_VALIDATE_PARAMS = "spec_clear_validate_param" +export const UPDATE_OPERATION_META_VALUE = "spec_update_operation_meta_value" +export const UPDATE_RESOLVED = "spec_update_resolved" +export const UPDATE_RESOLVED_SUBTREE = "spec_update_resolved_subtree" +export const SET_SCHEME = "set_scheme" + +const toStr = (str) => isString(str) ? str : "" + +export function updateSpec(spec) { + const cleanSpec = (toStr(spec)).replace(/\t/g, " ") + if(typeof spec === "string") { + return { + type: UPDATE_SPEC, + payload: cleanSpec + } + } +} + +export function updateResolved(spec) { + return { + type: UPDATE_RESOLVED, + payload: spec + } +} + +export function updateUrl(url) { + return {type: UPDATE_URL, payload: url} +} + +export function updateJsonSpec(json) { + return {type: UPDATE_JSON, payload: json} +} + +export const parseToJson = (str) => ({specActions, specSelectors, errActions}) => { + let { specStr } = specSelectors + + let json = null + try { + str = str || specStr() + errActions.clear({ source: "parser" }) + json = YAML.load(str, { schema: JSON_SCHEMA }) + } catch(e) { + // TODO: push error to state + console.error(e) + return errActions.newSpecErr({ + source: "parser", + level: "error", + message: e.reason, + line: e.mark && e.mark.line ? e.mark.line + 1 : undefined + }) + } + if(json && typeof json === "object") { + return specActions.updateJsonSpec(json) + } + return {} +} + +let hasWarnedAboutResolveSpecDeprecation = false + +export const resolveSpec = (json, url) => ({specActions, specSelectors, errActions, fn: { fetch, resolve, AST = {} }, getConfigs}) => { + if(!hasWarnedAboutResolveSpecDeprecation) { + console.warn(`specActions.resolveSpec is deprecated since v3.10.0 and will be removed in v4.0.0; use requestResolvedSubtree instead!`) + hasWarnedAboutResolveSpecDeprecation = true + } + + const { + modelPropertyMacro, + parameterMacro, + requestInterceptor, + responseInterceptor + } = getConfigs() + + if(typeof(json) === "undefined") { + json = specSelectors.specJson() + } + if(typeof(url) === "undefined") { + url = specSelectors.url() + } + + let getLineNumberForPath = AST.getLineNumberForPath ? AST.getLineNumberForPath : () => undefined + + let specStr = specSelectors.specStr() + + return resolve({ + fetch, + spec: json, + baseDoc: url, + modelPropertyMacro, + parameterMacro, + requestInterceptor, + responseInterceptor + }).then( ({spec, errors}) => { + errActions.clear({ + type: "thrown" + }) + if(Array.isArray(errors) && errors.length > 0) { + let preparedErrors = errors + .map(err => { + console.error(err) + err.line = err.fullPath ? getLineNumberForPath(specStr, err.fullPath) : null + err.path = err.fullPath ? err.fullPath.join(".") : null + err.level = "error" + err.type = "thrown" + err.source = "resolver" + Object.defineProperty(err, "message", { enumerable: true, value: err.message }) + return err + }) + errActions.newThrownErrBatch(preparedErrors) + } + + return specActions.updateResolved(spec) + }) +} + +let requestBatch = [] + +const debResolveSubtrees = debounce(async () => { + const system = requestBatch.system // Just a reference to the "latest" system + + if(!system) { + console.error("debResolveSubtrees: don't have a system to operate on, aborting.") + return + } + const { + errActions, + errSelectors, + fn: { + resolveSubtree, + fetch, + AST = {} + }, + specSelectors, + specActions, + } = system + + if(!resolveSubtree) { + console.error("Error: Swagger-Client did not provide a `resolveSubtree` method, doing nothing.") + return + } + + let getLineNumberForPath = AST.getLineNumberForPath ? AST.getLineNumberForPath : () => undefined + + const specStr = specSelectors.specStr() + + const { + modelPropertyMacro, + parameterMacro, + requestInterceptor, + responseInterceptor + } = system.getConfigs() + + try { + var batchResult = await requestBatch.reduce(async (prev, path) => { + const { resultMap, specWithCurrentSubtrees } = await prev + const { errors, spec } = await resolveSubtree(specWithCurrentSubtrees, path, { + baseDoc: specSelectors.url(), + modelPropertyMacro, + parameterMacro, + requestInterceptor, + responseInterceptor + }) + + if(errSelectors.allErrors().size) { + errActions.clearBy(err => { + // keep if... + return err.get("type") !== "thrown" // it's not a thrown error + || err.get("source") !== "resolver" // it's not a resolver error + || !err.get("fullPath").every((key, i) => key === path[i] || path[i] === undefined) // it's not within the path we're resolving + }) + } + + if(Array.isArray(errors) && errors.length > 0) { + let preparedErrors = errors + .map(err => { + err.line = err.fullPath ? getLineNumberForPath(specStr, err.fullPath) : null + err.path = err.fullPath ? err.fullPath.join(".") : null + err.level = "error" + err.type = "thrown" + err.source = "resolver" + Object.defineProperty(err, "message", { enumerable: true, value: err.message }) + return err + }) + errActions.newThrownErrBatch(preparedErrors) + } + + if (spec && specSelectors.isOAS3() && path[0] === "components" && path[1] === "securitySchemes") { + // Resolve OIDC URLs if present + await Promise.all(Object.values(spec) + .filter((scheme) => scheme.type === "openIdConnect") + .map(async (oidcScheme) => { + const req = { + url: oidcScheme.openIdConnectUrl, + requestInterceptor: requestInterceptor, + responseInterceptor: responseInterceptor + } + try { + const res = await fetch(req) + if (res instanceof Error || res.status >= 400) { + console.error(res.statusText + " " + req.url) + } else { + oidcScheme.openIdConnectData = JSON.parse(res.text) + } + } catch (e) { + console.error(e) + } + })) + } + set(resultMap, path, spec) + set(specWithCurrentSubtrees, path, spec) + + return { + resultMap, + specWithCurrentSubtrees + } + }, Promise.resolve({ + resultMap: (specSelectors.specResolvedSubtree([]) || Map()).toJS(), + specWithCurrentSubtrees: specSelectors.specJson().toJS() + })) + + delete requestBatch.system + requestBatch = [] // Clear stack + } catch(e) { + console.error(e) + } + + specActions.updateResolvedSubtree([], batchResult.resultMap) +}, 35) + +export const requestResolvedSubtree = path => system => { + // poor-man's array comparison + // if this ever inadequate, this should be rewritten to use Im.List + const isPathAlreadyBatched = requestBatch + .map(arr => arr.join("@@")) + .indexOf(path.join("@@")) > -1 + + if(isPathAlreadyBatched) { + return + } + + requestBatch.push(path) + requestBatch.system = system + debResolveSubtrees() +} + +export function changeParam( path, paramName, paramIn, value, isXml ){ + return { + type: UPDATE_PARAM, + payload:{ path, value, paramName, paramIn, isXml } + } +} + +export function changeParamByIdentity( pathMethod, param, value, isXml ){ + return { + type: UPDATE_PARAM, + payload:{ path: pathMethod, param, value, isXml } + } +} + +export const updateResolvedSubtree = (path, value) => { + return { + type: UPDATE_RESOLVED_SUBTREE, + payload: { path, value } + } +} + +export const invalidateResolvedSubtreeCache = () => { + return { + type: UPDATE_RESOLVED_SUBTREE, + payload: { + path: [], + value: Map() + } + } +} + +export const validateParams = ( payload, isOAS3 ) =>{ + return { + type: VALIDATE_PARAMS, + payload:{ + pathMethod: payload, + isOAS3 + } + } +} + +export const updateEmptyParamInclusion = ( pathMethod, paramName, paramIn, includeEmptyValue ) =>{ + return { + type: UPDATE_EMPTY_PARAM_INCLUSION, + payload:{ + pathMethod, + paramName, + paramIn, + includeEmptyValue + } + } +} + +export function clearValidateParams( payload ){ + return { + type: CLEAR_VALIDATE_PARAMS, + payload:{ pathMethod: payload } + } +} + +export function changeConsumesValue(path, value) { + return { + type: UPDATE_OPERATION_META_VALUE, + payload:{ path, value, key: "consumes_value" } + } +} + +export function changeProducesValue(path, value) { + return { + type: UPDATE_OPERATION_META_VALUE, + payload:{ path, value, key: "produces_value" } + } +} + +export const setResponse = ( path, method, res ) => { + return { + payload: { path, method, res }, + type: SET_RESPONSE + } +} + +export const setRequest = ( path, method, req ) => { + return { + payload: { path, method, req }, + type: SET_REQUEST + } +} + +export const setMutatedRequest = ( path, method, req ) => { + return { + payload: { path, method, req }, + type: SET_MUTATED_REQUEST + } +} + +// This is for debugging, remove this comment if you depend on this action +export const logRequest = (req) => { + return { + payload: req, + type: LOG_REQUEST + } +} + +// Actually fire the request via fn.execute +// (For debugging) and ease of testing +export const executeRequest = (req) => + ({fn, specActions, specSelectors, getConfigs, oas3Selectors}) => { + let { pathName, method, operation } = req + let { requestInterceptor, responseInterceptor } = getConfigs() + + + let op = operation.toJS() + + // ensure that explicitly-included params are in the request + + if (operation && operation.get("parameters")) { + operation.get("parameters") + .filter(param => param && param.get("allowEmptyValue") === true) + .forEach(param => { + if (specSelectors.parameterInclusionSettingFor([pathName, method], param.get("name"), param.get("in"))) { + req.parameters = req.parameters || {} + const paramValue = paramToValue(param, req.parameters) + + // if the value is falsy or an empty Immutable iterable... + if(!paramValue || (paramValue && paramValue.size === 0)) { + // set it to empty string, so Swagger Client will treat it as + // present but empty. + req.parameters[param.get("name")] = "" + } + } + }) + } + + // if url is relative, parseUrl makes it absolute by inferring from `window.location` + req.contextUrl = parseUrl(specSelectors.url()).toString() + + if(op && op.operationId) { + req.operationId = op.operationId + } else if(op && pathName && method) { + req.operationId = fn.opId(op, pathName, method) + } + + if(specSelectors.isOAS3()) { + const namespace = `${pathName}:${method}` + + req.server = oas3Selectors.selectedServer(namespace) || oas3Selectors.selectedServer() + + const namespaceVariables = oas3Selectors.serverVariables({ + server: req.server, + namespace + }).toJS() + const globalVariables = oas3Selectors.serverVariables({ server: req.server }).toJS() + + req.serverVariables = Object.keys(namespaceVariables).length ? namespaceVariables : globalVariables + + req.requestContentType = oas3Selectors.requestContentType(pathName, method) + req.responseContentType = oas3Selectors.responseContentType(pathName, method) || "*/*" + const requestBody = oas3Selectors.requestBodyValue(pathName, method) + const requestBodyInclusionSetting = oas3Selectors.requestBodyInclusionSetting(pathName, method) + + if(requestBody && requestBody.toJS) { + req.requestBody = requestBody + .map( + (val) => { + if (Map.isMap(val)) { + return val.get("value") + } + return val + } + ) + .filter( + (value, key) => (Array.isArray(value) + ? value.length !== 0 + : !isEmptyValue(value) + ) || requestBodyInclusionSetting.get(key) + ) + .toJS() + } else { + req.requestBody = requestBody + } + } + + let parsedRequest = Object.assign({}, req) + parsedRequest = fn.buildRequest(parsedRequest) + + specActions.setRequest(req.pathName, req.method, parsedRequest) + + let requestInterceptorWrapper = async (r) => { + let mutatedRequest = await requestInterceptor.apply(this, [r]) + let parsedMutatedRequest = Object.assign({}, mutatedRequest) + specActions.setMutatedRequest(req.pathName, req.method, parsedMutatedRequest) + return mutatedRequest + } + + req.requestInterceptor = requestInterceptorWrapper + req.responseInterceptor = responseInterceptor + + // track duration of request + const startTime = Date.now() + + + return fn.execute(req) + .then( res => { + res.duration = Date.now() - startTime + specActions.setResponse(req.pathName, req.method, res) + } ) + .catch( + err => { + // console.error(err) + if(err.message === "Failed to fetch") { + err.name = "" + err.message = "**Failed to fetch.** \n**Possible Reasons:** \n - CORS \n - Network Failure \n - URL scheme must be \"http\" or \"https\" for CORS request." + } + specActions.setResponse(req.pathName, req.method, { + error: true, err: serializeError(err) + }) + } + ) + } + + +// I'm using extras as a way to inject properties into the final, `execute` method - It's not great. Anyone have a better idea? @ponelat +export const execute = ( { path, method, ...extras }={} ) => (system) => { + let { fn:{fetch}, specSelectors, specActions } = system + let spec = specSelectors.specJsonWithResolvedSubtrees().toJS() + let scheme = specSelectors.operationScheme(path, method) + let { requestContentType, responseContentType } = specSelectors.contentTypeValues([path, method]).toJS() + let isXml = /xml/i.test(requestContentType) + let parameters = specSelectors.parameterValues([path, method], isXml).toJS() + + return specActions.executeRequest({ + ...extras, + fetch, + spec, + pathName: path, + method, parameters, + requestContentType, + scheme, + responseContentType + }) +} + +export function clearResponse (path, method) { + return { + type: CLEAR_RESPONSE, + payload:{ path, method } + } +} + +export function clearRequest (path, method) { + return { + type: CLEAR_REQUEST, + payload:{ path, method } + } +} + +export function setScheme (scheme, path, method) { + return { + type: SET_SCHEME, + payload: { scheme, path, method } + } +} diff --git a/frontend/web/api-doc/src/core/plugins/spec/index.js b/frontend/web/api-doc/src/core/plugins/spec/index.js new file mode 100644 index 0000000..4f3aab2 --- /dev/null +++ b/frontend/web/api-doc/src/core/plugins/spec/index.js @@ -0,0 +1,17 @@ +import reducers from "./reducers" +import * as actions from "./actions" +import * as selectors from "./selectors" +import * as wrapActions from "./wrap-actions" + +export default function() { + return { + statePlugins: { + spec: { + wrapActions, + reducers, + actions, + selectors + } + } + } +} diff --git a/frontend/web/api-doc/src/core/plugins/spec/reducers.js b/frontend/web/api-doc/src/core/plugins/spec/reducers.js new file mode 100644 index 0000000..e9eba19 --- /dev/null +++ b/frontend/web/api-doc/src/core/plugins/spec/reducers.js @@ -0,0 +1,177 @@ +import { fromJS, List } from "immutable" +import { fromJSOrdered, validateParam, paramToValue } from "core/utils" +import win from "../../window" + +// selector-in-reducer is suboptimal, but `operationWithMeta` is more of a helper +import { + specJsonWithResolvedSubtrees, + parameterValues, + parameterInclusionSettingFor, +} from "./selectors" + +import { + UPDATE_SPEC, + UPDATE_URL, + UPDATE_JSON, + UPDATE_PARAM, + UPDATE_EMPTY_PARAM_INCLUSION, + VALIDATE_PARAMS, + SET_RESPONSE, + SET_REQUEST, + SET_MUTATED_REQUEST, + UPDATE_RESOLVED, + UPDATE_RESOLVED_SUBTREE, + UPDATE_OPERATION_META_VALUE, + CLEAR_RESPONSE, + CLEAR_REQUEST, + CLEAR_VALIDATE_PARAMS, + SET_SCHEME +} from "./actions" +import { paramToIdentifier } from "../../utils" + +export default { + + [UPDATE_SPEC]: (state, action) => { + return (typeof action.payload === "string") + ? state.set("spec", action.payload) + : state + }, + + [UPDATE_URL]: (state, action) => { + return state.set("url", action.payload+"") + }, + + [UPDATE_JSON]: (state, action) => { + return state.set("json", fromJSOrdered(action.payload)) + }, + + [UPDATE_RESOLVED]: (state, action) => { + return state.setIn(["resolved"], fromJSOrdered(action.payload)) + }, + + [UPDATE_RESOLVED_SUBTREE]: (state, action) => { + const { value, path } = action.payload + return state.setIn(["resolvedSubtrees", ...path], fromJSOrdered(value)) + }, + + [UPDATE_PARAM]: ( state, {payload} ) => { + let { path: pathMethod, paramName, paramIn, param, value, isXml } = payload + + let paramKey = param ? paramToIdentifier(param) : `${paramIn}.${paramName}` + + const valueKey = isXml ? "value_xml" : "value" + + return state.setIn( + ["meta", "paths", ...pathMethod, "parameters", paramKey, valueKey], + value + ) + }, + + [UPDATE_EMPTY_PARAM_INCLUSION]: ( state, {payload} ) => { + let { pathMethod, paramName, paramIn, includeEmptyValue } = payload + + if(!paramName || !paramIn) { + console.warn("Warning: UPDATE_EMPTY_PARAM_INCLUSION could not generate a paramKey.") + return state + } + + const paramKey = `${paramIn}.${paramName}` + + return state.setIn( + ["meta", "paths", ...pathMethod, "parameter_inclusions", paramKey], + includeEmptyValue + ) + }, + + [VALIDATE_PARAMS]: ( state, { payload: { pathMethod, isOAS3 } } ) => { + const op = specJsonWithResolvedSubtrees(state).getIn(["paths", ...pathMethod]) + const paramValues = parameterValues(state, pathMethod).toJS() + + return state.updateIn(["meta", "paths", ...pathMethod, "parameters"], fromJS({}), paramMeta => { + return op.get("parameters", List()).reduce((res, param) => { + const value = paramToValue(param, paramValues) + const isEmptyValueIncluded = parameterInclusionSettingFor(state, pathMethod, param.get("name"), param.get("in")) + const errors = validateParam(param, value, { + bypassRequiredCheck: isEmptyValueIncluded, + isOAS3, + }) + return res.setIn([paramToIdentifier(param), "errors"], fromJS(errors)) + }, paramMeta) + }) + }, + [CLEAR_VALIDATE_PARAMS]: ( state, { payload: { pathMethod } } ) => { + return state.updateIn( [ "meta", "paths", ...pathMethod, "parameters" ], fromJS([]), parameters => { + return parameters.map(param => param.set("errors", fromJS([]))) + }) + }, + + [SET_RESPONSE]: (state, { payload: { res, path, method } } ) =>{ + let result + if ( res.error ) { + result = Object.assign({ + error: true, + name: res.err.name, + message: res.err.message, + statusCode: res.err.statusCode + }, res.err.response) + } else { + result = res + } + + // Ensure headers + result.headers = result.headers || {} + + let newState = state.setIn( [ "responses", path, method ], fromJSOrdered(result) ) + + // ImmutableJS messes up Blob. Needs to reset its value. + if (win.Blob && res.data instanceof win.Blob) { + newState = newState.setIn( [ "responses", path, method, "text" ], res.data) + } + return newState + }, + + [SET_REQUEST]: (state, { payload: { req, path, method } } ) =>{ + return state.setIn( [ "requests", path, method ], fromJSOrdered(req)) + }, + + [SET_MUTATED_REQUEST]: (state, { payload: { req, path, method } } ) =>{ + return state.setIn( [ "mutatedRequests", path, method ], fromJSOrdered(req)) + }, + + [UPDATE_OPERATION_META_VALUE]: (state, { payload: { path, value, key } }) => { + // path is a pathMethod tuple... can't change the name now. + let operationPath = ["paths", ...path] + let metaPath = ["meta", "paths", ...path] + + if( + !state.getIn(["json", ...operationPath]) + && !state.getIn(["resolved", ...operationPath]) + && !state.getIn(["resolvedSubtrees", ...operationPath]) + ) { + // do nothing if the operation does not exist + return state + } + + return state.setIn([...metaPath, key], fromJS(value)) + }, + + [CLEAR_RESPONSE]: (state, { payload: { path, method } } ) =>{ + return state.deleteIn( [ "responses", path, method ]) + }, + + [CLEAR_REQUEST]: (state, { payload: { path, method } } ) =>{ + return state.deleteIn( [ "requests", path, method ]) + }, + + [SET_SCHEME]: (state, { payload: { scheme, path, method } } ) =>{ + if ( path && method ) { + return state.setIn( [ "scheme", path, method ], scheme) + } + + if (!path && !method) { + return state.setIn( [ "scheme", "_defaultScheme" ], scheme) + } + + } + +} diff --git a/frontend/web/api-doc/src/core/plugins/spec/selectors.js b/frontend/web/api-doc/src/core/plugins/spec/selectors.js new file mode 100644 index 0000000..97a4788 --- /dev/null +++ b/frontend/web/api-doc/src/core/plugins/spec/selectors.js @@ -0,0 +1,538 @@ +import { createSelector } from "reselect" +import { sorters } from "core/utils" +import { fromJS, Set, Map, OrderedMap, List } from "immutable" +import { paramToIdentifier } from "../../utils" + +const DEFAULT_TAG = "default" + +const OPERATION_METHODS = [ + "get", "put", "post", "delete", "options", "head", "patch", "trace" +] + +const state = state => { + return state || Map() +} + +export const lastError = createSelector( + state, + spec => spec.get("lastError") +) + +export const url = createSelector( + state, + spec => spec.get("url") +) + +export const specStr = createSelector( + state, + spec => spec.get("spec") || "" +) + +export const specSource = createSelector( + state, + spec => spec.get("specSource") || "not-editor" +) + +export const specJson = createSelector( + state, + spec => spec.get("json", Map()) +) + +export const specResolved = createSelector( + state, + spec => spec.get("resolved", Map()) +) + +export const specResolvedSubtree = (state, path) => { + return state.getIn(["resolvedSubtrees", ...path], undefined) +} + +const mergerFn = (oldVal, newVal) => { + if(Map.isMap(oldVal) && Map.isMap(newVal)) { + if(newVal.get("$$ref")) { + // resolver artifacts indicated that this key was directly resolved + // so we should drop the old value entirely + return newVal + } + + return OrderedMap().mergeWith( + mergerFn, + oldVal, + newVal + ) + } + + return newVal +} + +export const specJsonWithResolvedSubtrees = createSelector( + state, + spec => OrderedMap().mergeWith( + mergerFn, + spec.get("json"), + spec.get("resolvedSubtrees") + ) +) + +// Default Spec ( as an object ) +export const spec = state => { + let res = specJson(state) + return res +} + +export const isOAS3 = createSelector( + // isOAS3 is stubbed out here to work around an issue with injecting more selectors + // in the OAS3 plugin, and to ensure that the function is always available. + // It's not perfect, but our hybrid (core+plugin code) implementation for OAS3 + // needs this. //KS + spec, + () => false +) + +export const info = createSelector( + spec, + spec => returnSelfOrNewMap(spec && spec.get("info")) +) + +export const externalDocs = createSelector( + spec, + spec => returnSelfOrNewMap(spec && spec.get("externalDocs")) +) + +export const version = createSelector( + info, + info => info && info.get("version") +) + +export const semver = createSelector( + version, + version => /v?([0-9]*)\.([0-9]*)\.([0-9]*)/i.exec(version).slice(1) +) + +export const paths = createSelector( + specJsonWithResolvedSubtrees, + spec => spec.get("paths") +) + +export const operations = createSelector( + paths, + paths => { + if(!paths || paths.size < 1) + return List() + + let list = List() + + if(!paths || !paths.forEach) { + return List() + } + + paths.forEach((path, pathName) => { + if(!path || !path.forEach) { + return {} + } + path.forEach((operation, method) => { + if(OPERATION_METHODS.indexOf(method) < 0) { + return + } + list = list.push(fromJS({ + path: pathName, + method, + operation, + id: `${method}-${pathName}` + })) + }) + }) + + return list + } +) + +export const consumes = createSelector( + spec, + spec => Set(spec.get("consumes")) +) + +export const produces = createSelector( + spec, + spec => Set(spec.get("produces")) +) + +export const security = createSelector( + spec, + spec => spec.get("security", List()) +) + +export const securityDefinitions = createSelector( + spec, + spec => spec.get("securityDefinitions") +) + + +export const findDefinition = ( state, name ) => { + const resolvedRes = state.getIn(["resolvedSubtrees", "definitions", name], null) + const unresolvedRes = state.getIn(["json", "definitions", name], null) + return resolvedRes || unresolvedRes || null +} + +export const definitions = createSelector( + spec, + spec => { + const res = spec.get("definitions") + return Map.isMap(res) ? res : Map() + } +) + +export const basePath = createSelector( + spec, + spec => spec.get("basePath") +) + +export const host = createSelector( + spec, + spec => spec.get("host") +) + +export const schemes = createSelector( + spec, + spec => spec.get("schemes", Map()) +) + +export const operationsWithRootInherited = createSelector( + operations, + consumes, + produces, + (operations, consumes, produces) => { + return operations.map( ops => ops.update("operation", op => { + if(op) { + if(!Map.isMap(op)) { return } + return op.withMutations( op => { + if ( !op.get("consumes") ) { + op.update("consumes", a => Set(a).merge(consumes)) + } + if ( !op.get("produces") ) { + op.update("produces", a => Set(a).merge(produces)) + } + return op + }) + } else { + // return something with Immutable methods + return Map() + } + + })) + } +) + +export const tags = createSelector( + spec, + json => { + const tags = json.get("tags", List()) + return List.isList(tags) ? tags.filter(tag => Map.isMap(tag)) : List() + } +) + +export const tagDetails = (state, tag) => { + let currentTags = tags(state) || List() + return currentTags.filter(Map.isMap).find(t => t.get("name") === tag, Map()) +} + +export const operationsWithTags = createSelector( + operationsWithRootInherited, + tags, + (operations, tags) => { + return operations.reduce( (taggedMap, op) => { + let tags = Set(op.getIn(["operation","tags"])) + if(tags.count() < 1) + return taggedMap.update(DEFAULT_TAG, List(), ar => ar.push(op)) + return tags.reduce( (res, tag) => res.update(tag, List(), (ar) => ar.push(op)), taggedMap ) + }, tags.reduce( (taggedMap, tag) => { + return taggedMap.set(tag.get("name"), List()) + } , OrderedMap())) + } +) + +export const taggedOperations = (state) => ({ getConfigs }) => { + let { tagsSorter, operationsSorter } = getConfigs() + return operationsWithTags(state) + .sortBy( + (val, key) => key, // get the name of the tag to be passed to the sorter + (tagA, tagB) => { + let sortFn = (typeof tagsSorter === "function" ? tagsSorter : sorters.tagsSorter[ tagsSorter ]) + return (!sortFn ? null : sortFn(tagA, tagB)) + } + ) + .map((ops, tag) => { + let sortFn = (typeof operationsSorter === "function" ? operationsSorter : sorters.operationsSorter[ operationsSorter ]) + let operations = (!sortFn ? ops : ops.sort(sortFn)) + + return Map({ tagDetails: tagDetails(state, tag), operations: operations }) + }) +} + +export const responses = createSelector( + state, + state => state.get( "responses", Map() ) +) + +export const requests = createSelector( + state, + state => state.get( "requests", Map() ) +) + +export const mutatedRequests = createSelector( + state, + state => state.get( "mutatedRequests", Map() ) +) + +export const responseFor = (state, path, method) => { + return responses(state).getIn([path, method], null) +} + +export const requestFor = (state, path, method) => { + return requests(state).getIn([path, method], null) +} + +export const mutatedRequestFor = (state, path, method) => { + return mutatedRequests(state).getIn([path, method], null) +} + +export const allowTryItOutFor = () => { + // This is just a hook for now. + return true +} + +export const parameterWithMetaByIdentity = (state, pathMethod, param) => { + const opParams = specJsonWithResolvedSubtrees(state).getIn(["paths", ...pathMethod, "parameters"], OrderedMap()) + const metaParams = state.getIn(["meta", "paths", ...pathMethod, "parameters"], OrderedMap()) + + const mergedParams = opParams.map((currentParam) => { + const inNameKeyedMeta = metaParams.get(`${param.get("in")}.${param.get("name")}`) + const hashKeyedMeta = metaParams.get(`${param.get("in")}.${param.get("name")}.hash-${param.hashCode()}`) + return OrderedMap().merge( + currentParam, + inNameKeyedMeta, + hashKeyedMeta + ) + }) + return mergedParams.find(curr => curr.get("in") === param.get("in") && curr.get("name") === param.get("name"), OrderedMap()) +} + +export const parameterInclusionSettingFor = (state, pathMethod, paramName, paramIn) => { + const paramKey = `${paramIn}.${paramName}` + return state.getIn(["meta", "paths", ...pathMethod, "parameter_inclusions", paramKey], false) +} + + +export const parameterWithMeta = (state, pathMethod, paramName, paramIn) => { + const opParams = specJsonWithResolvedSubtrees(state).getIn(["paths", ...pathMethod, "parameters"], OrderedMap()) + const currentParam = opParams.find(param => param.get("in") === paramIn && param.get("name") === paramName, OrderedMap()) + return parameterWithMetaByIdentity(state, pathMethod, currentParam) +} + +export const operationWithMeta = (state, path, method) => { + const op = specJsonWithResolvedSubtrees(state).getIn(["paths", path, method], OrderedMap()) + const meta = state.getIn(["meta", "paths", path, method], OrderedMap()) + + const mergedParams = op.get("parameters", List()).map((param) => { + return parameterWithMetaByIdentity(state, [path, method], param) + }) + + return OrderedMap() + .merge(op, meta) + .set("parameters", mergedParams) +} + +// Get the parameter value by parameter name +export function getParameter(state, pathMethod, name, inType) { + pathMethod = pathMethod || [] + let params = state.getIn(["meta", "paths", ...pathMethod, "parameters"], fromJS([])) + return params.find( (p) => { + return Map.isMap(p) && p.get("name") === name && p.get("in") === inType + }) || Map() // Always return a map +} + +export const hasHost = createSelector( + spec, + spec => { + const host = spec.get("host") + return typeof host === "string" && host.length > 0 && host[0] !== "/" + } +) + +// Get the parameter values, that the user filled out +export function parameterValues(state, pathMethod, isXml) { + pathMethod = pathMethod || [] + let paramValues = operationWithMeta(state, ...pathMethod).get("parameters", List()) + return paramValues.reduce( (hash, p) => { + let value = isXml && p.get("in") === "body" ? p.get("value_xml") : p.get("value") + return hash.set(paramToIdentifier(p, { allowHashes: false }), value) + }, fromJS({})) +} + +// True if any parameter includes `in: ?` +export function parametersIncludeIn(parameters, inValue="") { + if(List.isList(parameters)) { + return parameters.some( p => Map.isMap(p) && p.get("in") === inValue ) + } +} + +// True if any parameter includes `type: ?` +export function parametersIncludeType(parameters, typeValue="") { + if(List.isList(parameters)) { + return parameters.some( p => Map.isMap(p) && p.get("type") === typeValue ) + } +} + +// Get the consumes/produces value that the user selected +export function contentTypeValues(state, pathMethod) { + pathMethod = pathMethod || [] + let op = specJsonWithResolvedSubtrees(state).getIn(["paths", ...pathMethod], fromJS({})) + let meta = state.getIn(["meta", "paths", ...pathMethod], fromJS({})) + let producesValue = currentProducesFor(state, pathMethod) + + const parameters = op.get("parameters") || new List() + + const requestContentType = ( + meta.get("consumes_value") ? meta.get("consumes_value") + : parametersIncludeType(parameters, "file") ? "multipart/form-data" + : parametersIncludeType(parameters, "formData") ? "application/x-www-form-urlencoded" + : undefined + ) + + return fromJS({ + requestContentType, + responseContentType: producesValue + }) +} + +// Get the currently selected produces value for an operation +export function currentProducesFor(state, pathMethod) { + pathMethod = pathMethod || [] + + const operation = specJsonWithResolvedSubtrees(state).getIn([ "paths", ...pathMethod], null) + + if(operation === null) { + // return nothing if the operation does not exist + return + } + + const currentProducesValue = state.getIn(["meta", "paths", ...pathMethod, "produces_value"], null) + const firstProducesArrayItem = operation.getIn(["produces", 0], null) + + return currentProducesValue || firstProducesArrayItem || "application/json" + +} + +// Get the produces options for an operation +export function producesOptionsFor(state, pathMethod) { + pathMethod = pathMethod || [] + + const spec = specJsonWithResolvedSubtrees(state) + const operation = spec.getIn([ "paths", ...pathMethod], null) + + if(operation === null) { + // return nothing if the operation does not exist + return + } + + const [path] = pathMethod + + const operationProduces = operation.get("produces", null) + const pathItemProduces = spec.getIn(["paths", path, "produces"], null) + const globalProduces = spec.getIn(["produces"], null) + + return operationProduces || pathItemProduces || globalProduces +} + +// Get the consumes options for an operation +export function consumesOptionsFor(state, pathMethod) { + pathMethod = pathMethod || [] + + const spec = specJsonWithResolvedSubtrees(state) + const operation = spec.getIn(["paths", ...pathMethod], null) + + if (operation === null) { + // return nothing if the operation does not exist + return + } + + const [path] = pathMethod + + const operationConsumes = operation.get("consumes", null) + const pathItemConsumes = spec.getIn(["paths", path, "consumes"], null) + const globalConsumes = spec.getIn(["consumes"], null) + + return operationConsumes || pathItemConsumes || globalConsumes +} + +export const operationScheme = ( state, path, method ) => { + let url = state.get("url") + let matchResult = url.match(/^([a-z][a-z0-9+\-.]*):/) + let urlScheme = Array.isArray(matchResult) ? matchResult[1] : null + + return state.getIn(["scheme", path, method]) || state.getIn(["scheme", "_defaultScheme"]) || urlScheme || "" +} + +export const canExecuteScheme = ( state, path, method ) => { + return ["http", "https"].indexOf(operationScheme(state, path, method)) > -1 +} + +export const validationErrors = (state, pathMethod) => { + pathMethod = pathMethod || [] + let paramValues = state.getIn(["meta", "paths", ...pathMethod, "parameters"], fromJS([])) + const result = [] + + paramValues.forEach( (p) => { + let errors = p.get("errors") + if ( errors && errors.count() ) { + errors.forEach( e => result.push(e)) + } + }) + + return result +} + +export const validateBeforeExecute = (state, pathMethod) => { + return validationErrors(state, pathMethod).length === 0 +} + +export const getOAS3RequiredRequestBodyContentType = (state, pathMethod) => { + let requiredObj = { + requestBody: false, + requestContentType: {} + } + let requestBody = state.getIn(["resolvedSubtrees", "paths", ...pathMethod, "requestBody"], fromJS([])) + if (requestBody.size < 1) { + return requiredObj + } + if (requestBody.getIn(["required"])) { + requiredObj.requestBody = requestBody.getIn(["required"]) + } + requestBody.getIn(["content"]).entrySeq().forEach((contentType) => { // e.g application/json + const key = contentType[0] + if (contentType[1].getIn(["schema", "required"])) { + const val = contentType[1].getIn(["schema", "required"]).toJS() + requiredObj.requestContentType[key] = val + } + }) + return requiredObj +} + +export const isMediaTypeSchemaPropertiesEqual = ( state, pathMethod, currentMediaType, targetMediaType) => { + if((currentMediaType || targetMediaType) && currentMediaType === targetMediaType ) { + return true + } + let requestBodyContent = state.getIn(["resolvedSubtrees", "paths", ...pathMethod, "requestBody", "content"], fromJS([])) + if (requestBodyContent.size < 2 || !currentMediaType || !targetMediaType) { + // nothing to compare + return false + } + let currentMediaTypeSchemaProperties = requestBodyContent.getIn([currentMediaType, "schema", "properties"], fromJS([])) + let targetMediaTypeSchemaProperties = requestBodyContent.getIn([targetMediaType, "schema", "properties"], fromJS([])) + return !!currentMediaTypeSchemaProperties.equals(targetMediaTypeSchemaProperties) +} + +function returnSelfOrNewMap(obj) { + // returns obj if obj is an Immutable map, else returns a new Map + return Map.isMap(obj) ? obj : new Map() +} diff --git a/frontend/web/api-doc/src/core/plugins/spec/wrap-actions.js b/frontend/web/api-doc/src/core/plugins/spec/wrap-actions.js new file mode 100644 index 0000000..506e5e9 --- /dev/null +++ b/frontend/web/api-doc/src/core/plugins/spec/wrap-actions.js @@ -0,0 +1,38 @@ +import get from "lodash/get" + +export const updateSpec = (ori, {specActions}) => (...args) => { + ori(...args) + specActions.parseToJson(...args) +} + +export const updateJsonSpec = (ori, {specActions}) => (...args) => { + ori(...args) + + specActions.invalidateResolvedSubtreeCache() + + // Trigger resolution of any path-level $refs. + const [json] = args + const pathItems = get(json, ["paths"]) || {} + const pathItemKeys = Object.keys(pathItems) + + pathItemKeys.forEach(k => { + const val = get(pathItems, [k]) + + if(val.$ref) { + specActions.requestResolvedSubtree(["paths", k]) + } + }) + + // Trigger resolution of any securitySchemes-level $refs. + specActions.requestResolvedSubtree(["components", "securitySchemes"]) +} + +// Log the request ( just for debugging, shouldn't affect prod ) +export const executeRequest = (ori, { specActions }) => (req) => { + specActions.logRequest(req) + return ori(req) +} + +export const validateParams = (ori, { specSelectors }) => (req) => { + return ori(req, specSelectors.isOAS3()) +} diff --git a/frontend/web/api-doc/src/core/plugins/swagger-js/configs-wrap-actions.js b/frontend/web/api-doc/src/core/plugins/swagger-js/configs-wrap-actions.js new file mode 100644 index 0000000..34d0e15 --- /dev/null +++ b/frontend/web/api-doc/src/core/plugins/swagger-js/configs-wrap-actions.js @@ -0,0 +1,8 @@ +export const loaded = (ori, system) => (...args) => { + ori(...args) + const value = system.getConfigs().withCredentials + + if(value !== undefined) { + system.fn.fetch.withCredentials = typeof value === "string" ? (value === "true") : !!value + } +} diff --git a/frontend/web/api-doc/src/core/plugins/swagger-js/index.js b/frontend/web/api-doc/src/core/plugins/swagger-js/index.js new file mode 100644 index 0000000..cf08cf0 --- /dev/null +++ b/frontend/web/api-doc/src/core/plugins/swagger-js/index.js @@ -0,0 +1,39 @@ +import resolve from "swagger-client/es/resolver" +import { execute, buildRequest } from "swagger-client/es/execute" +import Http, { makeHttp, serializeRes } from "swagger-client/es/http" +import resolveSubtree from "swagger-client/es/subtree-resolver" +import { opId } from "swagger-client/es/helpers" +import { loaded } from "./configs-wrap-actions" + +export default function({ configs, getConfigs }) { + return { + fn: { + fetch: makeHttp(Http, configs.preFetch, configs.postFetch), + buildRequest, + execute, + resolve, + resolveSubtree: (obj, path, opts, ...rest) => { + if(opts === undefined) { + const freshConfigs = getConfigs() + opts = { + modelPropertyMacro: freshConfigs.modelPropertyMacro, + parameterMacro: freshConfigs.parameterMacro, + requestInterceptor: freshConfigs.requestInterceptor, + responseInterceptor: freshConfigs.responseInterceptor + } + } + + return resolveSubtree(obj, path, opts, ...rest) + }, + serializeRes, + opId + }, + statePlugins: { + configs: { + wrapActions: { + loaded, + } + } + }, + } +} diff --git a/frontend/web/api-doc/src/core/plugins/util/index.js b/frontend/web/api-doc/src/core/plugins/util/index.js new file mode 100644 index 0000000..033288a --- /dev/null +++ b/frontend/web/api-doc/src/core/plugins/util/index.js @@ -0,0 +1,7 @@ +import { shallowEqualKeys } from "core/utils" + +export default function() { + return { + fn: { shallowEqualKeys } + } +} diff --git a/frontend/web/api-doc/src/core/plugins/view/fn.js b/frontend/web/api-doc/src/core/plugins/view/fn.js new file mode 100644 index 0000000..65d22f4 --- /dev/null +++ b/frontend/web/api-doc/src/core/plugins/view/fn.js @@ -0,0 +1 @@ +export const getDisplayName = (WrappedComponent) => WrappedComponent.displayName || WrappedComponent.name || "Component" diff --git a/frontend/web/api-doc/src/core/plugins/view/index.js b/frontend/web/api-doc/src/core/plugins/view/index.js new file mode 100644 index 0000000..113f77f --- /dev/null +++ b/frontend/web/api-doc/src/core/plugins/view/index.js @@ -0,0 +1,34 @@ +import { memoize } from "core/utils" + +import { getComponent, render, withMappedContainer } from "./root-injects" +import { getDisplayName } from "./fn" +import memoizeN from "../../../helpers/memoizeN" + +const memoizeForGetComponent = (fn) => { + const resolver = (...args) => JSON.stringify(args) + return memoize(fn, resolver) +} + +const memoizeForWithMappedContainer = (fn) => { + const resolver = (...args) => args + return memoizeN(fn, resolver) +} + +const viewPlugin = ({getComponents, getStore, getSystem}) => { + // getComponent should be passed into makeMappedContainer, _already_ memoized... otherwise we have a big performance hit ( think, really big ) + const memGetComponent = memoizeForGetComponent(getComponent(getSystem, getStore, getComponents)) + const memMakeMappedContainer = memoizeForWithMappedContainer(withMappedContainer(getSystem, getStore, memGetComponent)) + + return { + rootInjects: { + getComponent: memGetComponent, + makeMappedContainer: memMakeMappedContainer, + render: render(getSystem, getStore, getComponent, getComponents), + }, + fn: { + getDisplayName, + }, + } +} + +export default viewPlugin diff --git a/frontend/web/api-doc/src/core/plugins/view/root-injects.jsx b/frontend/web/api-doc/src/core/plugins/view/root-injects.jsx new file mode 100644 index 0000000..ce93122 --- /dev/null +++ b/frontend/web/api-doc/src/core/plugins/view/root-injects.jsx @@ -0,0 +1,115 @@ +import React, { Component } from "react" +import ReactDOM from "react-dom" +import { compose } from "redux" +import { connect, Provider } from "react-redux" +import omit from "lodash/omit" +import identity from "lodash/identity" + +const withSystem = (getSystem) => (WrappedComponent) => { + const { fn } = getSystem() + + class WithSystem extends Component { + render() { + return + } + } + WithSystem.displayName = `WithSystem(${fn.getDisplayName(WrappedComponent)})` + return WithSystem +} + +const withRoot = (getSystem, reduxStore) => (WrappedComponent) => { + const { fn } = getSystem() + + class WithRoot extends Component { + render() { + return ( + + + + ) + } + } + WithRoot.displayName = `WithRoot(${fn.getDisplayName(WrappedComponent)})` + return WithRoot +} + +const withConnect = (getSystem, WrappedComponent, reduxStore) => { + const mapStateToProps = (state, ownProps) => { + const props = {...ownProps, ...getSystem()} + const customMapStateToProps = WrappedComponent.prototype?.mapStateToProps || (state => ({state})) + return customMapStateToProps(state, props) + } + + return compose( + reduxStore ? withRoot(getSystem, reduxStore) : identity, + connect(mapStateToProps), + withSystem(getSystem), + )(WrappedComponent) +} + +const handleProps = (getSystem, mapping, props, oldProps) => { + for (const prop in mapping) { + const fn = mapping[prop] + + if (typeof fn === "function") { + fn(props[prop], oldProps[prop], getSystem()) + } + } +} + +export const withMappedContainer = (getSystem, getStore, memGetComponent) => (componentName, mapping) => { + const { fn } = getSystem() + const WrappedComponent = memGetComponent(componentName, "root") + + class WithMappedContainer extends Component { + constructor(props, context) { + super(props, context) + handleProps(getSystem, mapping, props, {}) + } + + UNSAFE_componentWillReceiveProps(nextProps) { + handleProps(getSystem, mapping, nextProps, this.props) + } + + render() { + const cleanProps = omit(this.props, mapping ? Object.keys(mapping) : []) + return + } + } + WithMappedContainer.displayName = `WithMappedContainer(${fn.getDisplayName(WrappedComponent)})` + return WithMappedContainer +} + +export const render = (getSystem, getStore, getComponent, getComponents) => (domNode) => { + const App = getComponent(getSystem, getStore, getComponents)("App", "root") + ReactDOM.render(, domNode) +} + +export const getComponent = (getSystem, getStore, getComponents) => (componentName, container, config = {}) => { + + if (typeof componentName !== "string") + throw new TypeError("Need a string, to fetch a component. Was given a " + typeof componentName) + + // getComponent has a config object as a third, optional parameter + // using the config object requires the presence of the second parameter, container + // e.g. getComponent("JsonSchema_string_whatever", false, { failSilently: true }) + const component = getComponents(componentName) + + if (!component) { + if (!config.failSilently) { + getSystem().log.warn("Could not find component:", componentName) + } + return null + } + + if(!container) { + return component + } + + if(container === "root") { + return withConnect(getSystem, component, getStore()) + } + + // container == truthy + return withConnect(getSystem, component) +} diff --git a/frontend/web/api-doc/src/core/presets/apis.js b/frontend/web/api-doc/src/core/presets/apis.js new file mode 100644 index 0000000..14afc62 --- /dev/null +++ b/frontend/web/api-doc/src/core/presets/apis.js @@ -0,0 +1,12 @@ +import BasePreset from "./base" +import OAS3Plugin from "../plugins/oas3" + +// Just the base, for now. + +export default function PresetApis() { + + return [ + BasePreset, + OAS3Plugin + ] +} diff --git a/frontend/web/api-doc/src/core/presets/base.js b/frontend/web/api-doc/src/core/presets/base.js new file mode 100644 index 0000000..ddfe333 --- /dev/null +++ b/frontend/web/api-doc/src/core/presets/base.js @@ -0,0 +1,202 @@ +import err from "core/plugins/err" +import layout from "core/plugins/layout" +import spec from "core/plugins/spec" +import view from "core/plugins/view" +import samples from "core/plugins/samples" +import requestSnippets from "core/plugins/request-snippets" +import logs from "core/plugins/logs" +import swaggerJs from "core/plugins/swagger-js" +import auth from "core/plugins/auth" +import util from "core/plugins/util" +import downloadUrlPlugin from "core/plugins/download-url" +import configsPlugin from "core/plugins/configs" +import deepLinkingPlugin from "core/plugins/deep-linking" +import filter from "core/plugins/filter" +import onComplete from "core/plugins/on-complete" +import safeRender from "core/plugins/safe-render" + +import OperationContainer from "core/containers/OperationContainer" + +import App from "core/components/app" +import AuthorizationPopup from "core/components/auth/authorization-popup" +import AuthorizeBtn from "core/components/auth/authorize-btn" +import AuthorizeBtnContainer from "core/containers/authorize-btn" +import AuthorizeOperationBtn from "core/components/auth/authorize-operation-btn" +import Auths from "core/components/auth/auths" +import AuthItem from "core/components/auth/auth-item" +import AuthError from "core/components/auth/error" +import ApiKeyAuth from "core/components/auth/api-key-auth" +import BasicAuth from "core/components/auth/basic-auth" +import Example from "core/components/example" +import ExamplesSelect from "core/components/examples-select" +import ExamplesSelectValueRetainer from "core/components/examples-select-value-retainer" +import Oauth2 from "core/components/auth/oauth2" +import Clear from "core/components/clear" +import LiveResponse from "core/components/live-response" +import OnlineValidatorBadge from "core/components/online-validator-badge" +import Operations from "core/components/operations" +import OperationTag from "core/components/operation-tag" +import Operation from "core/components/operation" +import OperationSummary from "core/components/operation-summary" +import OperationSummaryMethod from "core/components/operation-summary-method" +import OperationSummaryPath from "core/components/operation-summary-path" +import OperationExt from "core/components/operation-extensions" +import OperationExtRow from "core/components/operation-extension-row" +import HighlightCode from "core/components/highlight-code" +import Responses from "core/components/responses" +import Response from "core/components/response" +import ResponseExtension from "core/components/response-extension" +import ResponseBody from "core/components/response-body" +import { Parameters } from "core/components/parameters" +import ParameterExt from "core/components/parameter-extension" +import ParameterIncludeEmpty from "core/components/parameter-include-empty" +import ParameterRow from "core/components/parameter-row" +import Execute from "core/components/execute" +import Headers from "core/components/headers" +import Errors from "core/components/errors" +import ContentType from "core/components/content-type" +import Overview from "core/components/overview" +import InitializedInput from "core/components/initialized-input" +import Info, { + InfoUrl, + InfoBasePath +} from "core/components/info" +import InfoContainer from "core/containers/info" +import JumpToPath from "core/components/jump-to-path" +import CopyToClipboardBtn from "core/components/copy-to-clipboard-btn" +import Footer from "core/components/footer" +import FilterContainer from "core/containers/filter" +import ParamBody from "core/components/param-body" +import Curl from "core/components/curl" +import Schemes from "core/components/schemes" +import SchemesContainer from "core/containers/schemes" +import ModelCollapse from "core/components/model-collapse" +import ModelExample from "core/components/model-example" +import ModelWrapper from "core/components/model-wrapper" +import Model from "core/components/model" +import Models from "core/components/models" +import EnumModel from "core/components/enum-model" +import ObjectModel from "core/components/object-model" +import ArrayModel from "core/components/array-model" +import PrimitiveModel from "core/components/primitive-model" +import Property from "core/components/property" +import TryItOutButton from "core/components/try-it-out-button" +import VersionPragmaFilter from "core/components/version-pragma-filter" +import VersionStamp from "core/components/version-stamp" +import DeepLink from "core/components/deep-link" +import SvgAssets from "core/components/svg-assets" + +import Markdown from "core/components/providers/markdown" + +import BaseLayout from "core/components/layouts/base" + +import * as LayoutUtils from "core/components/layout-utils" +import * as JsonSchemaComponents from "core/json-schema-components" + +export default function() { + + let coreComponents = { + components: { + App, + authorizationPopup: AuthorizationPopup, + authorizeBtn: AuthorizeBtn, + AuthorizeBtnContainer, + authorizeOperationBtn: AuthorizeOperationBtn, + auths: Auths, + AuthItem: AuthItem, + authError: AuthError, + oauth2: Oauth2, + apiKeyAuth: ApiKeyAuth, + basicAuth: BasicAuth, + clear: Clear, + liveResponse: LiveResponse, + InitializedInput, + info: Info, + InfoContainer, + JumpToPath, + CopyToClipboardBtn, + onlineValidatorBadge: OnlineValidatorBadge, + operations: Operations, + operation: Operation, + OperationSummary, + OperationSummaryMethod, + OperationSummaryPath, + highlightCode: HighlightCode, + responses: Responses, + response: Response, + ResponseExtension: ResponseExtension, + responseBody: ResponseBody, + parameters: Parameters, + parameterRow: ParameterRow, + execute: Execute, + headers: Headers, + errors: Errors, + contentType: ContentType, + overview: Overview, + footer: Footer, + FilterContainer, + ParamBody: ParamBody, + curl: Curl, + schemes: Schemes, + SchemesContainer, + modelExample: ModelExample, + ModelWrapper, + ModelCollapse, + Model, + Models, + EnumModel, + ObjectModel, + ArrayModel, + PrimitiveModel, + Property, + TryItOutButton, + Markdown, + BaseLayout, + VersionPragmaFilter, + VersionStamp, + OperationExt, + OperationExtRow, + ParameterExt, + ParameterIncludeEmpty, + OperationTag, + OperationContainer, + DeepLink, + InfoUrl, + InfoBasePath, + SvgAssets, + Example, + ExamplesSelect, + ExamplesSelectValueRetainer, + } + } + + let formComponents = { + components: LayoutUtils + } + + let jsonSchemaComponents = { + components: JsonSchemaComponents + } + + return [ + configsPlugin, + util, + logs, + view, + spec, + err, + layout, + samples, + coreComponents, + formComponents, + swaggerJs, + jsonSchemaComponents, + auth, + downloadUrlPlugin, + deepLinkingPlugin, + filter, + onComplete, + requestSnippets, + safeRender(), + ] +} diff --git a/frontend/web/api-doc/src/core/proptypes.js b/frontend/web/api-doc/src/core/proptypes.js new file mode 100644 index 0000000..d669e6f --- /dev/null +++ b/frontend/web/api-doc/src/core/proptypes.js @@ -0,0 +1,16 @@ +import PropTypes from "prop-types" + +// Takes a list and proptype, and returns a PropType.shape({ [item]: propType }) +const mapListToPropTypeShape = (list, propType) => PropTypes.shape( + list.reduce((shape, propName) => { + shape[propName] = propType + return shape +}, {})) + + +export const arrayOrString = PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string), + PropTypes.string, +]) + +export const objectWithFuncs = list => mapListToPropTypeShape(list, PropTypes.func.isRequired) diff --git a/frontend/web/api-doc/src/core/syntax-highlighting.js b/frontend/web/api-doc/src/core/syntax-highlighting.js new file mode 100644 index 0000000..86f79bd --- /dev/null +++ b/frontend/web/api-doc/src/core/syntax-highlighting.js @@ -0,0 +1,38 @@ +import SyntaxHighlighter from "react-syntax-highlighter/dist/esm/light" +import js from "react-syntax-highlighter/dist/esm/languages/hljs/javascript" +import json from "react-syntax-highlighter/dist/esm/languages/hljs/json" +import xml from "react-syntax-highlighter/dist/esm/languages/hljs/xml" +import bash from "react-syntax-highlighter/dist/esm/languages/hljs/bash" +import yaml from "react-syntax-highlighter/dist/esm/languages/hljs/yaml" +import http from "react-syntax-highlighter/dist/esm/languages/hljs/http" +import powershell from "react-syntax-highlighter/dist/esm/languages/hljs/powershell" +import javascript from "react-syntax-highlighter/dist/esm/languages/hljs/javascript" + +import agate from "react-syntax-highlighter/dist/esm/styles/hljs/agate" +import arta from "react-syntax-highlighter/dist/esm/styles/hljs/arta" +import monokai from "react-syntax-highlighter/dist/esm/styles/hljs/monokai" +import nord from "react-syntax-highlighter/dist/esm/styles/hljs/nord" +import obsidian from "react-syntax-highlighter/dist/esm/styles/hljs/obsidian" +import tomorrowNight from "react-syntax-highlighter/dist/esm/styles/hljs/tomorrow-night" + +SyntaxHighlighter.registerLanguage("json", json) +SyntaxHighlighter.registerLanguage("js", js) +SyntaxHighlighter.registerLanguage("xml", xml) +SyntaxHighlighter.registerLanguage("yaml", yaml) +SyntaxHighlighter.registerLanguage("http", http) +SyntaxHighlighter.registerLanguage("bash", bash) +SyntaxHighlighter.registerLanguage("powershell", powershell) +SyntaxHighlighter.registerLanguage("javascript", javascript) + +const styles = {agate, arta, monokai, nord, obsidian, "tomorrow-night": tomorrowNight} +export const availableStyles = Object.keys(styles) + +export const getStyle = name => { + if (!availableStyles.includes(name)) { + console.warn(`Request style '${name}' is not available, returning default instead`) + return agate + } + return styles[name] +} + +export {SyntaxHighlighter, styles} diff --git a/frontend/web/api-doc/src/core/system.js b/frontend/web/api-doc/src/core/system.js new file mode 100644 index 0000000..a1d5508 --- /dev/null +++ b/frontend/web/api-doc/src/core/system.js @@ -0,0 +1,509 @@ +import React from "react" +import { createStore, applyMiddleware, bindActionCreators, compose } from "redux" +import Im, { fromJS, Map } from "immutable" +import deepExtend from "deep-extend" +import { combineReducers } from "redux-immutable" +import { serializeError } from "serialize-error" +import merge from "lodash/merge" +import { NEW_THROWN_ERR } from "corePlugins/err/actions" +import win from "core/window" + +import { systemThunkMiddleware, isFn, objMap, objReduce, isObject, isArray, isFunc } from "core/utils" + +const idFn = a => a + +// Apply middleware that gets sandwitched between `dispatch` and the reducer function(s) +function createStoreWithMiddleware(rootReducer, initialState, getSystem) { + + let middlwares = [ + // createLogger( { + // stateTransformer: state => state && state.toJS() + // } ), + systemThunkMiddleware( getSystem ) + ] + + const composeEnhancers = win.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose + + return createStore(rootReducer, initialState, composeEnhancers( + applyMiddleware( ...middlwares ) + )) +} + +export default class Store { + + constructor(opts={}) { + deepExtend(this, { + state: {}, + plugins: [], + pluginsOptions: {}, + system: { + configs: {}, + fn: {}, + components: {}, + rootInjects: {}, + statePlugins: {} + }, + boundSystem: {}, + toolbox: {} + }, opts) + + this.getSystem = this._getSystem.bind(this) + + // Bare system (nothing in it, besides the state) + this.store = configureStore(idFn, fromJS(this.state), this.getSystem ) + + // will be the system + Im, we can add more tools when we need to + this.buildSystem(false) + + // Bootstrap plugins + this.register(this.plugins) + } + + getStore() { + return this.store + } + + register(plugins, rebuild=true) { + var pluginSystem = combinePlugins(plugins, this.getSystem(), this.pluginsOptions) + systemExtend(this.system, pluginSystem) + if(rebuild) { + this.buildSystem() + } + + const needAnotherRebuild = callAfterLoad.call(this.system, plugins, this.getSystem()) + + if(needAnotherRebuild) { + this.buildSystem() + } + } + + buildSystem(buildReducer=true) { + let dispatch = this.getStore().dispatch + let getState = this.getStore().getState + + this.boundSystem = Object.assign({}, + this.getRootInjects(), + this.getWrappedAndBoundActions(dispatch), + this.getWrappedAndBoundSelectors(getState, this.getSystem), + this.getStateThunks(getState), + this.getFn(), + this.getConfigs() + ) + + if(buildReducer) + this.rebuildReducer() + } + + _getSystem() { + return this.boundSystem + } + + getRootInjects() { + return Object.assign({ + getSystem: this.getSystem, + getStore: this.getStore.bind(this), + getComponents: this.getComponents.bind(this), + getState: this.getStore().getState, + getConfigs: this._getConfigs.bind(this), + Im, + React + }, this.system.rootInjects || {}) + } + + _getConfigs(){ + return this.system.configs + } + + getConfigs() { + return { + configs: this.system.configs + } + } + + setConfigs(configs) { + this.system.configs = configs + } + + rebuildReducer() { + this.store.replaceReducer(buildReducer(this.system.statePlugins)) + } + + /** + * Generic getter from system.statePlugins + * + */ + getType(name) { + let upName = name[0].toUpperCase() + name.slice(1) + return objReduce(this.system.statePlugins, (val, namespace) => { + let thing = val[name] + if(thing) + return {[namespace+upName]: thing} + }) + } + + getSelectors() { + return this.getType("selectors") + } + + getActions() { + let actionHolders = this.getType("actions") + + return objMap(actionHolders, (actions) => { + return objReduce(actions, (action, actionName) => { + if(isFn(action)) + return {[actionName]: action} + }) + }) + } + + getWrappedAndBoundActions(dispatch) { + let actionGroups = this.getBoundActions(dispatch) + return objMap(actionGroups, (actions, actionGroupName) => { + let wrappers = this.system.statePlugins[actionGroupName.slice(0,-7)].wrapActions + if(wrappers) { + return objMap(actions, (action, actionName) => { + let wrap = wrappers[actionName] + if(!wrap) { + return action + } + + if(!Array.isArray(wrap)) { + wrap = [wrap] + } + return wrap.reduce((acc, fn) => { + let newAction = (...args) => { + return fn(acc, this.getSystem())(...args) + } + if(!isFn(newAction)) { + throw new TypeError("wrapActions needs to return a function that returns a new function (ie the wrapped action)") + } + return wrapWithTryCatch(newAction) + }, action || Function.prototype) + }) + } + return actions + }) + } + + getWrappedAndBoundSelectors(getState, getSystem) { + let selectorGroups = this.getBoundSelectors(getState, getSystem) + return objMap(selectorGroups, (selectors, selectorGroupName) => { + let stateName = [selectorGroupName.slice(0, -9)] // selectors = 9 chars + let wrappers = this.system.statePlugins[stateName].wrapSelectors + if(wrappers) { + return objMap(selectors, (selector, selectorName) => { + let wrap = wrappers[selectorName] + if(!wrap) { + return selector + } + + if(!Array.isArray(wrap)) { + wrap = [wrap] + } + return wrap.reduce((acc, fn) => { + let wrappedSelector = (...args) => { + return fn(acc, this.getSystem())(getState().getIn(stateName), ...args) + } + if(!isFn(wrappedSelector)) { + throw new TypeError("wrapSelector needs to return a function that returns a new function (ie the wrapped action)") + } + return wrappedSelector + }, selector || Function.prototype) + }) + } + return selectors + }) + } + + getStates(state) { + return Object.keys(this.system.statePlugins).reduce((obj, key) => { + obj[key] = state.get(key) + return obj + }, {}) + } + + getStateThunks(getState) { + return Object.keys(this.system.statePlugins).reduce((obj, key) => { + obj[key] = ()=> getState().get(key) + return obj + }, {}) + } + + getFn() { + return { + fn: this.system.fn + } + } + + getComponents(component) { + const res = this.system.components[component] + + if(Array.isArray(res)) { + return res.reduce((ori, wrapper) => { + return wrapper(ori, this.getSystem()) + }) + } + if(typeof component !== "undefined") { + return this.system.components[component] + } + + return this.system.components + } + + getBoundSelectors(getState, getSystem) { + return objMap(this.getSelectors(), (obj, key) => { + let stateName = [key.slice(0, -9)] // selectors = 9 chars + const getNestedState = ()=> getState().getIn(stateName) + + return objMap(obj, (fn) => { + return (...args) => { + let res = wrapWithTryCatch(fn).apply(null, [getNestedState(), ...args]) + + // If a selector returns a function, give it the system - for advanced usage + if(typeof(res) === "function") + res = wrapWithTryCatch(res)(getSystem()) + + return res + } + }) + }) + } + + getBoundActions(dispatch) { + + dispatch = dispatch || this.getStore().dispatch + + const actions = this.getActions() + + const process = creator =>{ + if( typeof( creator ) !== "function" ) { + return objMap(creator, prop => process(prop)) + } + + return ( ...args )=>{ + var action = null + try{ + action = creator( ...args ) + } + catch( e ){ + action = {type: NEW_THROWN_ERR, error: true, payload: serializeError(e) } + } + finally{ + return action // eslint-disable-line no-unsafe-finally + } + } + + } + return objMap(actions, actionCreator => bindActionCreators( process( actionCreator ), dispatch ) ) + } + + getMapStateToProps() { + return () => { + return Object.assign({}, this.getSystem()) + } + } + + getMapDispatchToProps(extras) { + return (dispatch) => { + return deepExtend({}, this.getWrappedAndBoundActions(dispatch), this.getFn(), extras) + } + } + +} + +function combinePlugins(plugins, toolbox, pluginOptions) { + if(isObject(plugins) && !isArray(plugins)) { + return merge({}, plugins) + } + + if(isFunc(plugins)) { + return combinePlugins(plugins(toolbox), toolbox, pluginOptions) + } + + if(isArray(plugins)) { + const dest = pluginOptions.pluginLoadType === "chain" ? toolbox.getComponents() : {} + + return plugins + .map(plugin => combinePlugins(plugin, toolbox, pluginOptions)) + .reduce(systemExtend, dest) + } + + return {} +} + +function callAfterLoad(plugins, system, { hasLoaded } = {}) { + let calledSomething = hasLoaded + if(isObject(plugins) && !isArray(plugins)) { + if(typeof plugins.afterLoad === "function") { + calledSomething = true + wrapWithTryCatch(plugins.afterLoad).call(this, system) + } + } + + if(isFunc(plugins)) + return callAfterLoad.call(this, plugins(system), system, { hasLoaded: calledSomething }) + + if(isArray(plugins)) { + return plugins.map(plugin => callAfterLoad.call(this, plugin, system, { hasLoaded: calledSomething })) + } + + return calledSomething +} + +// Wraps deepExtend, to account for certain fields, being wrappers. +// Ie: we need to convert some fields into arrays, and append to them. +// Rather than overwrite +function systemExtend(dest={}, src={}) { + + if(!isObject(dest)) { + return {} + } + if(!isObject(src)) { + return dest + } + + // Wrap components + // Parses existing components in the system, and prepares them for wrapping via getComponents + if(src.wrapComponents) { + objMap(src.wrapComponents, (wrapperFn, key) => { + const ori = dest.components && dest.components[key] + if(ori && Array.isArray(ori)) { + dest.components[key] = ori.concat([wrapperFn]) + delete src.wrapComponents[key] + } else if(ori) { + dest.components[key] = [ori, wrapperFn] + delete src.wrapComponents[key] + } + }) + + if(!Object.keys(src.wrapComponents).length) { + // only delete wrapComponents if we've matched all of our wrappers to components + // this handles cases where the component to wrap may be out of our scope, + // but a higher recursive `combinePlugins` call will be able to handle it. + delete src.wrapComponents + } + } + + + // Account for wrapActions, make it an array and append to it + // Modifies `src` + // 80% of this code is just safe traversal. We need to address that ( ie: use a lib ) + const { statePlugins } = dest + if(isObject(statePlugins)) { + for(let namespace in statePlugins) { + const namespaceObj = statePlugins[namespace] + if(!isObject(namespaceObj)) { + continue + } + + const { wrapActions, wrapSelectors } = namespaceObj + + // process action wrapping + if (isObject(wrapActions)) { + for(let actionName in wrapActions) { + let action = wrapActions[actionName] + + // This should only happen if dest is the first plugin, since invocations after that will ensure its an array + if(!Array.isArray(action)) { + action = [action] + wrapActions[actionName] = action // Put the value inside an array + } + + if(src && src.statePlugins && src.statePlugins[namespace] && src.statePlugins[namespace].wrapActions && src.statePlugins[namespace].wrapActions[actionName]) { + src.statePlugins[namespace].wrapActions[actionName] = wrapActions[actionName].concat(src.statePlugins[namespace].wrapActions[actionName]) + } + + } + } + + // process selector wrapping + if (isObject(wrapSelectors)) { + for(let selectorName in wrapSelectors) { + let selector = wrapSelectors[selectorName] + + // This should only happen if dest is the first plugin, since invocations after that will ensure its an array + if(!Array.isArray(selector)) { + selector = [selector] + wrapSelectors[selectorName] = selector // Put the value inside an array + } + + if(src && src.statePlugins && src.statePlugins[namespace] && src.statePlugins[namespace].wrapSelectors && src.statePlugins[namespace].wrapSelectors[selectorName]) { + src.statePlugins[namespace].wrapSelectors[selectorName] = wrapSelectors[selectorName].concat(src.statePlugins[namespace].wrapSelectors[selectorName]) + } + + } + } + } + } + + return deepExtend(dest, src) +} + +function buildReducer(states) { + let reducerObj = objMap(states, (val) => { + return val.reducers + }) + return allReducers(reducerObj) +} + +function allReducers(reducerSystem) { + let reducers = Object.keys(reducerSystem).reduce((obj, key) => { + obj[key] = makeReducer(reducerSystem[key]) + return obj + },{}) + + if(!Object.keys(reducers).length) { + return idFn + } + + return combineReducers(reducers) +} + +function makeReducer(reducerObj) { + return (state = new Map(), action) => { + if(!reducerObj) + return state + + let redFn = (reducerObj[action.type]) + if(redFn) { + const res = wrapWithTryCatch(redFn)(state, action) + // If the try/catch wrapper kicks in, we'll get null back... + // in that case, we want to avoid making any changes to state + return res === null ? state : res + } + return state + } +} + +function wrapWithTryCatch(fn, { + logErrors = true +} = {}) { + if(typeof fn !== "function") { + return fn + } + + return function(...args) { + try { + return fn.call(this, ...args) + } catch(e) { + if(logErrors) { + console.error(e) + } + return null + } + } +} + +function configureStore(rootReducer, initialState, getSystem) { + const store = createStoreWithMiddleware(rootReducer, initialState, getSystem) + + // if (module.hot) { + // // Enable Webpack hot module replacement for reducers + // module.hot.accept("reducers/index", () => { + // const nextRootReducer = require("reducers/index") + // store.replaceReducer(nextRootReducer) + // }) + // } + + return store +} diff --git a/frontend/web/api-doc/src/core/utils.js b/frontend/web/api-doc/src/core/utils.js new file mode 100644 index 0000000..d50edd0 --- /dev/null +++ b/frontend/web/api-doc/src/core/utils.js @@ -0,0 +1,925 @@ +/* + ATTENTION! This file (but not the functions within) is deprecated. + + You should probably add a new file to `./helpers/` instead of adding a new + function here. + + One-function-per-file is a better pattern than what we have here. + + If you're refactoring something in here, feel free to break it out to a file + in `./helpers` if you have the time. +*/ + +import Im, { fromJS, Set } from "immutable" +import { sanitizeUrl as braintreeSanitizeUrl } from "@braintree/sanitize-url" +import camelCase from "lodash/camelCase" +import upperFirst from "lodash/upperFirst" +import _memoize from "lodash/memoize" +import find from "lodash/find" +import some from "lodash/some" +import eq from "lodash/eq" +import isFunction from "lodash/isFunction" +import { memoizedSampleFromSchema, memoizedCreateXMLExample } from "core/plugins/samples/fn" +import win from "./window" +import cssEscape from "css.escape" +import getParameterSchema from "../helpers/get-parameter-schema" +import randomBytes from "randombytes" +import shaJs from "sha.js" +import YAML, { JSON_SCHEMA } from "js-yaml" + + +const DEFAULT_RESPONSE_KEY = "default" + +export const isImmutable = (maybe) => Im.Iterable.isIterable(maybe) + +export function objectify (thing) { + if(!isObject(thing)) + return {} + if(isImmutable(thing)) + return thing.toJS() + return thing +} + +export function arrayify (thing) { + if(!thing) + return [] + + if(thing.toArray) + return thing.toArray() + + return normalizeArray(thing) +} + +export function fromJSOrdered(js) { + if (isImmutable(js)) { + return js // Can't do much here + } + if (js instanceof win.File) { + return js + } + if (!isObject(js)) { + return js + } + if (Array.isArray(js)) { + return Im.Seq(js).map(fromJSOrdered).toList() + } + if (isFunction(js.entries)) { + // handle multipart/form-data + const objWithHashedKeys = createObjWithHashedKeys(js) + return Im.OrderedMap(objWithHashedKeys).map(fromJSOrdered) + } + return Im.OrderedMap(js).map(fromJSOrdered) +} + +/** + * Convert a FormData object into plain object + * Append a hashIdx and counter to the key name, if multiple exists + * if single, key name = + * if multiple, key name = + * @example single entry for vegetable + * fdObj.entries.vegtables: "carrot" + * // returns newObj.vegetables : "carrot" + * @example multiple entries for fruits[] + * fdObj.entries.fruits[]: "apple" + * // returns newObj.fruits[]_**[]1 : "apple" + * fdObj.entries.fruits[]: "banana" + * // returns newObj.fruits[]_**[]2 : "banana" + * fdObj.entries.fruits[]: "grape" + * // returns newObj.fruits[]_**[]3 : "grape" + * @param {FormData} fdObj - a FormData object + * @return {Object} - a plain object + */ +export function createObjWithHashedKeys (fdObj) { + if (!isFunction(fdObj.entries)) { + return fdObj // not a FormData object with iterable + } + const newObj = {} + const hashIdx = "_**[]" // our internal identifier + const trackKeys = {} + for (let pair of fdObj.entries()) { + if (!newObj[pair[0]] && !(trackKeys[pair[0]] && trackKeys[pair[0]].containsMultiple)) { + newObj[pair[0]] = pair[1] // first key name: no hash required + } else { + if (!trackKeys[pair[0]]) { + // initiate tracking key for multiple + trackKeys[pair[0]] = { + containsMultiple: true, + length: 1 + } + // "reassign" first pair to matching hashed format for multiple + let hashedKeyFirst = `${pair[0]}${hashIdx}${trackKeys[pair[0]].length}` + newObj[hashedKeyFirst] = newObj[pair[0]] + // remove non-hashed key of multiple + delete newObj[pair[0]] // first + } + trackKeys[pair[0]].length += 1 + let hashedKeyCurrent = `${pair[0]}${hashIdx}${trackKeys[pair[0]].length}` + newObj[hashedKeyCurrent] = pair[1] + } + } + return newObj +} + +export function bindToState(obj, state) { + var newObj = {} + Object.keys(obj) + .filter(key => typeof obj[key] === "function") + .forEach(key => newObj[key] = obj[key].bind(null, state)) + return newObj +} + +export function normalizeArray(arr) { + if(Array.isArray(arr)) + return arr + return [arr] +} + +export function isFn(fn) { + return typeof fn === "function" +} + +export function isObject(obj) { + return !!obj && typeof obj === "object" +} + +export function isFunc(thing) { + return typeof(thing) === "function" +} + +export function isArray(thing) { + return Array.isArray(thing) +} + +// I've changed memoize libs more than once, so I'm using this a way to make that simpler +export const memoize = _memoize + +export function objMap(obj, fn) { + return Object.keys(obj).reduce((newObj, key) => { + newObj[key] = fn(obj[key], key) + return newObj + }, {}) +} + +export function objReduce(obj, fn) { + return Object.keys(obj).reduce((newObj, key) => { + let res = fn(obj[key], key) + if(res && typeof res === "object") + Object.assign(newObj, res) + return newObj + }, {}) +} + +// Redux middleware that exposes the system to async actions (like redux-thunk, but with out system instead of (dispatch, getState) +export function systemThunkMiddleware(getSystem) { + return ({ dispatch, getState }) => { // eslint-disable-line no-unused-vars + return next => action => { + if (typeof action === "function") { + return action(getSystem()) + } + + return next(action) + } + } +} + +export function defaultStatusCode ( responses ) { + let codes = responses.keySeq() + return codes.contains(DEFAULT_RESPONSE_KEY) ? DEFAULT_RESPONSE_KEY : codes.filter( key => (key+"")[0] === "2").sort().first() +} + + +/** + * Returns an Immutable List, safely + * @param {Immutable.Iterable} iterable the iterable to get the key from + * @param {String|[String]} key either an array of keys, or a single key + * @returns {Immutable.List} either iterable.get(keys) or an empty Immutable.List + */ +export function getList(iterable, keys) { + if(!Im.Iterable.isIterable(iterable)) { + return Im.List() + } + let val = iterable.getIn(Array.isArray(keys) ? keys : [keys]) + return Im.List.isList(val) ? val : Im.List() +} + +/** + * Take an immutable map, and convert to a list. + * Where the keys are merged with the value objects + * @param {Immutable.Map} map, the map to convert + * @param {String} key the key to use, when merging the `key` + * @returns {Immutable.List} + */ +export function mapToList(map, keyNames="key", collectedKeys=Im.Map()) { + if(!Im.Map.isMap(map) || !map.size) { + return Im.List() + } + + if(!Array.isArray(keyNames)) { + keyNames = [ keyNames ] + } + + if(keyNames.length < 1) { + return map.merge(collectedKeys) + } + + // I need to avoid `flatMap` from merging in the Maps, as well as the lists + let list = Im.List() + let keyName = keyNames[0] + for(let entry of map.entries()) { + let [key, val] = entry + let nextList = mapToList(val, keyNames.slice(1), collectedKeys.set(keyName, key)) + if(Im.List.isList(nextList)) { + list = list.concat(nextList) + } else { + list = list.push(nextList) + } + } + + return list +} + +export function extractFileNameFromContentDispositionHeader(value){ + let patterns = [ + /filename\*=[^']+'\w*'"([^"]+)";?/i, + /filename\*=[^']+'\w*'([^;]+);?/i, + /filename="([^;]*);?"/i, + /filename=([^;]*);?/i + ] + + let responseFilename + patterns.some(regex => { + responseFilename = regex.exec(value) + return responseFilename !== null + }) + + if (responseFilename !== null && responseFilename.length > 1) { + try { + return decodeURIComponent(responseFilename[1]) + } catch(e) { + console.error(e) + } + } + + return null +} + +// PascalCase, aka UpperCamelCase +export function pascalCase(str) { + return upperFirst(camelCase(str)) +} + +// Remove the ext of a filename, and pascalCase it +export function pascalCaseFilename(filename) { + return pascalCase(filename.replace(/\.[^./]*$/, "")) +} + +// Check if ... +// - new props +// - If immutable, use .is() +// - if in explicit objectList, then compare using _.eq +// - else use === +export const propChecker = (props, nextProps, objectList=[], ignoreList=[]) => { + + if(Object.keys(props).length !== Object.keys(nextProps).length) { + return true + } + + return ( + some(props, (a, name) => { + if(ignoreList.includes(name)) { + return false + } + let b = nextProps[name] + + if(Im.Iterable.isIterable(a)) { + return !Im.is(a,b) + } + + // Not going to compare objects + if(typeof a === "object" && typeof b === "object") { + return false + } + + return a !== b + }) + || objectList.some( objectPropName => !eq(props[objectPropName], nextProps[objectPropName]))) +} + +export const validateMaximum = ( val, max ) => { + if (val > max) { + return `Value must be less than ${max}` + } +} + +export const validateMinimum = ( val, min ) => { + if (val < min) { + return `Value must be greater than ${min}` + } +} + +export const validateNumber = ( val ) => { + if (!/^-?\d+(\.?\d+)?$/.test(val)) { + return "Value must be a number" + } +} + +export const validateInteger = ( val ) => { + if (!/^-?\d+$/.test(val)) { + return "Value must be an integer" + } +} + +export const validateFile = ( val ) => { + if ( val && !(val instanceof win.File) ) { + return "Value must be a file" + } +} + +export const validateBoolean = ( val ) => { + if ( !(val === "true" || val === "false" || val === true || val === false) ) { + return "Value must be a boolean" + } +} + +export const validateString = ( val ) => { + if ( val && typeof val !== "string" ) { + return "Value must be a string" + } +} + +export const validateDateTime = (val) => { + if (isNaN(Date.parse(val))) { + return "Value must be a DateTime" + } +} + +export const validateGuid = (val) => { + val = val.toString().toLowerCase() + if (!/^[{(]?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[)}]?$/.test(val)) { + return "Value must be a Guid" + } +} + +export const validateMaxLength = (val, max) => { + if (val.length > max) { + return `Value must be no longer than ${max} character${max !== 1 ? "s" : ""}` + } +} + +export const validateUniqueItems = (val, uniqueItems) => { + if (!val) { + return + } + if (uniqueItems === "true" || uniqueItems === true) { + const list = fromJS(val) + const set = list.toSet() + const hasDuplicates = val.length > set.size + if(hasDuplicates) { + let errorsPerIndex = Set() + list.forEach((item, i) => { + if(list.filter(v => isFunc(v.equals) ? v.equals(item) : v === item).size > 1) { + errorsPerIndex = errorsPerIndex.add(i) + } + }) + if(errorsPerIndex.size !== 0) { + return errorsPerIndex.map(i => ({index: i, error: "No duplicates allowed."})).toArray() + } + } + } +} + +export const validateMinItems = (val, min) => { + if (!val && min >= 1 || val && val.length < min) { + return `Array must contain at least ${min} item${min === 1 ? "" : "s"}` + } +} + +export const validateMaxItems = (val, max) => { + if (val && val.length > max) { + return `Array must not contain more then ${max} item${max === 1 ? "" : "s"}` + } +} + +export const validateMinLength = (val, min) => { + if (val.length < min) { + return `Value must be at least ${min} character${min !== 1 ? "s" : ""}` + } +} + +export const validatePattern = (val, rxPattern) => { + var patt = new RegExp(rxPattern) + if (!patt.test(val)) { + return "Value must follow pattern " + rxPattern + } +} + +function validateValueBySchema(value, schema, requiredByParam, bypassRequiredCheck, parameterContentMediaType) { + if(!schema) return [] + let errors = [] + let nullable = schema.get("nullable") + let requiredBySchema = schema.get("required") + let maximum = schema.get("maximum") + let minimum = schema.get("minimum") + let type = schema.get("type") + let format = schema.get("format") + let maxLength = schema.get("maxLength") + let minLength = schema.get("minLength") + let uniqueItems = schema.get("uniqueItems") + let maxItems = schema.get("maxItems") + let minItems = schema.get("minItems") + let pattern = schema.get("pattern") + + const schemaRequiresValue = requiredByParam || requiredBySchema === true + const hasValue = value !== undefined && value !== null + const isValidEmpty = !schemaRequiresValue && !hasValue + + const needsExplicitConstraintValidation = hasValue && type === "array" + + const requiresFurtherValidation = + schemaRequiresValue + || needsExplicitConstraintValidation + || !isValidEmpty + + const isValidNullable = nullable && value === null + + // will not be included in the request or [schema / value] does not [allow / require] further analysis. + const noFurtherValidationNeeded = + isValidNullable + || !type + || !requiresFurtherValidation + + if(noFurtherValidationNeeded) { + return [] + } + + // Further this point the parameter is considered worth to validate + let stringCheck = type === "string" && value + let arrayCheck = type === "array" && Array.isArray(value) && value.length + let arrayListCheck = type === "array" && Im.List.isList(value) && value.count() + let arrayStringCheck = type === "array" && typeof value === "string" && value + let fileCheck = type === "file" && value instanceof win.File + let booleanCheck = type === "boolean" && (value || value === false) + let numberCheck = type === "number" && (value || value === 0) + let integerCheck = type === "integer" && (value || value === 0) + let objectCheck = type === "object" && typeof value === "object" && value !== null + let objectStringCheck = type === "object" && typeof value === "string" && value + + const allChecks = [ + stringCheck, arrayCheck, arrayListCheck, arrayStringCheck, fileCheck, + booleanCheck, numberCheck, integerCheck, objectCheck, objectStringCheck, + ] + + const passedAnyCheck = allChecks.some(v => !!v) + + if (schemaRequiresValue && !passedAnyCheck && !bypassRequiredCheck) { + errors.push("Required field is not provided") + return errors + } + if ( + type === "object" && + (parameterContentMediaType === null || + parameterContentMediaType === "application/json") + ) { + let objectVal = value + if(typeof value === "string") { + try { + objectVal = JSON.parse(value) + } catch (e) { + errors.push("Parameter string value must be valid JSON") + return errors + } + } + if(schema && schema.has("required") && isFunc(requiredBySchema.isList) && requiredBySchema.isList()) { + requiredBySchema.forEach(key => { + if(objectVal[key] === undefined) { + errors.push({ propKey: key, error: "Required property not found" }) + } + }) + } + if(schema && schema.has("properties")) { + schema.get("properties").forEach((val, key) => { + const errs = validateValueBySchema(objectVal[key], val, false, bypassRequiredCheck, parameterContentMediaType) + errors.push(...errs + .map((error) => ({ propKey: key, error }))) + }) + } + } + + if (pattern) { + let err = validatePattern(value, pattern) + if (err) errors.push(err) + } + + if (minItems) { + if (type === "array") { + let err = validateMinItems(value, minItems) + if (err) errors.push(err) + } + } + + if (maxItems) { + if (type === "array") { + let err = validateMaxItems(value, maxItems) + if (err) errors.push({ needRemove: true, error: err }) + } + } + + if (uniqueItems) { + if (type === "array") { + let errorPerItem = validateUniqueItems(value, uniqueItems) + if (errorPerItem) errors.push(...errorPerItem) + } + } + + if (maxLength || maxLength === 0) { + let err = validateMaxLength(value, maxLength) + if (err) errors.push(err) + } + + if (minLength) { + let err = validateMinLength(value, minLength) + if (err) errors.push(err) + } + + if (maximum || maximum === 0) { + let err = validateMaximum(value, maximum) + if (err) errors.push(err) + } + + if (minimum || minimum === 0) { + let err = validateMinimum(value, minimum) + if (err) errors.push(err) + } + + if (type === "string") { + let err + if (format === "date-time") { + err = validateDateTime(value) + } else if (format === "uuid") { + err = validateGuid(value) + } else { + err = validateString(value) + } + if (!err) return errors + errors.push(err) + } else if (type === "boolean") { + let err = validateBoolean(value) + if (!err) return errors + errors.push(err) + } else if (type === "number") { + let err = validateNumber(value) + if (!err) return errors + errors.push(err) + } else if (type === "integer") { + let err = validateInteger(value) + if (!err) return errors + errors.push(err) + } else if (type === "array") { + if (!(arrayCheck || arrayListCheck)) { + return errors + } + if(value) { + value.forEach((item, i) => { + const errs = validateValueBySchema(item, schema.get("items"), false, bypassRequiredCheck, parameterContentMediaType) + errors.push(...errs + .map((err) => ({ index: i, error: err }))) + }) + } + } else if (type === "file") { + let err = validateFile(value) + if (!err) return errors + errors.push(err) + } + + return errors +} + +// validation of parameters before execute +export const validateParam = (param, value, { isOAS3 = false, bypassRequiredCheck = false } = {}) => { + + let paramRequired = param.get("required") + + let { schema: paramDetails, parameterContentMediaType } = getParameterSchema(param, { isOAS3 }) + + return validateValueBySchema(value, paramDetails, paramRequired, bypassRequiredCheck, parameterContentMediaType) +} + +const getXmlSampleSchema = (schema, config, exampleOverride) => { + if (schema && !schema.xml) { + schema.xml = {} + } + if (schema && !schema.xml.name) { + if (!schema.$$ref && (schema.type || schema.items || schema.properties || schema.additionalProperties)) { + return "\n" + } + if (schema.$$ref) { + let match = schema.$$ref.match(/\S*\/(\S+)$/) + schema.xml.name = match[1] + } + } + + return memoizedCreateXMLExample(schema, config, exampleOverride) +} + +const shouldStringifyTypesConfig = [ + { + when: /json/, + shouldStringifyTypes: ["string"] + } +] + +const defaultStringifyTypes = ["object"] + +const getStringifiedSampleForSchema = (schema, config, contentType, exampleOverride) => { + const res = memoizedSampleFromSchema(schema, config, exampleOverride) + const resType = typeof res + + const typesToStringify = shouldStringifyTypesConfig.reduce( + (types, nextConfig) => nextConfig.when.test(contentType) + ? [...types, ...nextConfig.shouldStringifyTypes] + : types, + defaultStringifyTypes) + + return some(typesToStringify, x => x === resType) + ? JSON.stringify(res, null, 2) + : res +} + +const getYamlSampleSchema = (schema, config, contentType, exampleOverride) => { + const jsonExample = getStringifiedSampleForSchema(schema, config, contentType, exampleOverride) + let yamlString + try { + yamlString = YAML.dump(YAML.load(jsonExample), { + + lineWidth: -1 // don't generate line folds + }, { schema: JSON_SCHEMA }) + if(yamlString[yamlString.length - 1] === "\n") { + yamlString = yamlString.slice(0, yamlString.length - 1) + } + } catch (e) { + console.error(e) + return "error: could not generate yaml example" + } + return yamlString + .replace(/\t/g, " ") +} + +export const getSampleSchema = (schema, contentType="", config={}, exampleOverride = undefined) => { + if(schema && isFunc(schema.toJS)) + schema = schema.toJS() + if(exampleOverride && isFunc(exampleOverride.toJS)) + exampleOverride = exampleOverride.toJS() + + if (/xml/.test(contentType)) { + return getXmlSampleSchema(schema, config, exampleOverride) + } + if (/(yaml|yml)/.test(contentType)) { + return getYamlSampleSchema(schema, config, contentType, exampleOverride) + } + return getStringifiedSampleForSchema(schema, config, contentType, exampleOverride) +} + +export const parseSearch = () => { + let map = {} + let search = win.location.search + + if(!search) + return {} + + if ( search != "" ) { + let params = search.substr(1).split("&") + + for (let i in params) { + if (!Object.prototype.hasOwnProperty.call(params, i)) { + continue + } + i = params[i].split("=") + map[decodeURIComponent(i[0])] = (i[1] && decodeURIComponent(i[1])) || "" + } + } + + return map +} + +export const serializeSearch = (searchMap) => { + return Object.keys(searchMap).map(k => { + return encodeURIComponent(k) + "=" + encodeURIComponent(searchMap[k]) + }).join("&") +} + +export const btoa = (str) => { + let buffer + + if (str instanceof Buffer) { + buffer = str + } else { + buffer = Buffer.from(str.toString(), "utf-8") + } + + return buffer.toString("base64") +} + +export const sorters = { + operationsSorter: { + alpha: (a, b) => a.get("path").localeCompare(b.get("path")), + method: (a, b) => a.get("method").localeCompare(b.get("method")) + }, + tagsSorter: { + alpha: (a, b) => a.localeCompare(b) + } +} + +export const buildFormData = (data) => { + let formArr = [] + + for (let name in data) { + let val = data[name] + if (val !== undefined && val !== "") { + formArr.push([name, "=", encodeURIComponent(val).replace(/%20/g,"+")].join("")) + } + } + return formArr.join("&") +} + +// Is this really required as a helper? Perhaps. TODO: expose the system of presets.apis in docs, so we know what is supported +export const shallowEqualKeys = (a,b, keys) => { + return !!find(keys, (key) => { + return eq(a[key], b[key]) + }) +} + +export function sanitizeUrl(url) { + if(typeof url !== "string" || url === "") { + return "" + } + + return braintreeSanitizeUrl(url) +} + +export function requiresValidationURL(uri) { + if (!uri || uri.indexOf("localhost") >= 0 || uri.indexOf("127.0.0.1") >= 0 || uri === "none") { + return false + } + return true +} + + +export function getAcceptControllingResponse(responses) { + if(!Im.OrderedMap.isOrderedMap(responses)) { + // wrong type! + return null + } + + if(!responses.size) { + // responses is empty + return null + } + + const suitable2xxResponse = responses.find((res, k) => { + return k.startsWith("2") && Object.keys(res.get("content") || {}).length > 0 + }) + + // try to find a suitable `default` responses + const defaultResponse = responses.get("default") || Im.OrderedMap() + const defaultResponseMediaTypes = (defaultResponse.get("content") || Im.OrderedMap()).keySeq().toJS() + const suitableDefaultResponse = defaultResponseMediaTypes.length ? defaultResponse : null + + return suitable2xxResponse || suitableDefaultResponse +} + +// suitable for use in URL fragments +export const createDeepLinkPath = (str) => typeof str == "string" || str instanceof String ? str.trim().replace(/\s/g, "%20") : "" +// suitable for use in CSS classes and ids +export const escapeDeepLinkPath = (str) => cssEscape( createDeepLinkPath(str).replace(/%20/g, "_") ) + +export const getExtensions = (defObj) => defObj.filter((v, k) => /^x-/.test(k)) +export const getCommonExtensions = (defObj) => defObj.filter((v, k) => /^pattern|maxLength|minLength|maximum|minimum/.test(k)) + +// Deeply strips a specific key from an object. +// +// `predicate` can be used to discriminate the stripping further, +// by preserving the key's place in the object based on its value. +export function deeplyStripKey(input, keyToStrip, predicate = () => true) { + if(typeof input !== "object" || Array.isArray(input) || input === null || !keyToStrip) { + return input + } + + const obj = Object.assign({}, input) + + Object.keys(obj).forEach(k => { + if(k === keyToStrip && predicate(obj[k], k)) { + delete obj[k] + return + } + obj[k] = deeplyStripKey(obj[k], keyToStrip, predicate) + }) + + return obj +} + +export function stringify(thing) { + if (typeof thing === "string") { + return thing + } + + if (thing && thing.toJS) { + thing = thing.toJS() + } + + if (typeof thing === "object" && thing !== null) { + try { + return JSON.stringify(thing, null, 2) + } + catch (e) { + return String(thing) + } + } + + if(thing === null || thing === undefined) { + return "" + } + + return thing.toString() +} + +export function numberToString(thing) { + if(typeof thing === "number") { + return thing.toString() + } + + return thing +} + +export function paramToIdentifier(param, { returnAll = false, allowHashes = true } = {}) { + if(!Im.Map.isMap(param)) { + throw new Error("paramToIdentifier: received a non-Im.Map parameter as input") + } + const paramName = param.get("name") + const paramIn = param.get("in") + + let generatedIdentifiers = [] + + // Generate identifiers in order of most to least specificity + + if (param && param.hashCode && paramIn && paramName && allowHashes) { + generatedIdentifiers.push(`${paramIn}.${paramName}.hash-${param.hashCode()}`) + } + + if(paramIn && paramName) { + generatedIdentifiers.push(`${paramIn}.${paramName}`) + } + + generatedIdentifiers.push(paramName) + + // Return the most preferred identifier, or all if requested + + return returnAll ? generatedIdentifiers : (generatedIdentifiers[0] || "") +} + +export function paramToValue(param, paramValues) { + const allIdentifiers = paramToIdentifier(param, { returnAll: true }) + + // Map identifiers to values in the provided value hash, filter undefined values, + // and return the first value found + const values = allIdentifiers + .map(id => { + return paramValues[id] + }) + .filter(value => value !== undefined) + + return values[0] +} + +// adapted from https://auth0.com/docs/flows/guides/auth-code-pkce/includes/create-code-verifier +export function generateCodeVerifier() { + return b64toB64UrlEncoded( + randomBytes(32).toString("base64") + ) +} + +export function createCodeChallenge(codeVerifier) { + return b64toB64UrlEncoded( + shaJs("sha256") + .update(codeVerifier) + .digest("base64") + ) +} + +function b64toB64UrlEncoded(str) { + return str + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=/g, "") +} + +export const isEmptyValue = (value) => { + if (!value) { + return true + } + + if (isImmutable(value) && value.isEmpty()) { + return true + } + + return false +} diff --git a/frontend/web/api-doc/src/core/utils/jsonParse.js b/frontend/web/api-doc/src/core/utils/jsonParse.js new file mode 100644 index 0000000..fda5cb9 --- /dev/null +++ b/frontend/web/api-doc/src/core/utils/jsonParse.js @@ -0,0 +1,15 @@ +export function canJsonParse(str) { + try { + let testValueForJson = JSON.parse(str) + return testValueForJson ? true : false + } catch (e) { + // exception: string is not valid json + return null + } +} + +export function getKnownSyntaxHighlighterLanguage(val) { + // to start, only check for json. can expand as needed in future + const isValidJson = canJsonParse(val) + return isValidJson ? "json" : null +} diff --git a/frontend/web/api-doc/src/core/utils/url.js b/frontend/web/api-doc/src/core/utils/url.js new file mode 100644 index 0000000..d721ca7 --- /dev/null +++ b/frontend/web/api-doc/src/core/utils/url.js @@ -0,0 +1,39 @@ +export function isAbsoluteUrl(url) { + return url.match(/^(?:[a-z]+:)?\/\//i) // Matches http://, HTTP://, https://, ftp://, //example.com, +} + +export function addProtocol(url) { + if (!url.match(/^\/\//i)) return url // Checks if protocol is missing e.g. //example.com + + return `${window.location.protocol}${url}` +} + +export function buildBaseUrl(selectedServer, specUrl) { + if (!selectedServer) return specUrl + if (isAbsoluteUrl(selectedServer)) return addProtocol(selectedServer) + + return new URL(selectedServer, specUrl).href +} + +export function buildUrl(url, specUrl, { selectedServer="" } = {}) { + if (!url) return undefined + if (isAbsoluteUrl(url)) return url + + const baseUrl = buildBaseUrl(selectedServer, specUrl) + if (!isAbsoluteUrl(baseUrl)) { + return new URL(url, window.location.href).href + } + return new URL(url, baseUrl).href +} + +/** + * Safe version of buildUrl function. `selectedServer` can contain server variables + * which can fail the URL resolution. + */ +export function safeBuildUrl(url, specUrl, { selectedServer="" } = {}) { + try { + return buildUrl(url, specUrl, { selectedServer }) + } catch { + return undefined + } +} diff --git a/frontend/web/api-doc/src/core/window.js b/frontend/web/api-doc/src/core/window.js new file mode 100644 index 0000000..4b9b8e7 --- /dev/null +++ b/frontend/web/api-doc/src/core/window.js @@ -0,0 +1,29 @@ +function makeWindow() { + var win = { + location: {}, + history: {}, + open: () => {}, + close: () => {}, + File: function() {} + } + + if(typeof window === "undefined") { + return win + } + + try { + win = window + var props = ["File", "Blob", "FormData"] + for (var prop of props) { + if (prop in window) { + win[prop] = window[prop] + } + } + } catch( e ) { + console.error(e) + } + + return win +} + +export default makeWindow() diff --git a/frontend/web/api-doc/src/helpers/create-html-ready-id.js b/frontend/web/api-doc/src/helpers/create-html-ready-id.js new file mode 100644 index 0000000..0aa7c9e --- /dev/null +++ b/frontend/web/api-doc/src/helpers/create-html-ready-id.js @@ -0,0 +1,10 @@ +/** + * Replace invalid characters from a string to create an html-ready ID + * + * @param {string} id A string that may contain invalid characters for the HTML ID attribute + * @param {string} [replacement=_] The string to replace invalid characters with; "_" by default + * @return {string} Information about the parameter schema + */ +export default function createHtmlReadyId(id, replacement = "_") { + return id.replace(/[^\w-]/g, replacement) +} diff --git a/frontend/web/api-doc/src/helpers/get-parameter-schema.js b/frontend/web/api-doc/src/helpers/get-parameter-schema.js new file mode 100644 index 0000000..cdf463c --- /dev/null +++ b/frontend/web/api-doc/src/helpers/get-parameter-schema.js @@ -0,0 +1,92 @@ +/** + * @prettier + */ + +import Im from "immutable" + +const swagger2SchemaKeys = Im.Set.of( + "type", + "format", + "items", + "default", + "maximum", + "exclusiveMaximum", + "minimum", + "exclusiveMinimum", + "maxLength", + "minLength", + "pattern", + "maxItems", + "minItems", + "uniqueItems", + "enum", + "multipleOf" +) + +/** + * @typedef {Object} ParameterSchemaDescriptor + * @property {Immutable.Map} schema - the parameter schema + * @property {string|null} parameterContentMediaType - the effective media type, for `content`-based OpenAPI 3.0 Parameters, or `null` otherwise + */ + +/** + * Get the effective schema value for a parameter, or an empty Immutable.Map if + * no suitable schema can be found. + * + * Supports OpenAPI 3.0 `Parameter.content` priority -- since a Parameter Object + * cannot have both `schema` and `content`, this function ignores `schema` when + * `content` is present. + * + * @param {Immutable.Map} parameter The parameter to identify a schema for + * @param {object} config + * @param {boolean} config.isOAS3 Whether the parameter is from an OpenAPI 2.0 + * or OpenAPI 3.0 definition + * @return {ParameterSchemaDescriptor} Information about the parameter schema + */ +export default function getParameterSchema(parameter, { isOAS3 } = {}) { + // Return empty Map if `parameter` isn't a Map + if (!Im.Map.isMap(parameter)) { + return { + schema: Im.Map(), + parameterContentMediaType: null, + } + } + + if (!isOAS3) { + // Swagger 2.0 + if (parameter.get("in") === "body") { + return { + schema: parameter.get("schema", Im.Map()), + parameterContentMediaType: null, + } + } else { + return { + schema: parameter.filter((v, k) => swagger2SchemaKeys.includes(k)), + parameterContentMediaType: null, + } + } + } + + // If we've reached here, the parameter is OpenAPI 3.0 + + if (parameter.get("content")) { + const parameterContentMediaTypes = parameter + .get("content", Im.Map({})) + .keySeq() + + const parameterContentMediaType = parameterContentMediaTypes.first() + + return { + schema: parameter.getIn( + ["content", parameterContentMediaType, "schema"], + Im.Map() + ), + parameterContentMediaType, + } + } + + return { + schema: parameter.get("schema") ? parameter.get("schema", Im.Map()): Im.Map(), + parameterContentMediaType: null, + } +} diff --git a/frontend/web/api-doc/src/helpers/memoizeN.js b/frontend/web/api-doc/src/helpers/memoizeN.js new file mode 100644 index 0000000..f925a34 --- /dev/null +++ b/frontend/web/api-doc/src/helpers/memoizeN.js @@ -0,0 +1,48 @@ +import memoize from "lodash/memoize" + +/** + * This function is extension on top of lodash.memoize. + * It uses all the arguments of the `fn` as the cache key instead of just the first one. + * If resolver is provided, it determines the cache key for + * storing the result based on the arguments provided to the memoized function. + */ + +const shallowArrayEquals = (a) => (b) => { + return Array.isArray(a) && Array.isArray(b) + && a.length === b.length + && a.every((val, index) => val === b[index]) +} + +const list = (...args) => args + +class Cache extends Map { + delete(key) { + const keys = Array.from(this.keys()) + const foundKey = keys.find(shallowArrayEquals(key)) + return super.delete(foundKey) + } + + get(key) { + const keys = Array.from(this.keys()) + const foundKey = keys.find(shallowArrayEquals(key)) + return super.get(foundKey) + } + + has(key) { + const keys = Array.from(this.keys()) + return keys.findIndex(shallowArrayEquals(key)) !== -1 + } +} + +const memoizeN = (fn, resolver = list) => { + const { Cache: OriginalCache } = memoize + memoize.Cache = Cache + + const memoized = memoize(fn, resolver) + + memoize.Cache = OriginalCache + + return memoized +} + +export default memoizeN diff --git a/frontend/web/api-doc/src/img/logo_small.png b/frontend/web/api-doc/src/img/logo_small.png new file mode 100644 index 0000000000000000000000000000000000000000..fe60b1ffb0a04fd9e96789c32360b12d1078bbab GIT binary patch literal 581 zcmV-L0=oT)P)X zMin(c7dA(Q>MeulEN8hhf#obdcRWalI#ilBGGsqQfIBW-K|y;wZpJYbg52fON#J`~QEp z5|Ry;!@MN9b27J+Mqo#V=NXY5&>I#j6Mmj%S(^JWSBq!XHBj+{2_!s)sNA+ne~AAs(-UIFa zan|@IA}<|meBvQ)z=tz6h=TFjPs2N*AHM{&_C-z|I!$-D2o--#VIwS8^qS^UstIW$ zd?qE1n%0YRt>fE0=55BZf7+Dd?X!3y z#luGDqR`{4LUUwdDsTBvi7m&nTyUCp>!Qj+hAe8MTVCm+k~_BK21q^ueBBb-h=%UF ztD^I?)(xegt=I=taOJqn4ik|xJBmbK-rOJ)EhD6#)<6qvW|nBck84NP%L@JhNlZQB T(=f$000000NkvXXu0mjfPnPf| literal 0 HcmV?d00001 diff --git a/frontend/web/api-doc/src/img/rolling-load.svg b/frontend/web/api-doc/src/img/rolling-load.svg new file mode 100644 index 0000000..86ead2e --- /dev/null +++ b/frontend/web/api-doc/src/img/rolling-load.svg @@ -0,0 +1 @@ + diff --git a/frontend/web/api-doc/src/index.js b/frontend/web/api-doc/src/index.js new file mode 100644 index 0000000..ca4601a --- /dev/null +++ b/frontend/web/api-doc/src/index.js @@ -0,0 +1,3 @@ +import SwaggerUI from "./core" + +export default SwaggerUI diff --git a/frontend/web/api-doc/src/plugins/add-plugin.md b/frontend/web/api-doc/src/plugins/add-plugin.md new file mode 100644 index 0000000..a5fd0c5 --- /dev/null +++ b/frontend/web/api-doc/src/plugins/add-plugin.md @@ -0,0 +1,127 @@ +# Add a plugin + +### Swagger-UI relies on plugins for all the good stuff. + +Plugins allow you to add +- `statePlugins` + - `selectors` - query the state + - `reducers` - modify the state + - `actions` - fire and forget, that will eventually be handled by a reducer. You *can* rely on the result of async actions. But in general it's not recommended + - `wrapActions` - replace an action with a wrapped action (useful for hooking into existing `actions`) +- `components` - React components +- `fn` - commons functions + +To add a plugin we include it in the configs... + +```js +SwaggerUI({ + url: 'some url', + plugins: [ ... ] +}) +``` + +Or if you're updating the core plugins.. you'll add it to the base preset: [src/core/presets/base.js](https://github.com/swagger-api/swagger-ui/blob/master/src/core/presets/base.js) + +Each Plugin is a function that returns an object. That object will get merged with the `system` and later bound to the state. +Here is an example of each `type` + +```js +// A contrived, but quite full example.... + +export function SomePlugin(toolbox) { + + const UPDATE_SOMETHING = "some_namespace_update_something" // strings just need to be uniuqe... see below + + // Tools + const fromJS = toolbox.Im.fromJS // needed below + const createSelector = toolbox.createSelector // same, needed below + + return { + statePlugins: { + + someNamespace: { + actions: { + actionName: (args)=> ({type: UPDATE_SOMETHING, payload: args}), // Synchronous action must return an object for the reducer to handle + anotherAction: (a,b,c) => (system) => system.someNamespaceActions.actionName(a || b) // Asynchronous actions must return a function. The function gets the whole system, and can call other actions (based on state if needed) + }, + wrapActions: { + anotherAction: (oriAction, system) => (...args) => { + oriAction(...args) // Usually we at least call the original action + system.someNamespace.actionName(...args) // why not call this? + console.log("args", args) // Log the args + // anotherAction in the someNamespace has now been replaced with the this function + } + }, + reducers: { + [UPDATE_SOMETHING]: (state, action) => { // Take a state (which is immutable) and an action (see synchronous actions) and return a new state + return state.set("something", fromJS(action.payload)) // we're updating the Immutable state object... we need to convert vanilla objects into an immutable type (fromJS) + // See immutable about how to modify the state + // PS: you're only working with the state under the namespace, in this case "someNamespace". So you can do what you want, without worrying about /other/ namespaces + } + }, + selectors: { + // creatSelector takes a list of fn's and passes all the results to the last fn. + // eg: createSelector(a => a, a => a+1, (a,a2) => a + a2)(1) // = 3 + something: createSelector( // see [reselect#createSelector](https://github.com/reactjs/reselect#createselectorinputselectors--inputselectors-resultfunc) + getState => getState(), // This is a requirement... because we `bind` selectors, we don't want to bind to any particular state (which is an immutable value) so we bind to a function, which returns the current state + state => state.get("something") // return the whatever "something" points to + ), + foo: getState => "bar" // In the end selectors are just functions that we pass getState to + } + } + + ... // you can include as many namespaces as you want. They just get merged into the 'system' + + }, + + components: { + foo: ()=>

Hello

// just a map of names to react components, naturally you'd want to import a fuller react component + }, + + fn: { + addOne: (a) => a + 1 // just any extra functions you want to include + } + } +} +``` + +>The plugin factory gets one argument, which I like to call `toolbox`. +This argument is the entire plugin system (at the point the plugin factory is called). It also includes a reference to the `Immutable` lib, so that plugin authors don't need to include it. + + +### The Plugin system + +Each plugin you include will end up getting merged into the `system`, which is just an object. + +Then we bind the `system` to our state. And flatten it, so that we don't need to reach into deep objects + +> ie: spec.actions becomes specActions, spec.selectors becomes specSelectors + +You can reach this bound system by calling `getSystem` on the store. + +`getSystem` is the heart of this whole project. Each container component will receive a spread of props from `getSystem` + +here is an example.... +```js +class Bobby extends React.Component { + + handleClick(e) { + this.props.someNamespaceActions.actionName() // fires an action... which the reducer will *eventually* see + } + + render() { + + let { someNamespaceSelectors, someNamespaceActions } = this.props // this.props has the whole state spread + let something = someNamespaceSelectors.something() // calls our selector, which returns some state (either an immutable object or value) + + return ( +

Hello {something}

// render the contents + ) + + } + +} +``` + +TODO: a lot more elaboration +` diff --git a/frontend/web/api-doc/src/plugins/index.js b/frontend/web/api-doc/src/plugins/index.js new file mode 100644 index 0000000..f143ff0 --- /dev/null +++ b/frontend/web/api-doc/src/plugins/index.js @@ -0,0 +1,7 @@ +import Configs from "./configs" +import Topbar from "./topbar" + +export default { + Configs, + Topbar +} diff --git a/frontend/web/api-doc/src/plugins/topbar/index.js b/frontend/web/api-doc/src/plugins/topbar/index.js new file mode 100644 index 0000000..dc00e51 --- /dev/null +++ b/frontend/web/api-doc/src/plugins/topbar/index.js @@ -0,0 +1,11 @@ +import Topbar from "./topbar" +import Logo from "./logo" + +export default function () { + return { + components: { + Topbar, + Logo + } + } +} diff --git a/frontend/web/api-doc/src/plugins/topbar/logo.jsx b/frontend/web/api-doc/src/plugins/topbar/logo.jsx new file mode 100644 index 0000000..bbfc599 --- /dev/null +++ b/frontend/web/api-doc/src/plugins/topbar/logo.jsx @@ -0,0 +1,8 @@ +import React from "react" +import SwaggerUILogo from "./logo_small.svg" + +export const Logo = () => ( + Swagger UI +) + +export default Logo diff --git a/frontend/web/api-doc/src/plugins/topbar/logo_small.svg b/frontend/web/api-doc/src/plugins/topbar/logo_small.svg new file mode 100644 index 0000000..454542f --- /dev/null +++ b/frontend/web/api-doc/src/plugins/topbar/logo_small.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/web/api-doc/src/plugins/topbar/topbar.jsx b/frontend/web/api-doc/src/plugins/topbar/topbar.jsx new file mode 100644 index 0000000..9dc07ba --- /dev/null +++ b/frontend/web/api-doc/src/plugins/topbar/topbar.jsx @@ -0,0 +1,171 @@ +import React, { cloneElement } from "react" +import PropTypes from "prop-types" + +//import "./topbar.less" +import {parseSearch, serializeSearch} from "../../core/utils" + +export default class Topbar extends React.Component { + + static propTypes = { + layoutActions: PropTypes.object.isRequired, + authActions: PropTypes.object.isRequired + } + + constructor(props, context) { + super(props, context) + this.state = { url: props.specSelectors.url(), selectedIndex: 0 } + } + + UNSAFE_componentWillReceiveProps(nextProps) { + this.setState({ url: nextProps.specSelectors.url() }) + } + + onUrlChange =(e)=> { + let {target: {value}} = e + this.setState({url: value}) + } + + flushAuthData() { + const { persistAuthorization } = this.props.getConfigs() + if (persistAuthorization) + { + return + } + this.props.authActions.restoreAuthorization({ + authorized: {} + }) + } + + loadSpec = (url) => { + this.flushAuthData() + this.props.specActions.updateUrl(url) + this.props.specActions.download(url) + } + + onUrlSelect =(e)=> { + let url = e.target.value || e.target.href + this.loadSpec(url) + this.setSelectedUrl(url) + e.preventDefault() + } + + downloadUrl = (e) => { + this.loadSpec(this.state.url) + e.preventDefault() + } + + setSearch = (spec) => { + let search = parseSearch() + search["urls.primaryName"] = spec.name + const newUrl = `${window.location.protocol}//${window.location.host}${window.location.pathname}` + if(window && window.history && window.history.pushState) { + window.history.replaceState(null, "", `${newUrl}?${serializeSearch(search)}`) + } + } + + setSelectedUrl = (selectedUrl) => { + const configs = this.props.getConfigs() + const urls = configs.urls || [] + + if(urls && urls.length) { + if(selectedUrl) + { + urls.forEach((spec, i) => { + if(spec.url === selectedUrl) + { + this.setState({selectedIndex: i}) + this.setSearch(spec) + } + }) + } + } + } + + componentDidMount() { + const configs = this.props.getConfigs() + const urls = configs.urls || [] + + if(urls && urls.length) { + var targetIndex = this.state.selectedIndex + let search = parseSearch() + let primaryName = search["urls.primaryName"] || configs["urls.primaryName"] + if(primaryName) + { + urls.forEach((spec, i) => { + if(spec.name === primaryName) + { + this.setState({selectedIndex: i}) + targetIndex = i + } + }) + } + + this.loadSpec(urls[targetIndex].url) + } + } + + onFilterChange =(e) => { + let {target: {value}} = e + this.props.layoutActions.updateFilter(value) + } + + render() { + let { getComponent, specSelectors, getConfigs } = this.props + const Button = getComponent("Button") + const Link = getComponent("Link") + const Logo = getComponent("Logo") + + let isLoading = specSelectors.loadingStatus() === "loading" + let isFailed = specSelectors.loadingStatus() === "failed" + + const classNames = ["download-url-input"] + if (isFailed) classNames.push("failed") + if (isLoading) classNames.push("loading") + + const { urls } = getConfigs() + let control = [] + let formOnSubmit = null + + if(urls) { + let rows = [] + urls.forEach((link, i) => { + rows.push() + }) + + control.push( + + ) + } + else { + formOnSubmit = this.downloadUrl + control.push() + control.push() + } + + return ( +
+
+
+ + + +
+ {control.map((el, i) => cloneElement(el, { key: i }))} +
+
+
+
+ ) + } +} + +Topbar.propTypes = { + specSelectors: PropTypes.object.isRequired, + specActions: PropTypes.object.isRequired, + getComponent: PropTypes.func.isRequired, + getConfigs: PropTypes.func.isRequired +} diff --git a/frontend/web/api-doc/src/standalone/index.js b/frontend/web/api-doc/src/standalone/index.js new file mode 100644 index 0000000..83277b5 --- /dev/null +++ b/frontend/web/api-doc/src/standalone/index.js @@ -0,0 +1,24 @@ +import StandaloneLayout from "./layout" +import TopbarPlugin from "plugins/topbar" +import ConfigsPlugin from "corePlugins/configs" +import SafeRenderPlugin from "core/plugins/safe-render" + +// the Standalone preset + +export default [ + TopbarPlugin, + ConfigsPlugin, + () => { + return { + components: { StandaloneLayout } + } + }, + SafeRenderPlugin({ + fullOverride: true, + componentList: [ + "Topbar", + "StandaloneLayout", + "onlineValidatorBadge" + ] + }) +] diff --git a/frontend/web/api-doc/src/standalone/layout.jsx b/frontend/web/api-doc/src/standalone/layout.jsx new file mode 100644 index 0000000..4176160 --- /dev/null +++ b/frontend/web/api-doc/src/standalone/layout.jsx @@ -0,0 +1,38 @@ +import React from "react" +import PropTypes from "prop-types" + +export default class StandaloneLayout extends React.Component { + + static propTypes = { + errSelectors: PropTypes.object.isRequired, + errActions: PropTypes.object.isRequired, + specActions: PropTypes.object.isRequired, + specSelectors: PropTypes.object.isRequired, + layoutSelectors: PropTypes.object.isRequired, + layoutActions: PropTypes.object.isRequired, + getComponent: PropTypes.func.isRequired + } + + render() { + const { getComponent } = this.props + const Container = getComponent("Container") + const Row = getComponent("Row") + const Col = getComponent("Col") + const Topbar = getComponent("Topbar", true) + const BaseLayout = getComponent("BaseLayout", true) + const OnlineValidatorBadge = getComponent("onlineValidatorBadge", true) + + return ( + + {Topbar ? : null} + + + + + + + + ) + } + +} diff --git a/frontend/web/api-doc/src/style/_authorize.scss b/frontend/web/api-doc/src/style/_authorize.scss new file mode 100644 index 0000000..24fc09c --- /dev/null +++ b/frontend/web/api-doc/src/style/_authorize.scss @@ -0,0 +1,107 @@ +.auth-btn-wrapper +{ + display: flex; + + padding: 10px 0; + + justify-content: center; + + .btn-done { + margin-right: 1em; + } +} + +.auth-wrapper +{ + display: flex; + + flex: 1; + justify-content: flex-end; + + .authorize + { + padding-right: 20px; + margin-left: 10px; + margin-right: 10px; + } +} + +.auth-container +{ + margin: 0 0 10px 0; + padding: 10px 20px; + + border-bottom: 1px solid $auth-container-border-color; + + &:last-of-type + { + margin: 0; + padding: 10px 20px; + + border: 0; + } + + h4 + { + margin: 5px 0 15px 0 !important; + } + + .wrapper + { + margin: 0; + padding: 0; + } + + input[type=text], + input[type=password] + { + min-width: 230px; + } + + .errors + { + font-size: 12px; + + padding: 10px; + + border-radius: 4px; + + background-color: #ffeeee; + + color: red; + + margin: 1em; + + @include text_code(); + + b + { + text-transform: capitalize; + margin-right: 1em; + } + } +} + +.scopes +{ + h2 + { + font-size: 14px; + + @include text_headline(); + + a + { + font-size: 12px; + color: $auth-select-all-none-link-font-color; + cursor: pointer; + padding-left: 10px; + text-decoration: underline; + } + } +} + +.scope-def +{ + padding: 0 0 20px 0; +} diff --git a/frontend/web/api-doc/src/style/_buttons.scss b/frontend/web/api-doc/src/style/_buttons.scss new file mode 100644 index 0000000..55dbd26 --- /dev/null +++ b/frontend/web/api-doc/src/style/_buttons.scss @@ -0,0 +1,214 @@ +.btn +{ + font-size: 14px; + font-weight: bold; + + padding: 5px 23px; + + transition: all .3s; + + border: 2px solid $btn-border-color; + border-radius: 4px; + background: transparent; + box-shadow: 0 1px 2px rgba($btn-box-shadow-color,.1); + + @include text_headline(); + + &.btn-sm + { + font-size: 12px; + padding: 4px 23px; + } + + &[disabled] + { + cursor: not-allowed; + + opacity: .3; + } + + &:hover + { + box-shadow: 0 0 5px rgba($btn-box-shadow-color,.3); + } + + &.cancel + { + border-color: $btn-cancel-border-color; + background-color: $btn-cancel-background-color; + @include text_headline($btn-cancel-font-color); + } + + &.authorize + { + line-height: 1; + + display: inline; + + color: $btn-authorize-font-color; + border-color: $btn-authorize-border-color; + background-color: $btn-authorize-background-color; + + span + { + float: left; + + padding: 4px 20px 0 0; + } + + svg + { + fill: $btn-authorize-svg-fill-color; + } + } + + &.execute + { + background-color: $btn-execute-background-color-alt; + color: $btn-execute-font-color; + border-color: $btn-execute-border-color; + } +} + +.btn-group +{ + display: flex; + + padding: 30px; + + .btn + { + flex: 1; + + &:first-child + { + border-radius: 4px 0 0 4px; + } + + &:last-child + { + border-radius: 0 4px 4px 0; + } + } +} + +.authorization__btn +{ + padding: 0 0 0 10px; + + border: none; + background: none; + + &.locked + { + opacity: 1; + } + + &.unlocked + { + opacity: .4; + } +} + +.opblock-summary-control, +.models-control, +.model-box-control +{ + all: inherit; + flex: 1; + border-bottom: 0; + padding: 0; + cursor: pointer; + + &:focus { + outline: auto; + } +} + +.expand-methods, +.expand-operation +{ + border: none; + background: none; + + svg + { + width: 20px; + height: 20px; + } +} + +.expand-methods +{ + padding: 0 10px; + + &:hover + { + svg + { + fill: $expand-methods-svg-fill-color-hover; + } + } + + svg + { + transition: all .3s; + + fill: $expand-methods-svg-fill-color; + } +} + +button +{ + cursor: pointer; + + &.invalid + { + @include invalidFormElement(); + } +} + +.copy-to-clipboard +{ + position: absolute; + display: flex; + justify-content: center; + align-items: center; + bottom: 10px; + right: 100px; + width: 30px; + height: 30px; + background: #7d8293; + border-radius: 4px; + border: none; + + button + { + flex-grow: 1; + flex-shrink: 1; + border: none; + height: 25px; + background: url("data:image/svg+xml, ") center center no-repeat; + } +} + +// overrides for smaller copy button for curl command +.curl-command .copy-to-clipboard +{ + bottom: 5px; + right: 10px; + width: 20px; + height: 20px; + + button + { + height: 18px; + } +} + +// overrides for copy to clipboard button +.opblock .opblock-summary .view-line-link.copy-to-clipboard +{ + height: 26px; + position: unset; +} \ No newline at end of file diff --git a/frontend/web/api-doc/src/style/_errors.scss b/frontend/web/api-doc/src/style/_errors.scss new file mode 100644 index 0000000..8d36901 --- /dev/null +++ b/frontend/web/api-doc/src/style/_errors.scss @@ -0,0 +1,83 @@ +.errors-wrapper +{ + margin: 20px; + padding: 10px 20px; + + animation: scaleUp .5s; + + border: 2px solid $_color-delete; + border-radius: 4px; + background: rgba($_color-delete, .1); + + .error-wrapper + { + margin: 0 0 10px 0; + } + + .errors + { + h4 + { + font-size: 14px; + + margin: 0; + + @include text_code(); + } + + small + { + color: $errors-wrapper-errors-small-font-color; + } + + .message + { + white-space: pre-line; + + &.thrown + { + max-width: 100%; + } + } + + .error-line + { + text-decoration: underline; + cursor: pointer; + } + } + + hgroup + { + display: flex; + + align-items: center; + + h4 + { + font-size: 20px; + + margin: 0; + + flex: 1; + @include text_headline(); + } + } +} + + +@keyframes scaleUp +{ + 0% + { + transform: scale(.8); + + opacity: 0; + } + 100% + { + transform: scale(1); + + opacity: 1; + } +} diff --git a/frontend/web/api-doc/src/style/_form.scss b/frontend/web/api-doc/src/style/_form.scss new file mode 100644 index 0000000..555dd01 --- /dev/null +++ b/frontend/web/api-doc/src/style/_form.scss @@ -0,0 +1,235 @@ +select +{ + font-size: 14px; + font-weight: bold; + + padding: 5px 40px 5px 10px; + + border: 2px solid $form-select-border-color; + border-radius: 4px; + background: $form-select-background-color url('data:image/svg+xml, ') right 10px center no-repeat; + background-size: 20px; + box-shadow: 0 1px 2px 0 rgba($form-select-box-shadow-color, .25); + + @include text_headline(); + appearance: none; + + &[multiple] + { + margin: 5px 0; + padding: 5px; + + background: $form-select-background-color; + } + + &.invalid { + @include invalidFormElement(); + } +} + +.opblock-body select +{ + min-width: 230px; + @media (max-width: 768px) + { + min-width: 180px; + } + @media (max-width: 640px) + { + width: 100%; + min-width: 100%; + } +} + +label +{ + font-size: 12px; + font-weight: bold; + + margin: 0 0 5px 0; + + @include text_headline(); +} + +input[type=text], +input[type=password], +input[type=search], +input[type=email], +input[type=file] +{ + line-height: 1; + + @media (max-width: 768px) { + max-width: 175px; + } +} + + +input[type=text], +input[type=password], +input[type=search], +input[type=email], +input[type=file], +textarea +{ + min-width: 100px; + margin: 5px 0; + padding: 8px 10px; + + border: 1px solid $form-input-border-color; + border-radius: 4px; + background: $form-input-background-color; + + + &.invalid + { + @include invalidFormElement(); + } + +} + +input, +textarea, +select { + &[disabled] { + // opacity: 0.85; + background-color: #fafafa; + color: #888; + cursor: not-allowed; + } +} + +select[disabled] { + border-color: #888; +} + +textarea[disabled] { + background-color: #41444e; + color: #fff; +} + +@keyframes shake +{ + 10%, + 90% + { + transform: translate3d(-1px, 0, 0); + } + + 20%, + 80% + { + transform: translate3d(2px, 0, 0); + } + + 30%, + 50%, + 70% + { + transform: translate3d(-4px, 0, 0); + } + + 40%, + 60% + { + transform: translate3d(4px, 0, 0); + } +} + +textarea +{ + font-size: 12px; + + width: 100%; + min-height: 280px; + padding: 10px; + + border: none; + border-radius: 4px; + outline: none; + background: rgba($form-textarea-background-color,.8); + + @include text_code(); + + &:focus + { + border: 2px solid $form-textarea-focus-border-color; + } + + &.curl + { + font-size: 12px; + + min-height: 100px; + margin: 0; + padding: 10px; + + resize: none; + + border-radius: 4px; + background: $form-textarea-curl-background-color; + + @include text_code($form-textarea-curl-font-color); + } +} + + +.checkbox +{ + padding: 5px 0 10px; + + transition: opacity .5s; + + color: $form-checkbox-label-font-color; + + label + { + display: flex; + } + + p + { + font-weight: normal !important; + font-style: italic; + + margin: 0 !important; + + @include text_code(); + } + + input[type=checkbox] + { + display: none; + + & + label > .item + { + position: relative; + top: 3px; + + display: inline-block; + + width: 16px; + height: 16px; + margin: 0 8px 0 0; + padding: 5px; + + cursor: pointer; + + border-radius: 1px; + background: $form-checkbox-background-color; + box-shadow: 0 0 0 2px $form-checkbox-box-shadow-color; + + flex: none; + + &:active + { + transform: scale(.9); + } + } + + &:checked + label > .item + { + background: $form-checkbox-background-color url('data:image/svg+xml, ') center center no-repeat; + } + } +} diff --git a/frontend/web/api-doc/src/style/_information.scss b/frontend/web/api-doc/src/style/_information.scss new file mode 100644 index 0000000..d085959 --- /dev/null +++ b/frontend/web/api-doc/src/style/_information.scss @@ -0,0 +1,104 @@ +.info +{ + margin: 50px 0; + + &.failed-config + { + max-width: 880px; + margin-left: auto; + margin-right: auto; + text-align: center + } + + hgroup.main + { + margin: 0 0 20px 0; + a + { + font-size: 12px; + } + } + pre + { + font-size: 14px; + } + p, li, table + { + font-size: 14px; + + @include text_body(); + } + + h1, h2, h3, h4, h5 + { + @include text_body(); + } + + a + { + font-size: 14px; + + transition: all .4s; + + @include text_body($info-link-font-color); + + &:hover + { + color: darken($info-link-font-color-hover, 15%); + } + } + > div + { + margin: 0 0 5px 0; + } + + .base-url + { + font-size: 12px; + font-weight: 300 !important; + + margin: 0; + + @include text_code(); + } + + .title + { + font-size: 36px; + + margin: 0; + + @include text_body(); + + small + { + font-size: 10px; + + position: relative; + top: -5px; + + display: inline-block; + + margin: 0 0 0 5px; + padding: 2px 4px; + + vertical-align: super; + + border-radius: 57px; + background: $info-title-small-background-color; + + &.version-stamp + { + background-color: #89bf04; + } + + pre + { + margin: 0; + padding: 0; + + @include text_headline($info-title-small-pre-font-color); + } + } + } +} diff --git a/frontend/web/api-doc/src/style/_layout.scss b/frontend/web/api-doc/src/style/_layout.scss new file mode 100644 index 0000000..e5b979e --- /dev/null +++ b/frontend/web/api-doc/src/style/_layout.scss @@ -0,0 +1,1005 @@ +.wrapper +{ + width: 100%; + max-width: 1460px; + margin: 0 auto; + padding: 0 20px; + box-sizing: border-box; +} + +.opblock-tag-section +{ + display: flex; + flex-direction: column; +} + +.try-out.btn-group { + padding: 0; + display: flex; + flex: 0.1 2 auto; +} + +.try-out__btn { + margin-left: 1.25rem; +} + +.opblock-tag +{ + display: flex; + align-items: center; + + padding: 10px 20px 10px 10px; + + cursor: pointer; + transition: all .2s; + + border-bottom: 1px solid rgba($opblock-tag-border-bottom-color, .3); + + &:hover + { + background: rgba($opblock-tag-background-color-hover,.02); + } +} + +@mixin method($color) +{ + border-color: $color; + background: rgba($color, .1); + + .opblock-summary-method + { + background: $color; + } + + .opblock-summary + { + border-color: $color; + } + + .tab-header .tab-item.active h4 span:after + { + background: $color; + } +} + + + + +.opblock-tag +{ + font-size: 24px; + + margin: 0 0 5px 0; + + @include text_headline(); + + &.no-desc + { + span + { + flex: 1; + } + } + + svg + { + transition: all .4s; + } + + small + { + font-size: 14px; + font-weight: normal; + + flex: 2; + + padding: 0 10px; + + @include text_body(); + } + + >div + { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + flex: 1 1 150px; + font-weight: 400; + } + + @media (max-width: 640px) { + small + { + flex: 1; + } + + >div + { + flex: 1; + } + } + + .info__externaldocs + { + text-align: right; + } +} + +.parameter__type +{ + font-size: 12px; + + padding: 5px 0; + + @include text_code(); +} + +.parameter-controls { + margin-top: 0.75em; +} + +.examples { + &__title { + display: block; + font-size: 1.1em; + font-weight: bold; + margin-bottom: 0.75em; + } + + &__section { + margin-top: 1.5em; + } + &__section-header { + font-weight: bold; + font-size: .9rem; + margin-bottom: .5rem; + // color: #555; + } +} + +.examples-select { + margin-bottom: .75em; + display: inline-block; + .examples-select-element { + width: 100%; + } + &__section-label { + font-weight: bold; + font-size: .9rem; + margin-right: .5rem; + } +} + +.example { + &__section { + margin-top: 1.5em; + } + &__section-header { + font-weight: bold; + font-size: .9rem; + margin-bottom: .5rem; + // color: #555; + } +} + +.view-line-link +{ + position: relative; + top: 3px; + + width: 20px; + margin: 0 5px; + + cursor: pointer; + transition: all .5s; +} + + + +.opblock +{ + margin: 0 0 15px 0; + + border: 1px solid $opblock-border-color; + border-radius: 4px; + box-shadow: 0 0 3px rgba($opblock-box-shadow-color,.19); + + .tab-header + { + display: flex; + + flex: 1; + + .tab-item + { + padding: 0 40px; + + cursor: pointer; + + &:first-of-type + { + padding: 0 40px 0 0; + } + &.active + { + h4 + { + span + { + position: relative; + + + &:after + { + position: absolute; + bottom: -15px; + left: 50%; + + width: 120%; + height: 4px; + + content: ''; + transform: translateX(-50%); + + background: $opblock-tab-header-tab-item-active-h4-span-after-background-color; + } + } + } + } + } + } + + + &.is-open + { + .opblock-summary + { + border-bottom: 1px solid $opblock-isopen-summary-border-bottom-color; + } + } + + .opblock-section-header + { + display: flex; + align-items: center; + + padding: 8px 20px; + + min-height: 50px; + + background: rgba($opblock-isopen-section-header-background-color,.8); + box-shadow: 0 1px 2px rgba($opblock-isopen-section-header-box-shadow-color,.1); + + >label + { + font-size: 12px; + font-weight: bold; + + display: flex; + align-items: center; + + margin: 0; + margin-left: auto; + + @include text_headline(); + + >span + { + padding: 0 10px 0 0; + } + } + + h4 + { + font-size: 14px; + + flex: 1; + + margin: 0; + + @include text_headline(); + } + } + + .opblock-summary-method + { + font-size: 14px; + font-weight: bold; + + min-width: 80px; + padding: 6px 0; + + text-align: center; + + border-radius: 3px; + background: $opblock-summary-method-background-color; + text-shadow: 0 1px 0 rgba($opblock-summary-method-text-shadow-color,.1); + + @include text_headline($opblock-summary-method-font-color); + } + + .opblock-summary-path, + .opblock-summary-operation-id, + .opblock-summary-path__deprecated + { + font-size: 16px; + @media (max-width: 768px) { + font-size: 12px; + } + + + display: flex; + align-items: center; + + word-break: break-word; + + padding: 0 10px; + + @include text_code(); + + } + + .opblock-summary-path + { + flex-shrink: 0; + max-width: calc(100% - 110px - 15rem); + } + + @media (max-width: 640px) { + .opblock-summary-path + { + flex-shrink: 1; + max-width: 100%; + } + } + + .opblock-summary-path__deprecated + { + text-decoration: line-through; + } + + .opblock-summary-operation-id + { + font-size: 14px; + } + + .opblock-summary-description + { + font-size: 13px; + + flex: 1 1 auto; + + word-break: break-word; + + @include text_body(); + } + + .opblock-summary + { + display: flex; + align-items: center; + + padding: 5px; + + cursor: pointer; + + .view-line-link + { + position: relative; + top: 2px; + + width: 0; + margin: 0; + + cursor: pointer; + transition: all .5s; + } + + &:hover + { + .view-line-link + { + width: 18px; + margin: 0 5px; + + &.copy-to-clipboard { + width: 24px; + } + } + } + } + + + + &.opblock-post + { + @include method($_color-post); + } + + &.opblock-put + { + @include method($_color-put); + } + + &.opblock-delete + { + @include method($_color-delete); + } + + &.opblock-get + { + @include method($_color-get); + } + + &.opblock-patch + { + @include method($_color-patch); + } + + &.opblock-head + { + @include method($_color-head); + } + + &.opblock-options + { + @include method($_color-options); + } + + &.opblock-deprecated + { + opacity: .6; + + @include method($_color-disabled); + } + + .opblock-schemes + { + padding: 8px 20px; + + .schemes-title + { + padding: 0 10px 0 0; + } + } +} + +.filter +{ + .operation-filter-input + { + width: 100%; + margin: 20px 0; + padding: 10px 10px; + + border: 2px solid $operational-filter-input-border-color; + } +} + +.filter, .download-url-wrapper +{ + .failed + { + color: red; + } + + .loading + { + color: #aaa; + } +} + +.model-example { + margin-top: 1em; +} + +.tab +{ + display: flex; + + padding: 0; + + list-style: none; + + li + { + font-size: 12px; + + min-width: 60px; + padding: 0; + + cursor: pointer; + + @include text_headline(); + + &:first-of-type + { + position: relative; + + padding-left: 0; + padding-right: 12px; + + &:after + { + position: absolute; + top: 0; + right: 6px; + + width: 1px; + height: 100%; + + content: ''; + + background: rgba($tab-list-item-first-background-color,.2); + } + } + + &.active + { + font-weight: bold; + } + + button.tablinks + { + background: none; + border: 0; + padding: 0; + + color: inherit; + font-family: inherit; + font-weight: inherit; + } + } +} + +.opblock-description-wrapper, +.opblock-external-docs-wrapper, +.opblock-title_normal +{ + font-size: 12px; + + margin: 0 0 5px 0; + padding: 15px 20px; + + @include text_body(); + + h4 + { + font-size: 12px; + + margin: 0 0 5px 0; + + @include text_body(); + } + + p + { + font-size: 14px; + + margin: 0; + + @include text_body(); + } +} + +.opblock-external-docs-wrapper { + h4 { + padding-left: 0px; + } +} + +.execute-wrapper +{ + padding: 20px; + + text-align: right; + + .btn + { + width: 100%; + padding: 8px 40px; + } +} + +.body-param-options +{ + display: flex; + flex-direction: column; + + .body-param-edit + { + padding: 10px 0; + } + + label + { + padding: 8px 0; + select + { + margin: 3px 0 0 0; + } + } +} + +.responses-inner +{ + padding: 20px; + + h5, + h4 + { + font-size: 12px; + + margin: 10px 0 5px 0; + + @include text_body(); + } + + .curl + { + white-space: normal; + } +} + +.response-col_status +{ + font-size: 14px; + + @include text_body(); + + .response-undocumented + { + font-size: 11px; + + @include text_code($response-col-status-undocumented-font-color); + } +} + +.response-col_links +{ + padding-left: 2em; + max-width: 40em; + font-size: 14px; + + @include text_body(); + + .response-undocumented + { + font-size: 11px; + + @include text_code($response-col-links-font-color); + } + + .operation-link + { + margin-bottom: 1.5em; + + .description + { + margin-bottom: 0.5em; + } + } +} + +.opblock-body +{ + .opblock-loading-animation + { + display: block; + margin: 3em; + margin-left: auto; + margin-right: auto; + } +} + +.opblock-body pre.microlight +{ + font-size: 12px; + + margin: 0; + padding: 10px; + + white-space: pre-wrap; + word-wrap: break-word; + word-break: break-all; + word-break: break-word; + hyphens: auto; + + border-radius: 4px; + background: $opblock-body-background-color; + + overflow-wrap: break-word; + @include text_code($opblock-body-font-color); + + // disabled to have syntax highliting with react-syntax-highlight + // span + // { + // color: $opblock-body-font-color !important; + // } + + .headerline + { + display: block; + } +} + +.highlight-code { + position: relative; + + > .microlight { + overflow-y: auto; + max-height: 400px; + min-height: 6em; + + code { + white-space: pre-wrap !important; + word-break: break-all; + } + } +} +.curl-command { + position: relative; +} + +.download-contents { + position: absolute; + bottom: 10px; + right: 10px; + cursor: pointer; + background: #7d8293; + text-align: center; + padding: 5px; + border-radius: 4px; + font-family: sans-serif; + font-weight: 600; + color: white; + font-size: 14px; + height: 30px; + justify-content: center; + align-items: center; + display: flex; +} + +.scheme-container +{ + margin: 0 0 20px 0; + padding: 30px 0; + + background: $scheme-container-background-color; + box-shadow: 0 1px 2px 0 rgba($scheme-container-box-shadow-color,.15); + + .schemes + { + display: flex; + align-items: flex-end; + + > label + { + font-size: 12px; + font-weight: bold; + + display: flex; + flex-direction: column; + + margin: -20px 15px 0 0; + + @include text_headline(); + + select + { + min-width: 130px; + + text-transform: uppercase; + } + } + } +} + +.loading-container +{ + padding: 40px 0 60px; + margin-top: 1em; + min-height: 1px; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + + .loading + { + position: relative; + + + &:after + { + font-size: 10px; + font-weight: bold; + + position: absolute; + top: 50%; + left: 50%; + + content: 'loading'; + transform: translate(-50%,-50%); + text-transform: uppercase; + + @include text_headline(); + } + + &:before + { + position: absolute; + top: 50%; + left: 50%; + + display: block; + + width: 60px; + height: 60px; + margin: -30px -30px; + + content: ''; + animation: rotation 1s infinite linear, opacity .5s; + + opacity: 1; + border: 2px solid rgba($loading-container-before-border-color, .1); + border-top-color: rgba($loading-container-before-border-top-color, .6); + border-radius: 100%; + + backface-visibility: hidden; + + @keyframes rotation + { + to + { + transform: rotate(360deg); + } + } + } + } +} + +.response-controls { + padding-top: 1em; + display: flex; +} + +.response-control-media-type { + margin-right: 1em; + + &--accept-controller { + select { + border-color: $response-content-type-controls-accept-header-select-border-color; + } + } + + &__accept-message { + color: $response-content-type-controls-accept-header-small-font-color; + font-size: .7em; + } + + &__title { + display: block; + margin-bottom: 0.2em; + font-size: .7em; + } +} + +.response-control-examples { + &__title { + display: block; + margin-bottom: 0.2em; + font-size: .7em; + } +} + +@keyframes blinker +{ + 50% + { + opacity: 0; + } +} + +.hidden +{ + display: none; +} + +.no-margin +{ + height: auto; + border: none; + margin: 0; + padding: 0; +} + +.float-right +{ + float: right; +} + +.svg-assets +{ + position: absolute; + width: 0; + height: 0; +} + +section +{ + h3 + { + @include text_headline(); + } +} + +a.nostyle { + text-decoration: inherit; + color: inherit; + cursor: pointer; + display: inline; + + &:visited { + text-decoration: inherit; + color: inherit; + cursor: pointer; + } +} + +.fallback +{ + padding: 1em; + color: #aaa; +} + +.version-pragma { + height: 100%; + padding: 5em 0px; + + &__message { + display: flex; + justify-content: center; + height: 100%; + font-size: 1.2em; + text-align: center; + line-height: 1.5em; + + padding: 0px .6em; + + > div { + max-width: 55ch; + flex: 1; + } + + code { + background-color: #dedede; + padding: 4px 4px 2px; + white-space: pre; + } + } +} + +.opblock-link +{ + font-weight: normal; + + &.shown + { + font-weight: bold; + } +} + +span +{ + &.token-string + { + color: #555; + } + + &.token-not-formatted + { + color: #555; + font-weight: bold; + } +} diff --git a/frontend/web/api-doc/src/style/_markdown.scss b/frontend/web/api-doc/src/style/_markdown.scss new file mode 100644 index 0000000..2675f58 --- /dev/null +++ b/frontend/web/api-doc/src/style/_markdown.scss @@ -0,0 +1,29 @@ +.markdown, .renderedMarkdown { + p, pre { + margin: 1em auto; + + word-break: break-all; /* Fallback trick */ + word-break: break-word; + } + pre { + color: black; + font-weight: normal; + white-space: pre-wrap; + background: none; + padding: 0px; + } + + code { + font-size: 14px; + padding: 5px 7px; + + border-radius: 4px; + background: rgba($info-code-background-color,.05); + + @include text_code($info-code-font-color); + } + + pre > code { + display: block; + } +} diff --git a/frontend/web/api-doc/src/style/_mixins.scss b/frontend/web/api-doc/src/style/_mixins.scss new file mode 100644 index 0000000..096dc0e --- /dev/null +++ b/frontend/web/api-doc/src/style/_mixins.scss @@ -0,0 +1,173 @@ +// - - - - - - - - - - - - - - - - - - - +// - - _mixins.scss module +// styles for the _mixins.scss module +@function calculateRem($size) +{ + $remSize: $size / 16px; + @return $remSize * 1rem; +} + +@mixin font-size($size) +{ + font-size: $size; + font-size: calculateRem($size); +} + +%clearfix +{ + &:before, + &:after + { + display: table; + + content: ' '; + } + &:after + { + clear: both; + } +} + +@mixin size($width, $height: $width) +{ + width: $width; + height: $height; +} + +$ease: ( + in-quad: cubic-bezier(.550, .085, .680, .530), + in-cubic: cubic-bezier(.550, .055, .675, .190), + in-quart: cubic-bezier(.895, .030, .685, .220), + in-quint: cubic-bezier(.755, .050, .855, .060), + in-sine: cubic-bezier(.470, .000, .745, .715), + in-expo: cubic-bezier(.950, .050, .795, .035), + in-circ: cubic-bezier(.600, .040, .980, .335), + in-back: cubic-bezier(.600, -.280, .735, .045), + out-quad: cubic-bezier(.250, .460, .450, .940), + out-cubic: cubic-bezier(.215, .610, .355, 1.000), + out-quart: cubic-bezier(.165, .840, .440, 1.000), + out-quint: cubic-bezier(.230, 1.000, .320, 1.000), + out-sine: cubic-bezier(.390, .575, .565, 1.000), + out-expo: cubic-bezier(.190, 1.000, .220, 1.000), + out-circ: cubic-bezier(.075, .820, .165, 1.000), + out-back: cubic-bezier(.175, .885, .320, 1.275), + in-out-quad: cubic-bezier(.455, .030, .515, .955), + in-out-cubic: cubic-bezier(.645, .045, .355, 1.000), + in-out-quart: cubic-bezier(.770, .000, .175, 1.000), + in-out-quint: cubic-bezier(.860, .000, .070, 1.000), + in-out-sine: cubic-bezier(.445, .050, .550, .950), + in-out-expo: cubic-bezier(1.000, .000, .000, 1.000), + in-out-circ: cubic-bezier(.785, .135, .150, .860), + in-out-back: cubic-bezier(.680, -.550, .265, 1.550) +); + +@function ease($key) +{ + @if map-has-key($ease, $key) + { + @return map-get($ease, $key); + } + + @warn 'Unkown \'#{$key}\' in $ease.'; + @return null; +} + + +@mixin ease($key) +{ + transition-timing-function: ease($key); +} + +@mixin text-truncate +{ + overflow: hidden; + + white-space: nowrap; + text-overflow: ellipsis; +} + +@mixin aspect-ratio($width, $height) +{ + position: relative; + &:before + { + display: block; + + width: 100%; + padding-top: ($height / $width) * 100%; + + content: ''; + } + > iframe + { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } +} + +$browser-context: 16; + +@function em($pixels, $context: $browser-context) +{ + @if (unitless($pixels)) + { + $pixels: $pixels * 1px; + } + + @if (unitless($context)) + { + $context: $context * 1px; + } + + @return $pixels / $context * 1em; +} + +@mixin maxHeight($height) +{ + @media (max-height: $height) + { + @content; + } +} + + +@mixin breakpoint($class) +{ + @if $class == tablet + { + @media (min-width: 768px) and (max-width: 1024px) + { + @content; + } + } + + @else if $class == mobile + { + @media (min-width: 320px) and (max-width : 736px) + { + @content; + } + } + + @else if $class == desktop + { + @media (min-width: 1400px) + { + @content; + } + } + + @else + { + @warn 'Breakpoint mixin supports: tablet, mobile, desktop'; + } +} + +@mixin invalidFormElement() { + animation: shake .4s 1; + border-color: $_color-delete; + background: lighten($_color-delete, 35%); +} diff --git a/frontend/web/api-doc/src/style/_modal.scss b/frontend/web/api-doc/src/style/_modal.scss new file mode 100644 index 0000000..374365f --- /dev/null +++ b/frontend/web/api-doc/src/style/_modal.scss @@ -0,0 +1,102 @@ +.dialog-ux +{ + position: fixed; + z-index: 9999; + top: 0; + right: 0; + bottom: 0; + left: 0; + + .backdrop-ux + { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + + background: rgba($dialog-ux-backdrop-background-color,.8); + } + + .modal-ux + { + position: absolute; + z-index: 9999; + top: 50%; + left: 50%; + + width: 100%; + min-width: 300px; + max-width: 650px; + + transform: translate(-50%,-50%); + + border: 1px solid $dialog-ux-modal-border-color; + border-radius: 4px; + background: $dialog-ux-modal-background-color; + box-shadow: 0 10px 30px 0 rgba($dialog-ux-modal-box-shadow-color,.20); + } + + .modal-ux-content + { + overflow-y: auto; + + max-height: 540px; + padding: 20px; + + p + { + font-size: 12px; + + margin: 0 0 5px 0; + + color: $dialog-ux-modal-content-font-color; + + @include text_body(); + } + + h4 + { + font-size: 18px; + font-weight: 600; + + margin: 15px 0 0 0; + + @include text_headline(); + } + } + + .modal-ux-header + { + display: flex; + + padding: 12px 0; + + border-bottom: 1px solid $dialog-ux-modal-header-border-bottom-color; + + align-items: center; + + .close-modal + { + padding: 0 10px; + + border: none; + background: none; + + appearance: none; + } + + + h3 + { + font-size: 20px; + font-weight: 600; + + margin: 0; + padding: 0 20px; + + flex: 1; + @include text_headline(); + } + } +} diff --git a/frontend/web/api-doc/src/style/_models.scss b/frontend/web/api-doc/src/style/_models.scss new file mode 100644 index 0000000..6683c56 --- /dev/null +++ b/frontend/web/api-doc/src/style/_models.scss @@ -0,0 +1,366 @@ +.model +{ + font-size: 12px; + font-weight: 300; + + @include text_code(); + + .deprecated + { + span, + td + { + color: $model-deprecated-font-color !important; + } + + > td:first-of-type { + text-decoration: line-through; + } + } + &-toggle + { + font-size: 10px; + + position: relative; + top: 6px; + + display: inline-block; + + margin: auto .3em; + + cursor: pointer; + transition: transform .15s ease-in; + transform: rotate(90deg); + transform-origin: 50% 50%; + + &.collapsed + { + transform: rotate(0deg); + } + + &:after + { + display: block; + + width: 20px; + height: 20px; + + content: ''; + + background: url('data:image/svg+xml, ') center no-repeat; + background-size: 100%; + } + } + + &-jump-to-path + { + position: relative; + + cursor: pointer; + + .view-line-link + { + position: absolute; + top: -.4em; + + cursor: pointer; + } + } + + &-title + { + position: relative; + + &:hover .model-hint + { + visibility: visible; + } + } + + &-hint + { + position: absolute; + top: -1.8em; + + visibility: hidden; + + padding: .1em .5em; + + white-space: nowrap; + + color: $model-hint-font-color; + border-radius: 4px; + background: rgba($model-hint-background-color,.7); + } + + p + { + margin: 0 0 1em 0; + } + + .property + { + color: #999; + font-style: italic; + + &.primitive + { + color: #6b6b6b; + } + } + + .external-docs + { + color: #666; + font-weight: normal; + } +} + +table.model +{ + tr + { + &.description + { + color: #666; + font-weight: normal; + + td:first-child + { + font-weight: bold; + } + } + + &.property-row + { + &.required td:first-child + { + font-weight: bold; + } + + td + { + vertical-align: top; + + &:first-child + { + padding-right: 0.2em; + } + } + + .star + { + color: red; + } + } + + &.extension + { + color: #777; + + td:last-child + { + vertical-align: top; + } + } + + &.external-docs + { + td:first-child + { + font-weight: bold; + } + } + + .renderedMarkdown p:first-child + { + margin-top: 0; + } + } +} + +section.models +{ + margin: 30px 0; + + border: 1px solid rgba($section-models-border-color, .3); + border-radius: 4px; + + .pointer + { + cursor: pointer; + } + + &.is-open + { + padding: 0 0 20px; + h4 + { + margin: 0 0 5px 0; + + border-bottom: 1px solid rgba($section-models-isopen-h4-border-bottom-color, .3); + } + } + h4 + { + font-size: 16px; + + display: flex; + align-items: center; + + margin: 0; + padding: 10px 20px 10px 10px; + + cursor: pointer; + transition: all .2s; + + @include text_headline($section-models-h4-font-color); + + svg + { + transition: all .4s; + } + + span + { + flex: 1; + } + + &:hover + { + background: rgba($section-models-h4-background-color-hover,.02); + } + } + + h5 + { + font-size: 16px; + + margin: 0 0 10px 0; + + @include text_headline($section-models-h5-font-color); + } + + .model-jump-to-path + { + position: relative; + top: 5px; + } + + .model-container + { + margin: 0 20px 15px; + position: relative; + + transition: all .5s; + + border-radius: 4px; + background: rgba($section-models-model-container-background-color,.05); + + &:hover + { + background: rgba($section-models-model-container-background-color,.07); + } + + &:first-of-type + { + margin: 20px; + } + + &:last-of-type + { + margin: 0 20px; + } + + .models-jump-to-path { + position: absolute; + top: 8px; + right: 5px; + opacity: 0.65; + } + } + + .model-box + { + background: none; + } +} + + +.model-box +{ + padding: 10px; + display: inline-block; + + border-radius: 4px; + background: rgba($section-models-model-box-background-color,.1); + + .model-jump-to-path + { + position: relative; + top: 4px; + } + + &.deprecated + { + opacity: .5; + } +} + + +.model-title +{ + font-size: 16px; + + @include text_headline($section-models-model-title-font-color); + + img + { + margin-left: 1em; + position: relative; + bottom: 0px; + } +} + +.model-deprecated-warning +{ + font-size: 16px; + font-weight: 600; + + margin-right: 1em; + + @include text_headline($_color-delete); +} + + +span +{ + > span.model + { + .brace-close + { + padding: 0 0 0 10px; + } + } +} + +.prop-name +{ + display: inline-block; + + margin-right: 1em; +} + +.prop-type +{ + color: $prop-type-font-color; +} + +.prop-enum +{ + display: block; +} +.prop-format +{ + color: $prop-format-font-color; +} diff --git a/frontend/web/api-doc/src/style/_servers.scss b/frontend/web/api-doc/src/style/_servers.scss new file mode 100644 index 0000000..fe53a54 --- /dev/null +++ b/frontend/web/api-doc/src/style/_servers.scss @@ -0,0 +1,66 @@ +.servers +{ + > label + { + font-size: 12px; + + margin: -20px 15px 0 0; + + @include text_headline(); + + select + { + min-width: 130px; + max-width: 100%; + width: 100%; + } + } + + h4.message { + padding-bottom: 2em; + } + + table { + tr { + width: 30em; + } + td { + display: inline-block; + max-width: 15em; + vertical-align: middle; + padding-top: 10px; + padding-bottom: 10px; + + &:first-of-type { + padding-right: 1em; + } + + input { + width: 100%; + height: 100%; + } + } + } + + .computed-url { + margin: 2em 0; + + code { + display: inline-block; + padding: 4px; + font-size: 16px; + margin: 0 1em; + } + } +} + +.servers-title { + font-size: 12px; + font-weight: bold; +} + +.operation-servers { + h4.message { + margin-bottom: 2em; + } +} diff --git a/frontend/web/api-doc/src/style/_split-pane-mode.scss b/frontend/web/api-doc/src/style/_split-pane-mode.scss new file mode 100644 index 0000000..4a53d4c --- /dev/null +++ b/frontend/web/api-doc/src/style/_split-pane-mode.scss @@ -0,0 +1,3 @@ +.Resizer.vertical.disabled { + display: none; +} \ No newline at end of file diff --git a/frontend/web/api-doc/src/style/_table.scss b/frontend/web/api-doc/src/style/_table.scss new file mode 100644 index 0000000..3cd1281 --- /dev/null +++ b/frontend/web/api-doc/src/style/_table.scss @@ -0,0 +1,206 @@ +table +{ + width: 100%; + padding: 0 10px; + + border-collapse: collapse; + + &.model + { + tbody + { + tr + { + td + { + padding: 0; + + vertical-align: top; + + &:first-of-type + { + width: 174px; + padding: 0 0 0 2em; + } + } + } + } + } + + &.headers + { + td + { + font-size: 12px; + font-weight: 300; + + vertical-align: middle; + + @include text_code(); + } + + .header-example + { + color: #999; + font-style: italic; + } + } + + tbody + { + tr + { + td + { + padding: 10px 0 0 0; + + vertical-align: top; + + &:first-of-type + { + min-width: 6em; + padding: 10px 0; + } + } + } + } + + thead + { + tr + { + th, + td + { + font-size: 12px; + font-weight: bold; + + padding: 12px 0; + + text-align: left; + + border-bottom: 1px solid rgba($table-thead-td-border-bottom-color, .2); + + @include text_body(); + } + } + } +} + +.parameters-col_description +{ + width: 99%; // forces other columns to shrink to their content widths + margin-bottom: 2em; + input + { + width: 100%; + max-width: 340px; + } + + select { + border-width: 1px; + } + + .markdown { + p { + margin: 0; + } + } +} + +.parameter__name +{ + font-size: 16px; + font-weight: normal; + + // hack to give breathing room to the name column + // TODO: refactor all of this to flexbox + margin-right: .75em; + + @include text_headline(); + + &.required + { + font-weight: bold; + + span + { + color: red; + } + + &:after + { + font-size: 10px; + + position: relative; + top: -6px; + + padding: 5px; + + content: 'required'; + + color: rgba($table-parameter-name-required-font-color, .6); + } + } +} + +.parameter__in, +.parameter__extension +{ + font-size: 12px; + font-style: italic; + + @include text_code($table-parameter-in-font-color); +} + +.parameter__deprecated +{ + font-size: 12px; + font-style: italic; + + @include text_code($table-parameter-deprecated-font-color); +} + +.parameter__empty_value_toggle { + display: block; + font-size: 13px; + padding-top: 5px; + padding-bottom: 12px; + + input { + margin-right: 7px; + } + + &.disabled { + opacity: 0.7; + } +} + + +.table-container +{ + padding: 20px; +} + + +.response-col_description { + width: 99%; // forces other columns to shrink to their content widths + + .markdown { + p { + margin: 0; + } + } +} + +.response-col_links { + min-width: 6em; +} + +.response__extension +{ + font-size: 12px; + font-style: italic; + + @include text_code($table-parameter-in-font-color); +} diff --git a/frontend/web/api-doc/src/style/_topbar.scss b/frontend/web/api-doc/src/style/_topbar.scss new file mode 100644 index 0000000..f6c037a --- /dev/null +++ b/frontend/web/api-doc/src/style/_topbar.scss @@ -0,0 +1,96 @@ +.topbar +{ + padding: 10px 0; + + background-color: $topbar-background-color; + .topbar-wrapper + { + display: flex; + align-items: center; + } + a + { + font-size: 1.5em; + font-weight: bold; + + display: flex; + align-items: center; + flex: 1; + + max-width: 300px; + + text-decoration: none; + + @include text_headline($topbar-link-font-color); + + span + { + margin: 0; + padding: 0 10px; + } + } + + .download-url-wrapper + { + display: flex; + flex: 3; + justify-content: flex-end; + + input[type=text] + { + width: 100%; + margin: 0; + + border: 2px solid $topbar-download-url-wrapper-element-border-color; + border-radius: 4px 0 0 4px; + outline: none; + } + + .select-label + { + display: flex; + align-items: center; + + width: 100%; + max-width: 600px; + margin: 0; + color: #f0f0f0; + span + { + font-size: 16px; + + flex: 1; + + padding: 0 10px 0 0; + + text-align: right; + } + + select + { + flex: 2; + + width: 100%; + + border: 2px solid $topbar-download-url-wrapper-element-border-color; + outline: none; + box-shadow: none; + } + } + + + .download-url-button + { + font-size: 16px; + font-weight: bold; + + padding: 4px 30px; + + border: none; + border-radius: 0 4px 4px 0; + background: $topbar-download-url-button-background-color; + + @include text_headline($topbar-download-url-button-font-color); + } + } +} diff --git a/frontend/web/api-doc/src/style/_type.scss b/frontend/web/api-doc/src/style/_type.scss new file mode 100644 index 0000000..eed8829 --- /dev/null +++ b/frontend/web/api-doc/src/style/_type.scss @@ -0,0 +1,21 @@ +@mixin text_body($color: $text-body-default-font-color) +{ + font-family: sans-serif; + + color: $color; +} + +@mixin text_code($color: $text-code-default-font-color) +{ + font-family: monospace; + font-weight: 600; + + color: $color; +} + +@mixin text_headline($color: $text-headline-default-font-color) +{ + font-family: sans-serif; + + color: $color; +} diff --git a/frontend/web/api-doc/src/style/_variables.scss b/frontend/web/api-doc/src/style/_variables.scss new file mode 100644 index 0000000..6e5b9b7 --- /dev/null +++ b/frontend/web/api-doc/src/style/_variables.scss @@ -0,0 +1,229 @@ +// Base Colours +$black: #000 !default; +$white: #fff !default; +$gray-50: lighten($black, 92%) !default; //ebebeb +$gray-200: lighten($black, 62.75%) !default; // #a0a0a0 +$gray-300: lighten($black, 56.5%) !default; // #909090 +$gray-400: lighten($black, 50%) !default; // #808080 +$gray-500: lighten($black, 43.75%) !default; // #707070 +$gray-600: lighten($black, 37.5%) !default; // #606060 +$gray-650: lighten($black, 33.3%) !default; // #555555 +$gray-700: lighten($black, 31.25%) !default; // #505050 +$gray-800: lighten($black, 25%) !default; // #404040 +$gray-900: lighten($black, 18.75%) !default; // #303030 + +$cod-gray: #1b1b1b !default; +$agate-gray: #333333 !default; +$bright-gray: #3b4151 !default; +$mako-gray: #41444e !default; +$waterloo-gray: #7d8492 !default; +$alto-gray: #d9d9d9 !default; +$mercury-gray: #e4e4e4 !default; +$concrete-gray: #e8e8e8 !default; +$alabaster: #f7f7f7 !default; +$apple-green: #62a03f !default; +$green-haze: #009d77 !default; +$japanese-laurel: #008000 !default; +$persian-green: #00a0a7 !default; +$geyser-blue: #d8dde7 !default; +$dodger-blue: #1391ff !default; +$endeavour-blue: #005dae !default; +$scampi-purple: #55a !default; +$electric-violet: #7300e5 !default; +$persian-red: #cf3030 !default; +$mango-tango: #e97500 !default; + +// Theme + +$color-primary: #89bf04 !default; +$color-secondary: #9012fe !default; +$color-info: #4990e2 !default; +$color-warning: #ff6060 !default; +$color-danger: #f00 !default; + +$color-primary-hover: lighten($color-primary, .5%) !default; + +$_color-post: #49cc90 !default; +$_color-get: #61affe !default; +$_color-put: #fca130 !default; +$_color-delete: #f93e3e !default; +$_color-head: #9012fe !default; +$_color-patch: #50e3c2 !default; +$_color-disabled: #ebebeb !default; +$_color-options: #0d5aa7 !default; + +// Authorize + +$auth-container-border-color: $gray-50 !default; +$auth-select-all-none-link-font-color: $color-info !default; +// Buttons + +$btn-background-color: transparent !default; +$btn-border-color: $gray-400 !default; +$btn-font-color: inherit !default; +$btn-box-shadow-color: $black !default; + +$btn-authorize-background-color: transparent !default; +$btn-authorize-border-color: $_color-post !default; +$btn-authorize-font-color: $_color-post !default; +$btn-authorize-svg-fill-color: $_color-post !default; + +$btn-cancel-background-color: transparent !default; +$btn-cancel-border-color: $color-warning !default; +$btn-cancel-font-color: $color-warning !default; + +$btn-execute-background-color: transparent !default; +$btn-execute-border-color: $color-info !default; +$btn-execute-font-color: $white !default; +$btn-execute-background-color-alt: $color-info !default; + +$expand-methods-svg-fill-color: $gray-500 !default; +$expand-methods-svg-fill-color-hover: $gray-800 !default; + +// Errors + +$errors-wrapper-background-color: $_color-delete !default; +$errors-wrapper-border-color: $_color-delete !default; + +$errors-wrapper-errors-small-font-color: $gray-600 !default; + +// Form + +$form-select-background-color: $alabaster !default; +$form-select-border-color: $mako-gray !default; +$form-select-box-shadow-color: $black !default; + +$form-input-border-color: $alto-gray !default; +$form-input-background-color: $white !default; + +$form-textarea-background-color: $white !default; +$form-textarea-focus-border-color: $_color-get !default; + +$form-textarea-curl-background-color: $mako-gray !default; +$form-textarea-curl-font-color: $white !default; + +$form-checkbox-label-font-color: $gray-900 !default; +$form-checkbox-background-color: $concrete-gray !default; +$form-checkbox-box-shadow-color: $concrete-gray !default; + +// Information + +$info-code-background-color: $black !default; +$info-code-font-color: $_color-head !default; + +$info-link-font-color: $color-info !default; +$info-link-font-color-hover: $info-link-font-color !default; + +$info-title-small-background-color: $waterloo-gray !default; + +$info-title-small-pre-font-color: $white !default; + +// Layout + +$opblock-border-color: $black !default; +$opblock-box-shadow-color: $black !default; + +$opblock-tag-border-bottom-color: $bright-gray !default; +$opblock-tag-background-color-hover: $black !default; + +$opblock-tab-header-tab-item-active-h4-span-after-background-color: $gray-400 !default; + +$opblock-isopen-summary-border-bottom-color: $black !default; + +$opblock-isopen-section-header-background-color: $white !default; +$opblock-isopen-section-header-box-shadow-color: $black !default; + +$opblock-summary-method-background-color: $black !default; +$opblock-summary-method-font-color: $white !default; +$opblock-summary-method-text-shadow-color: $black !default; + +$operational-filter-input-border-color: $geyser-blue !default; + +$tab-list-item-first-background-color: $black !default; + +$response-col-status-undocumented-font-color: $gray-300 !default; + +$response-col-links-font-color: $gray-300 !default; + +$opblock-body-background-color: $agate-gray !default; +$opblock-body-font-color: $white !default; + +$scheme-container-background-color: $white !default; +$scheme-container-box-shadow-color: $black !default; + +$server-container-background-color: $white !default; +$server-container-box-shadow-color: $black !default; + +$server-container-computed-url-code-font-color: $gray-400 !default; + +$loading-container-before-border-color: $gray-650 !default; +$loading-container-before-border-top-color: $black !default; + +$response-content-type-controls-accept-header-select-border-color: $japanese-laurel !default; +$response-content-type-controls-accept-header-small-font-color: $japanese-laurel !default; + +// Modal + +$dialog-ux-backdrop-background-color: $black !default; + + +$dialog-ux-modal-background-color: $white !default; +$dialog-ux-modal-border-color: $gray-50 !default; +$dialog-ux-modal-box-shadow-color: $black !default; + +$dialog-ux-modal-content-font-color: $mako-gray !default; + +$dialog-ux-modal-header-border-bottom-color: $gray-50 !default; + +// Models + +$model-deprecated-font-color: $gray-200 !default; + +$model-hint-font-color: $gray-50 !default; +$model-hint-background-color: $black !default; + +$section-models-border-color: $bright-gray !default; + +$section-models-isopen-h4-border-bottom-color: $section-models-border-color !default; + +$section-models-h4-font-color: $gray-600 !default; +$section-models-h4-background-color-hover: $black !default; + +$section-models-h5-font-color: $gray-500 !default; + +$section-models-model-container-background-color: $black !default; + +$section-models-model-box-background-color: $black !default; + +$section-models-model-title-font-color: $gray-700 !default; + +$prop-type-font-color: $scampi-purple !default; + +$prop-format-font-color: $gray-600 !default; + +// Tables + +$table-thead-td-border-bottom-color: $bright-gray !default; + +$table-parameter-name-required-font-color: $color-danger !default; + +$table-parameter-in-font-color: $gray-400 !default; + +$table-parameter-deprecated-font-color: $color-danger !default; + +// Topbar + +$topbar-background-color: $cod-gray !default; + +$topbar-link-font-color: $white !default; + +$topbar-download-url-wrapper-element-border-color: $apple-green !default; + +$topbar-download-url-button-background-color: $apple-green !default; +$topbar-download-url-button-font-color: $white !default; + +// Type + +$text-body-default-font-color: $bright-gray !default; +$text-code-default-font-color: $bright-gray !default; +$text-headline-default-font-color: $bright-gray !default; diff --git a/frontend/web/api-doc/src/style/main.scss b/frontend/web/api-doc/src/style/main.scss new file mode 100644 index 0000000..7fb2beb --- /dev/null +++ b/frontend/web/api-doc/src/style/main.scss @@ -0,0 +1,21 @@ +.swagger-ui +{ + @import '~tachyons-sass/tachyons.scss'; + @import 'mixins'; + @import 'variables'; + @import 'type'; + @import 'layout'; + @import 'buttons'; + @import 'form'; + @import 'modal'; + @import 'models'; + @import 'servers'; + @import 'table'; + @import 'topbar'; + @import 'information'; + @import 'authorize'; + @import 'errors'; + @include text_body(); + @import 'split-pane-mode'; + @import 'markdown'; +} diff --git a/frontend/web/api-doc/swagger-config.yaml b/frontend/web/api-doc/swagger-config.yaml new file mode 100644 index 0000000..4ee3c5a --- /dev/null +++ b/frontend/web/api-doc/swagger-config.yaml @@ -0,0 +1,4 @@ +--- +url: "swagger.yaml" +dom_id: "#swagger-ui" +validatorUrl: "https://validator.swagger.io/validator" diff --git a/frontend/web/api-doc/swagger-ui-dist-package/.npmignore b/frontend/web/api-doc/swagger-ui-dist-package/.npmignore new file mode 100644 index 0000000..1071019 --- /dev/null +++ b/frontend/web/api-doc/swagger-ui-dist-package/.npmignore @@ -0,0 +1,2 @@ +README.md +deploy.sh diff --git a/frontend/web/api-doc/swagger-ui-dist-package/.npmrc b/frontend/web/api-doc/swagger-ui-dist-package/.npmrc new file mode 100644 index 0000000..ae64359 --- /dev/null +++ b/frontend/web/api-doc/swagger-ui-dist-package/.npmrc @@ -0,0 +1 @@ +//registry.npmjs.org/:_authToken=${NPM_TOKEN} diff --git a/frontend/web/api-doc/swagger-ui-dist-package/README.md b/frontend/web/api-doc/swagger-ui-dist-package/README.md new file mode 100644 index 0000000..6628422 --- /dev/null +++ b/frontend/web/api-doc/swagger-ui-dist-package/README.md @@ -0,0 +1,22 @@ +# Swagger UI Dist +[![NPM version](https://badge.fury.io/js/swagger-ui-dist.svg)](http://badge.fury.io/js/swagger-ui-dist) + +# API + +This module, `swagger-ui-dist`, exposes Swagger-UI's entire dist folder as a dependency-free npm module. +Use `swagger-ui` instead, if you'd like to have npm install dependencies for you. + +`SwaggerUIBundle` and `SwaggerUIStandalonePreset` can be imported: +```javascript + import { SwaggerUIBundle, SwaggerUIStandalonePreset } from "swagger-ui-dist" +``` + +To get an absolute path to this directory for static file serving, use the exported `getAbsoluteFSPath` method: + +```javascript +const swaggerUiAssetPath = require("swagger-ui-dist").getAbsoluteFSPath() + +// then instantiate server that serves files from the swaggerUiAssetPath +``` + +For anything else, check the [Swagger-UI](https://github.com/swagger-api/swagger-ui) repository. diff --git a/frontend/web/api-doc/swagger-ui-dist-package/absolute-path.js b/frontend/web/api-doc/swagger-ui-dist-package/absolute-path.js new file mode 100644 index 0000000..af42bc8 --- /dev/null +++ b/frontend/web/api-doc/swagger-ui-dist-package/absolute-path.js @@ -0,0 +1,14 @@ +/* + * getAbsoluteFSPath + * @return {string} When run in NodeJS env, returns the absolute path to the current directory + * When run outside of NodeJS, will return an error message + */ +const getAbsoluteFSPath = function () { + // detect whether we are running in a browser or nodejs + if (typeof module !== "undefined" && module.exports) { + return require("path").resolve(__dirname) + } + throw new Error('getAbsoluteFSPath can only be called within a Nodejs environment'); +} + +module.exports = getAbsoluteFSPath diff --git a/frontend/web/api-doc/swagger-ui-dist-package/deploy.sh b/frontend/web/api-doc/swagger-ui-dist-package/deploy.sh new file mode 100755 index 0000000..65b5cdd --- /dev/null +++ b/frontend/web/api-doc/swagger-ui-dist-package/deploy.sh @@ -0,0 +1,25 @@ +# Deploy `swagger-ui-dist` to npm. + +# Parameter Expansion: http://stackoverflow.com/questions/6393551/what-is-the-meaning-of-0-in-a-bash-script +cd "${0%/*}" + +# Get UI version +UI_VERSION=$(node -p "require('../package.json').version") + +# Replace our version placeholder with UI's version +sed -i "s|\$\$VERSION|$UI_VERSION|g" package.json + +# Copy UI's dist files to our directory +cp ../dist/* . + +# Copy LICENSE & NOTICE to our directory +cp ../LICENSE . +cp ../NOTICE . + +if [ "$PUBLISH_DIST" = "true" ] || [ "$TRAVIS" = "true" ] ; then + npm publish . +else + npm pack . +fi + +find . -not -name .npmignore -not -name .npmrc -not -name deploy.sh -not -name index.js -not -name package.json -not -name README.md -not -name *.tgz -delete diff --git a/frontend/web/api-doc/swagger-ui-dist-package/index.js b/frontend/web/api-doc/swagger-ui-dist-package/index.js new file mode 100644 index 0000000..c229ec4 --- /dev/null +++ b/frontend/web/api-doc/swagger-ui-dist-package/index.js @@ -0,0 +1,17 @@ +try { + module.exports.SwaggerUIBundle = require("./swagger-ui-bundle.js") + module.exports.SwaggerUIStandalonePreset = require("./swagger-ui-standalone-preset.js") +} catch(e) { + // swallow the error if there's a problem loading the assets. + // allows this module to support providing the assets for browserish contexts, + // without exploding in a Node context. + // + // see https://github.com/swagger-api/swagger-ui/issues/3291#issuecomment-311195388 + // for more information. +} + +// `absolutePath` and `getAbsoluteFSPath` are both here because at one point, +// we documented having one and actually implemented the other. +// They were both retained so we don't break anyone's code. +module.exports.absolutePath = require("./absolute-path.js") +module.exports.getAbsoluteFSPath = require("./absolute-path.js") diff --git a/frontend/web/api-doc/swagger-ui-dist-package/package.json b/frontend/web/api-doc/swagger-ui-dist-package/package.json new file mode 100644 index 0000000..f4ddd7c --- /dev/null +++ b/frontend/web/api-doc/swagger-ui-dist-package/package.json @@ -0,0 +1,18 @@ +{ + "name": "swagger-ui-dist", + "version": "$$VERSION", + "main": "index.js", + "repository": "git@github.com:swagger-api/swagger-ui.git", + "contributors": [ + "(in alphabetical order)", + "Anna Bodnia ", + "Buu Nguyen ", + "Josh Ponelat ", + "Kyle Shockey ", + "Robert Barnwell ", + "Sahar Jafari " + ], + "license": "Apache-2.0", + "dependencies": {}, + "devDependencies": {} +} diff --git a/frontend/web/api-doc/swagger.yaml b/frontend/web/api-doc/swagger.yaml new file mode 100644 index 0000000..717c261 --- /dev/null +++ b/frontend/web/api-doc/swagger.yaml @@ -0,0 +1,6 @@ +openapi: 3.0.0 +info: + title: 'Документация для Гильдии' + version: '0.1' +paths: + '/products/{product_id}': { } diff --git a/frontend/web/api-doc/test/.eslintrc b/frontend/web/api-doc/test/.eslintrc new file mode 100644 index 0000000..0c15d61 --- /dev/null +++ b/frontend/web/api-doc/test/.eslintrc @@ -0,0 +1,7 @@ +env: + mocha: true +rules: + "react/prop-types": 1 # bah humbug + "react/require-render-return": 1 + "no-unused-vars": 1 # unused vars in tests can be useful for indicating a full signature + "no-global-assign": 1 \ No newline at end of file diff --git a/frontend/web/api-doc/test/build-artifacts/.eslintrc b/frontend/web/api-doc/test/build-artifacts/.eslintrc new file mode 100644 index 0000000..93c9934 --- /dev/null +++ b/frontend/web/api-doc/test/build-artifacts/.eslintrc @@ -0,0 +1,14 @@ +{ + "rules": { + "import/no-unresolved": 0, + "import/extensions": 0, + "quotes": [ + "error", + "single" + ], + "semi": [ + "error", + "always" + ] + } +} diff --git a/frontend/web/api-doc/test/build-artifacts/es-bundle-core.js b/frontend/web/api-doc/test/build-artifacts/es-bundle-core.js new file mode 100644 index 0000000..a1701eb --- /dev/null +++ b/frontend/web/api-doc/test/build-artifacts/es-bundle-core.js @@ -0,0 +1,8 @@ +import { describe, expect, test } from '@jest/globals'; +import SwaggerUI from '../../dist/swagger-ui-es-bundle-core'; + +describe('webpack browser es-bundle-core build', () => { + test('should export a function for es-bundle-core', () => { + expect(SwaggerUI).toBeInstanceOf(Function); + }); +}); diff --git a/frontend/web/api-doc/test/build-artifacts/es-bundle.js b/frontend/web/api-doc/test/build-artifacts/es-bundle.js new file mode 100644 index 0000000..79d70dd --- /dev/null +++ b/frontend/web/api-doc/test/build-artifacts/es-bundle.js @@ -0,0 +1,8 @@ +import { describe, expect, test } from '@jest/globals'; +import SwaggerUI from '../../dist/swagger-ui-es-bundle'; + +describe('webpack browser es-bundle build', () => { + test('should export a function for es-bundle', () => { + expect(SwaggerUI).toBeInstanceOf(Function); + }); +}); diff --git a/frontend/web/api-doc/test/build-artifacts/umd.js b/frontend/web/api-doc/test/build-artifacts/umd.js new file mode 100644 index 0000000..b6e7454 --- /dev/null +++ b/frontend/web/api-doc/test/build-artifacts/umd.js @@ -0,0 +1,8 @@ +import { describe, expect, test } from '@jest/globals'; +import SwaggerUI from '../../dist/swagger-ui-bundle'; + +describe('webpack browser umd build', () => { + test('should export a function for (umd) bundle', () => { + expect(SwaggerUI).toBeInstanceOf(Function); + }); +}); diff --git a/frontend/web/api-doc/test/components/highlight-code.jsx b/frontend/web/api-doc/test/components/highlight-code.jsx new file mode 100644 index 0000000..ebe53fe --- /dev/null +++ b/frontend/web/api-doc/test/components/highlight-code.jsx @@ -0,0 +1,30 @@ +import React from "react" +import expect from "expect" +import { shallow } from "enzyme" +import HighlightCode from "components/highlight-code" + +const fakeGetConfigs = () => ({syntaxHighlight: {activated: true, theme: "agate"}}) + +describe("", () => { + it("should render a Download button if downloadable", () => { + const props = {downloadable: true, getConfigs: fakeGetConfigs } + const wrapper = shallow() + expect(wrapper.find(".download-contents").length).toEqual(1) + }) + + it("should render a Copy To Clipboard button if copyable", () => { + const props = {canCopy: true, getConfigs: fakeGetConfigs } + const wrapper = shallow() + expect(wrapper.find("CopyToClipboard").length).toEqual(1) + }) + + it("should render values in a preformatted element", () => { + const value = "test text" + const props = {value: value, getConfigs: fakeGetConfigs} + const wrapper = shallow() + const preTag = wrapper.find("pre") + + expect(preTag.length).toEqual(1) + expect(preTag.contains(value)).toEqual(true) + }) +}) diff --git a/frontend/web/api-doc/test/components/response-body.jsx b/frontend/web/api-doc/test/components/response-body.jsx new file mode 100644 index 0000000..3b7502c --- /dev/null +++ b/frontend/web/api-doc/test/components/response-body.jsx @@ -0,0 +1,41 @@ +import React from "react" +import expect from "expect" +import { shallow } from "enzyme" +import ResponseBody from "components/response-body" + +describe("", function() { + const highlightCodeComponent = () => null + const components = { + highlightCode: highlightCodeComponent + } + const props = { + getComponent: c => components[c], + } + + it("renders ResponseBody as 'application/json'", function() { + props.contentType = "application/json" + props.content = "{\"key\": \"a test value\"}" + const wrapper = shallow() + expect(wrapper.find("highlightCodeComponent").length).toEqual(1) + }) + + it("renders ResponseBody as 'text/html'", function() { + props.contentType = "application/json" + props.content = "Result" + const wrapper = shallow() + expect(wrapper.find("highlightCodeComponent").length).toEqual(1) + }) + + it("renders ResponseBody as 'image/svg'", function() { + props.contentType = "image/svg" + const wrapper = shallow() + expect(wrapper.find("highlightCodeComponent").length).toEqual(0) + }) + + it("should render a copyable highlightCodeComponent for text types", function() { + props.contentType = "text/plain" + props.content = "test text" + const wrapper = shallow() + expect(wrapper.find("highlightCodeComponent[canCopy]").length).toEqual(1) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/.eslintrc b/frontend/web/api-doc/test/e2e-cypress/.eslintrc new file mode 100644 index 0000000..c916569 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/.eslintrc @@ -0,0 +1,8 @@ +globals: + cy: false + Cypress: false + expect: false + assert: false + +rules: + "no-console": 0 \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/fixtures/example.json b/frontend/web/api-doc/test/e2e-cypress/fixtures/example.json new file mode 100644 index 0000000..da18d93 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/helpers/multiple-examples.js b/frontend/web/api-doc/test/e2e-cypress/helpers/multiple-examples.js new file mode 100644 index 0000000..88a4504 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/helpers/multiple-examples.js @@ -0,0 +1,679 @@ +module.exports = { + ParameterPrimitiveTestCases, + RequestBodyPrimitiveTestCases, + ResponsePrimitiveTestCases, +} + +function ParameterPrimitiveTestCases({ +operationDomId, + parameterName, + exampleA, // { value, key } + exampleB, // { value, key } + exampleC, + customUserInput, + customExpectedUrlSubstring, +}) { + it("should render examples options without Modified Value by default", () => { + cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml") + .get(operationDomId) + .click() + .get(`tr[data-param-name="${parameterName}"]`) + .find(".examples-select option") + .should("have.length", exampleC ? 3 : 2) + // Ensure the relevant input is disabled + .get( + `tr[data-param-name="${parameterName}"] input, tr[data-param-name="${parameterName}"] textarea` + ) + .should("have.attr", "disabled") + // Switch to Try-It-Out + .get(".try-out__btn") + .click() + .get(`.opblock-section-request-body`) + .find(".examples-select option") + .should("have.length", exampleC ? 3 : 2) + }) + + it("should set default static and Try-It-Out values based on the first member", () => { + cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml") + // Expand the operation + .get(operationDomId) + .click() + // Assert on the static docs value + .get( + `tr[data-param-name="${parameterName}"] input,tr[data-param-name="${parameterName}"] textarea` + ) + .should("have.value", exampleA.value) + // Switch to Try-It-Out + .get(".try-out__btn") + .click() + // Assert on the Try-It-Out value + .get( + `tr[data-param-name="${parameterName}"] input,tr[data-param-name="${parameterName}"] textarea` + ) + .should("have.value", exampleA.value) + // Execute the operation + .get(".execute") + .click() + // Assert on the request URL + .get(".request-url") + .contains( + exampleA.serializedValue || `?message=${escape(exampleA.value)}` + ) + }) + + it("should set static and Try-It-Out values based on the second member", () => { + cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml") + // Expand the operation + .get(operationDomId) + .click() + // Choose the second example + .get("table.parameters .examples-select > select") + .select(exampleB.key) + // Assert on the static docs value + .get( + `tr[data-param-name="${parameterName}"] input,tr[data-param-name="${parameterName}"] textarea` + ) + .should("have.value", exampleB.value) + // Switch to Try-It-Out + .get(".try-out__btn") + .click() + // Assert on the Try-It-Out value + .get( + `tr[data-param-name="${parameterName}"] input,tr[data-param-name="${parameterName}"] textarea` + ) + .should("have.value", exampleB.value) + // Execute the operation + .get(".execute") + .click() + // Assert on the request URL + .get(".request-url") + .contains( + exampleB.serializedValue + ? `?${exampleB.serializedValue}` + : `?message=${escape(exampleB.value)}` + ) + }) + + it("should handle user-entered values correctly", () => { + cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml") + // Expand the operation + .get(operationDomId) + .click() + // Switch to Try-It-Out + .get(".try-out__btn") + .click() + // Modify the input value + .get( + `tr[data-param-name="${parameterName}"] input,tr[data-param-name="${parameterName}"] textarea` + ) + .clear() + .type(customUserInput) + // Assert on the active select menu item + .get("table.parameters .examples-select > select") + .find(":selected") + .should("have.text", "[Modified value]") + // Execute the operation + .get(".execute") + .click() + // Assert on the request URL + .get(".request-url") + .contains( + customExpectedUrlSubstring || `?message=${escape(customUserInput)}` + ) + }) + + it("should retain user-entered values correctly", () => { + cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml") + // Expand the operation + .get(operationDomId) + .click() + // Switch to Try-It-Out + .get(".try-out__btn") + .click() + // Modify the input value + .get( + `tr[data-param-name="${parameterName}"] input,tr[data-param-name="${parameterName}"] textarea` + ) + .clear() + .type(customUserInput) + // Select the first example + .get("table.parameters .examples-select > select") + .select(exampleA.key) + // Execute the operation + .get(".execute") + .click() + // Assert on the request URL + .get(".request-url") + .contains( + exampleA.serializedValue + ? `?${exampleA.serializedValue}` + : `?message=${escape(exampleA.value)}` + ) + // Select the modified value + .get("table.parameters .examples-select > select") + .select("__MODIFIED__VALUE__") + // Execute the operation + .get(".execute") + .click() + // Assert on the request URL + .get(".request-url") + .contains( + customExpectedUrlSubstring || `?message=${escape(customUserInput)}` + ) + }) +} + +function RequestBodyPrimitiveTestCases({ + operationDomId, + exampleA, // { value, key, summary } + exampleB, // { value, key, summary } + exampleC, + customUserInput, + customUserInputExpectedCurlSubstring, + primaryMediaType = "text/plain", + secondaryMediaType = "text/plain+other", +}) { + it("should render examples options without Modified Value by default", () => { + cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml") + .get(operationDomId) + .click() + .get(`.opblock-section-request-body`) + .find(".examples-select option") + .should("have.length", exampleC ? 3 : 2) + // Switch to Try-It-Out + .get(".try-out__btn") + .click() + .get(`.opblock-section-request-body`) + .find(".examples-select option") + .should("have.length", exampleC ? 3 : 2) + }) + + it("should set default static and Try-It-Out values based on the first member", () => { + cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml") + // Expand the operation + .get(operationDomId) + .click() + // Assert on the static docs value + .get(`.opblock-section-request-body .microlight`) + .should("include.text", exampleA.value) + // Switch to Try-It-Out + .get(".try-out__btn") + .click() + // Assert on the Try-It-Out value + .get(`.opblock-section-request-body textarea`) + .should("have.value", exampleA.value) + // Execute the operation + .get(".execute") + .click() + // Assert on the curl body + // TODO: use an interceptor instead of curl + .get(".curl") + .contains(`-d '${exampleA.serializedValue || exampleA.value}'`) + }) + + it("should set default static and Try-It-Out values based on choosing the second member in static mode", () => { + cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml") + // Expand the operation + .get(operationDomId) + .click() + // Choose the second example + .get(".opblock-section-request-body .examples-select > select") + .select(exampleB.key) + // Assert on the static docs value + .get(`.opblock-section-request-body .microlight`) + .should("include.text", exampleB.value) + // Switch to Try-It-Out + .get(".try-out__btn") + .click() + // Assert on the Try-It-Out value + .get(`.opblock-section-request-body textarea`) + .should("have.value", exampleB.value) + // Execute the operation + .get(".execute") + .click() + // Assert on the request URL + // TODO: use an interceptor instead of curl + .get(".curl") + .contains(`-d '${exampleB.serializedValue || exampleB.value}'`) + }) + + it("should set default static and Try-It-Out values based on choosing the second member in Try-It-Out mode", () => { + cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml") + // Expand the operation + .get(operationDomId) + .click() + // Switch to Try-It-Out + .get(".try-out__btn") + .click() + // Choose the second example + .get(".opblock-section-request-body .examples-select > select") + .select(exampleB.key) + // Assert on the Try-It-Out value + .get(`.opblock-section-request-body textarea`) + .should("have.value", exampleB.value) + // Execute the operation + .get(".execute") + .click() + // Assert on the request URL + // TODO: use an interceptor instead of curl + .get(".curl") + .contains(`-d '${exampleB.serializedValue || exampleB.value}'`) + // Switch to static docs + .get(".try-out__btn") + .click() + // Assert on the static docs value + .get(`.opblock-section-request-body .microlight`) + .should("include.text", exampleB.value) + }) + + it("should return the dropdown entry for an example when manually returning to its value", () => { + cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml") + // Expand the operation + .get(operationDomId) + .click() + // Assert on the static docs value + .get(`.opblock-section-request-body .microlight`) + .should("include.text", exampleA.value) + // Switch to Try-It-Out + .get(".try-out__btn") + .click() + // Assert on the Try-It-Out value + .get(`.opblock-section-request-body textarea`) + .should("have.value", exampleA.value) + // Clear the Try-It-Out value, replace it with custom value + .clear() + .type(customUserInput) + // Assert on the dropdown value + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("have.text", "[Modified value]") + // Modify the value again, going back to the example value + .get(`.opblock-section-request-body textarea`) + .clear() + .type(exampleA.value) + // Assert on the dropdown value returning to the example value + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("include.text", exampleA.summary) + }) + + it("should retain choosing a member in static docs when changing the media type", () => { + cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml") + // Expand the operation + .get(operationDomId) + .click() + // Choose the second example + .get(".opblock-section-request-body .examples-select > select") + .select(exampleB.key) + // Change the media type + .get(".opblock-section-request-body .content-type") + .select(secondaryMediaType) + // Assert on the static docs value + .get(`.opblock-section-request-body .microlight`) + .should("include.text", exampleB.value) + // Switch to Try-It-Out + .get(".try-out__btn") + .click() + // Assert on the Try-It-Out value + .get(`.opblock-section-request-body textarea`) + .should("have.value", exampleB.value) + // Execute the operation + .get(".execute") + .click() + // Assert on the request URL + // TODO: use an interceptor instead of curl + .get(".curl") + .contains(`-d '${exampleB.serializedValue || exampleB.value}'`) + }) + + it("should use the first example for the media type when changing the media type without prior interactions with the value", () => { + cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml") + // Expand the operation + .get(operationDomId) + .click() + // Change the media type + .get(".opblock-section-request-body .content-type") + .select(secondaryMediaType) + // Assert on the static docs value + .get(`.opblock-section-request-body .microlight`) + .should("include.text", exampleA.value) + // Switch to Try-It-Out + .get(".try-out__btn") + .click() + // Assert on the Try-It-Out value + .get(`.opblock-section-request-body textarea`) + .should("have.value", exampleA.value) + // Execute the operation + .get(".execute") + .click() + // Assert on the request URL + // TODO: use an interceptor instead of curl + .get(".curl") + .contains(`-d '${exampleA.serializedValue || exampleA.value}'`) + }) + + it("static mode toggling: mediaType -> example -> mediaType -> example", () => { + cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml") + // Expand the operation + .get(operationDomId) + .click() + // Change the media type + .get(".opblock-section-request-body .content-type") + .select(secondaryMediaType) + // Assert on the static docs value + .get(`.opblock-section-request-body .microlight`) + .should("include.text", exampleA.value) + // Assert on the dropdown value + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("include.text", exampleA.summary) + + // Choose exampleB + .get(".opblock-section-request-body .examples-select > select") + .select(exampleB.key) + // Assert on the static docs value + .get(`.opblock-section-request-body .microlight`) + .should("include.text", exampleB.value) + // Assert on the dropdown value + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("include.text", exampleB.summary) + + // Change the media type + .get(".opblock-section-request-body .content-type") + .select(primaryMediaType) + // Assert that the static docs value didn't change + .get(`.opblock-section-request-body .microlight`) + .should("include.text", exampleB.value) + // Assert that the dropdown value didn't change + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("include.text", exampleB.summary) + + // Choose exampleA + .get(".opblock-section-request-body .examples-select > select") + .select(exampleA.key) + // Assert on the static docs value + .get(`.opblock-section-request-body .microlight`) + .should("include.text", exampleA.value) + // Assert on the dropdown value + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("include.text", exampleA.summary) + }) + + it("Try-It-Out toggling: mediaType -> example -> mediaType -> example", () => { + cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml") + // Expand the operation + .get(operationDomId) + .click() + // Switch to Try-It-Out + .get(".try-out__btn") + .click() + // Change the media type + .get(".opblock-section-request-body .content-type") + .select(secondaryMediaType) + // Assert on the static docs value + .get(`.opblock-section-request-body textarea`) + .should("include.text", exampleA.value) + // Assert on the dropdown value + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("include.text", exampleA.summary) + + // Choose exampleB + .get(".opblock-section-request-body .examples-select > select") + .select(exampleB.key) + // Assert on the static docs value + .get(`.opblock-section-request-body textarea`) + .should("include.text", exampleB.value) + // Assert on the dropdown value + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("include.text", exampleB.summary) + + // Change the media type + .get(".opblock-section-request-body .content-type") + .select(primaryMediaType) + // Assert that the static docs value didn't change + .get(`.opblock-section-request-body textarea`) + .should("include.text", exampleB.value) + // Assert that the dropdown value didn't change + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("include.text", exampleB.summary) + + // Choose exampleA + .get(".opblock-section-request-body .examples-select > select") + .select(exampleA.key) + // Assert on the static docs value + .get(`.opblock-section-request-body textarea`) + .should("include.text", exampleA.value) + // Assert on the dropdown value + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("include.text", exampleA.summary) + }) + + it("Try-It-Out toggling and execution with modified values: mediaType -> modified value -> example -> mediaType -> example", () => { + cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml") + // Expand the operation + .get(operationDomId) + .click() + // Switch to Try-It-Out + .get(".try-out__btn") + .click() + // Change the media type + .get(".opblock-section-request-body .content-type") + .select(secondaryMediaType) + // Assert on the static docs value + .get(`.opblock-section-request-body textarea`) + .should("include.text", exampleA.value) + // Assert on the dropdown value + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("include.text", exampleA.summary) + + // Modify the value + .get(`.opblock-section-request-body textarea`) + .clear() + .type(customUserInput) + // Assert on the dropdown value + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("have.text", "[Modified value]") + // Fire the operation + .get(".execute") + .click() + // Assert on the curl body + // TODO: use an interceptor instead of curl + .get(".curl") + .contains( + `-d '${customUserInputExpectedCurlSubstring || customUserInput}'` + ) + + // Choose exampleB + .get(".opblock-section-request-body .examples-select > select") + .select(exampleB.key) + // Assert on the static docs value + .get(`.opblock-section-request-body textarea`) + .should("include.text", exampleB.value) + // Assert on the dropdown value + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("include.text", exampleB.summary) + // Fire the operation + .get(".execute") + .click() + // Assert on the curl body + // TODO: use an interceptor instead of curl + .get(".curl") + .contains(`-d '${exampleB.serializedValue || exampleB.value}'`) + + // Ensure the modified value is still accessible + .get(".opblock-section-request-body .examples-select > select") + .contains("[Modified value]") + + // Change the media type to text/plain + .get(".opblock-section-request-body .content-type") + .select(primaryMediaType) + // Assert that the static docs value didn't change + .get(`.opblock-section-request-body textarea`) + .should("include.text", exampleB.value) + // Assert that the dropdown value didn't change + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("include.text", exampleB.summary) + // Fire the operation + .get(".execute") + .click() + // Assert on the curl body + // TODO: use an interceptor instead of curl + .get(".curl") + .contains(`-d '${exampleB.serializedValue || exampleB.value}'`) + + // Ensure the modified value is still accessible + .get(".opblock-section-request-body .examples-select > select") + .contains("[Modified value]") + + // Choose exampleA + .get(".opblock-section-request-body .examples-select > select") + .select(exampleA.key) + // Assert on the static docs value + .get(`.opblock-section-request-body textarea`) + .should("include.text", exampleA.value) + // Assert on the dropdown value + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("include.text", exampleA.summary) + // Fire the operation + .get(".execute") + .click() + // Assert on the curl body + // TODO: use an interceptor instead of curl + .get(".curl") + .contains(`-d '${exampleA.serializedValue || exampleA.value}'`) + + // Ensure the modified value is still the same value + .get(".opblock-section-request-body .examples-select > select") + .select("__MODIFIED__VALUE__") + // Assert on the static docs value + .get(`.opblock-section-request-body textarea`) + .should("have.text", customUserInput.replace(/{{}/g, "{")) + // Assert on the dropdown value + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("have.text", "[Modified value]") + // Fire the operation + .get(".execute") + .click() + // Assert on the curl body + // TODO: use an interceptor instead of curl + .get(".curl") + .contains( + `-d '${customUserInputExpectedCurlSubstring || customUserInput}'` + ) + }) + + // TODO: Try-It-Out + Try-It-Out media type changes +} + +function ResponsePrimitiveTestCases({ + operationDomId, + exampleA, // { value, key, summary } + exampleB, // { value, key, summary } + exampleC, // { value, key, summary } +}) { + it("should render the first example by default", () => { + cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml") + .get(operationDomId) + .click() + .get(".responses-wrapper") + .within(() => { + cy.get(".examples-select > select") + .find(":selected") + .should("include.text", exampleA.summary) + .get(".microlight") + .should("include.text", exampleA.value) + }) + }) + it("should render the second example", () => { + cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml") + .get(operationDomId) + .click() + .get(".responses-wrapper") + .within(() => { + cy.get(".examples-select > select") + .select(exampleB.key) + .find(":selected") + .should("include.text", exampleB.summary) + .get(".microlight") + .should("include.text", exampleB.value) + }) + }) + + it("should retain an example choice across media types if they share the same example", () => { + cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml") + .get(operationDomId) + .click() + .get(".responses-wrapper") + .within(() => { + cy + // Change examples + .get(".examples-select > select") + .select(exampleB.key) + // Assert against dropdown value + .find(":selected") + .should("include.text", exampleB.summary) + // Assert against example value + .get(".microlight") + .should("include.text", exampleB.value) + + // Change media types + .get(".content-type") + .select("text/plain+other") + // Assert against dropdown value + .get(".examples-select > select") + .find(":selected") + .should("include.text", exampleB.summary) + // Assert against example value + .get(".microlight") + .should("include.text", exampleB.value) + }) + }) + ;(exampleC ? it : it.skip)( + "should reset to the first example if the new media type lacks the current example", + () => { + cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml") + .get(operationDomId) + .click() + .get(".responses-wrapper") + .within(() => { + cy + // Change media types + .get(".content-type") + .select("text/plain+other") + // Change examples + .get(".examples-select > select") + .select(exampleC.key) + // Assert against dropdown value + .find(":selected") + .should("include.text", exampleC.summary || exampleC.key) + // Assert against example value + .get(".microlight") + .should("include.text", exampleC.value) + + // Change media types + .get(".content-type") + .select("text/plain") + // Assert against dropdown value + .get(".examples-select > select") + .find(":selected") + .should("include.text", exampleA.summary) + // Assert against example value + .get(".microlight") + .should("include.text", exampleA.value) + }) + } + ) +} diff --git a/frontend/web/api-doc/test/e2e-cypress/helpers/oauth2-server/index.js b/frontend/web/api-doc/test/e2e-cypress/helpers/oauth2-server/index.js new file mode 100644 index 0000000..3e5534c --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/helpers/oauth2-server/index.js @@ -0,0 +1,50 @@ +// from https://github.com/pedroetb/node-oauth2-server-example + +let Http = require("http") +let path = require("path") +let express = require("express") +let bodyParser = require("body-parser") +let oauthserver = require("oauth2-server") +let cors = require("cors") + +let app = express() + +app.use(cors()) + +app.use(bodyParser.urlencoded({ extended: true })) + +app.use(bodyParser.json()) + +app.oauth = oauthserver({ + model: require("./model.js"), + grants: ["password", "client_credentials", "implicit"], + debug: true +}) + +app.all("/oauth/token", app.oauth.grant()) + +app.get("/swagger.yaml", function (req, res) { + res.sendFile(path.join(__dirname, "swagger.yaml")) +}) + +app.get("*", app.oauth.authorise(), function (req, res) { + res.send("Secret secrets are no fun, secret secrets hurt someone.") +}) + +app.use(app.oauth.errorHandler()) + +function startServer() { + let httpServer = Http.createServer(app) + httpServer.listen("3231") + + return function stopServer() { + httpServer.close() + } +} + +module.exports = startServer + +if (require.main === module) { + // for debugging + startServer() +} \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/helpers/oauth2-server/model.js b/frontend/web/api-doc/test/e2e-cypress/helpers/oauth2-server/model.js new file mode 100644 index 0000000..d794e8d --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/helpers/oauth2-server/model.js @@ -0,0 +1,141 @@ +// from https://github.com/pedroetb/node-oauth2-server-example + +let config = { + clients: [{ + clientId: "application", + clientSecret: "secret" + }], + confidentialClients: [{ + clientId: "confidentialApplication", + clientSecret: "topSecret" + }], + tokens: [], + users: [{ + id: "123", + username: "swagger", + password: "password" + }] +} + +/** + * Dump the memory storage content (for debug). + */ + +let dump = function () { + + console.log("clients", config.clients) + console.log("confidentialClients", config.confidentialClients) + console.log("tokens", config.tokens) + console.log("users", config.users) +} + +/* + * Methods used by all grant types. + */ + +let getAccessToken = function (bearerToken, callback) { + + let tokens = config.tokens.filter(function (token) { + + return token.accessToken === bearerToken + }) + + return callback(false, tokens[0]) +} + +let getClient = function (clientId, clientSecret, callback) { + + let clients = config.clients.filter(function (client) { + + return client.clientId === clientId && client.clientSecret === clientSecret + }) + + let confidentialClients = config.confidentialClients.filter(function (client) { + + return client.clientId === clientId && client.clientSecret === clientSecret + }) + + callback(false, clients[0] || confidentialClients[0]) +} + +let grantTypeAllowed = function (clientId, grantType, callback) { + + let clientsSource, + clients = [] + + if (grantType === "password") { + clientsSource = config.clients + } else if (grantType === "client_credentials") { + clientsSource = config.confidentialClients + } + + if (clientsSource) { + clients = clientsSource.filter(function (client) { + + return client.clientId === clientId + }) + } + + callback(false, clients.length) +} + +let saveAccessToken = function (accessToken, clientId, expires, user, callback) { + + config.tokens.push({ + accessToken: accessToken, + expires: expires, + clientId: clientId, + user: user + }) + + callback(false) +} + +/* + * Method used only by password grant type. + */ + +let getUser = function (username, password, callback) { + + let users = config.users.filter(function (user) { + + return user.username === username && user.password === password + }) + + callback(false, users[0]) +} + +/* + * Method used only by client_credentials grant type. + */ + +let getUserFromClient = function (clientId, clientSecret, callback) { + + let clients = config.confidentialClients.filter(function (client) { + + return client.clientId === clientId && client.clientSecret === clientSecret + }) + + let user + + if (clients.length) { + user = { + username: clientId + } + } + + callback(false, user) +} + +/** + * Export model definition object. + */ + +module.exports = { + getAccessToken: getAccessToken, + getClient: getClient, + grantTypeAllowed: grantTypeAllowed, + saveAccessToken: saveAccessToken, + getUser: getUser, + getUserFromClient: getUserFromClient +} \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/helpers/oauth2-server/swagger.yaml b/frontend/web/api-doc/test/e2e-cypress/helpers/oauth2-server/swagger.yaml new file mode 100644 index 0000000..314829f --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/helpers/oauth2-server/swagger.yaml @@ -0,0 +1,36 @@ +swagger: "2.0" +host: localhost:3231 +paths: + /password: + get: + summary: OAuth2 Password + security: + - oauthPassword: [] + responses: + 200: + description: OK + schema: + type: string + /application: + get: + summary: OAuth2 Application + security: + - oauthApplication: [] + responses: + 200: + description: OK + schema: + type: string +securityDefinitions: + oauthPassword: + type: oauth2 + flow: password + tokenUrl: /oauth/token + oauthApplication: + type: oauth2 + flow: application + tokenUrl: /oauth/token + oauthImplicit: + type: oauth2 + flow: implicit + authorizationUrl: /oauth/token diff --git a/frontend/web/api-doc/test/e2e-cypress/plugins/index.js b/frontend/web/api-doc/test/e2e-cypress/plugins/index.js new file mode 100644 index 0000000..852cfa6 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/plugins/index.js @@ -0,0 +1,19 @@ +const startOAuthServer = require("../helpers/oauth2-server") +// *********************************************************** +// This example plugins/index.js can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +module.exports = (on, config) => { + startOAuthServer() + // `on` is used to hook into various events Cypress emits + // `config` is the resolved Cypress config +} diff --git a/frontend/web/api-doc/test/e2e-cypress/static/configs/urls-primary-name.yaml b/frontend/web/api-doc/test/e2e-cypress/static/configs/urls-primary-name.yaml new file mode 100644 index 0000000..520c8c7 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/configs/urls-primary-name.yaml @@ -0,0 +1,6 @@ +urls: +- name: One + url: /documents/features/urls/1.yaml +- name: Two + url: /documents/features/urls/2.yaml +urls.primaryName: Two \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/static/configs/urls-server-variables.yaml b/frontend/web/api-doc/test/e2e-cypress/static/configs/urls-server-variables.yaml new file mode 100644 index 0000000..a8ec508 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/configs/urls-server-variables.yaml @@ -0,0 +1,5 @@ +urls: +- name: One + url: /documents/features/urls/server-variables-1.yaml +- name: Two + url: /documents/features/urls/server-variables-2.yaml diff --git a/frontend/web/api-doc/test/e2e-cypress/static/configs/urls.yaml b/frontend/web/api-doc/test/e2e-cypress/static/configs/urls.yaml new file mode 100644 index 0000000..7a7b601 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/configs/urls.yaml @@ -0,0 +1,5 @@ +urls: +- name: One + url: /documents/features/urls/1.yaml +- name: Two + url: /documents/features/urls/2.yaml \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/4442.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/4442.yaml new file mode 100644 index 0000000..3f232a1 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/4442.yaml @@ -0,0 +1,41 @@ +openapi: "3.0.0" + + +paths: + /: + get: + parameters: + - name: users + in: query + description: List of users to query for + content: + application/json: + example: + - userId: 1 + currency: USD + - userId: 2 + currency: CAD + schema: + $ref: "#/components/schemas/UserArray" + responses: + 200: + description: OK! +components: + schemas: + + UserArray: + type: array + items: + $ref: "#/components/schemas/User" + + User: + type: object + required: + - userId + - currency + properties: + userId: + type: integer + format: int32 + currency: + type: string diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/4641.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/4641.yaml new file mode 100644 index 0000000..e586f1c --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/4641.yaml @@ -0,0 +1,47 @@ +openapi: 3.0.0 +info: + title: Demo API + description: First test + termsOfService: 'http://demo.io/terms-of-service/' + contact: + name: Demo Support + email: support@demo.io + version: 1.0.0 + +paths: + /4641_1: + get: + summary: Returns a 200 + security: + - api_key_1: [] + responses: + '200': + description: A 200 + content: + application/text: + schema: + type: string + /4641_2: + get: + summary: Returns a 200 + security: + - api_key_1: [] + - api_key_2: [] + responses: + '200': + description: A 200 + content: + application/text: + schema: + type: string + +components: + securitySchemes: + api_key_1: + type: apiKey + name: api_key_1 + in: header + api_key_2: + type: apiKey + name: api_key_2 + in: header diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/4838.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/4838.yaml new file mode 100644 index 0000000..c1dd848 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/4838.yaml @@ -0,0 +1,12 @@ +openapi: "3.0.0" + +paths: + /some/route: + post: + description: This should be visible + tags: + - Some + requestBody: {} + responses: + '200': + description: Description \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/4867.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/4867.yaml new file mode 100644 index 0000000..c8c01f4 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/4867.yaml @@ -0,0 +1,24 @@ +openapi: 3.0.1 +info: + title: test + version: "0.1" +paths: + /test: + post: + operationId: myOp + responses: + default: + description: ok + callbacks: + subscription: + http://$request.query.url: + post: + description: myCallback + parameters: + - name: myParam + in: query + schema: + type: string + responses: + default: + description: ok \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/4943.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/4943.yaml new file mode 100644 index 0000000..f1a8a84 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/4943.yaml @@ -0,0 +1,43 @@ +openapi: 3.0.0 +info: + description: Test API + version: v1 + title: Test API +tags: + - name: Test + description: Test API +servers: + - url: /v1 +paths: + /test: + post: + tags: + - Test + summary: Test endpoint + description: Test + operationId: postTest + responses: + '200': + description: Returns response + content: + application/xml: + schema: + $ref: '#/components/schemas/test' +components: + schemas: + test: + type: object + properties: + a: + type: string + b: + type: integer + c: + oneOf: + - type: object + - type: array + items: + type: string + - type: boolean + - type: integer + - type: number diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5043/status.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5043/status.yaml new file mode 100644 index 0000000..52d8c37 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5043/status.yaml @@ -0,0 +1,17 @@ +--- +paths: + findByStatus: + get: + tags: + - "pet" + summary: "Finds Pets by status" + description: "Multiple status values can be provided with comma separated strings" + operationId: "findPetsByStatus" + parameters: + - name: "status" + in: "body" + schema: + type: string + responses: + 200: + description: ok \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5043/swagger.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5043/swagger.yaml new file mode 100644 index 0000000..7adabae --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5043/swagger.yaml @@ -0,0 +1,44 @@ +swagger: "2.0" +info: + description: "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters." + version: "1.0.0" + title: "Swagger Petstore" + termsOfService: "http://swagger.io/terms/" + contact: + email: "apiteam@swagger.io" + license: + name: "Apache 2.0" + url: "http://www.apache.org/licenses/LICENSE-2.0.html" +host: "petstore.swagger.io" +basePath: "/v2" +produces: + - application/json + - application/xml + - text/csv +consumes: + - application/json + - application/xml + - text/csv +schemes: +- "https" +- "http" +paths: + /pet: + post: + tags: + - "pet" + summary: "Add a new pet to the store" + description: "" + operationId: "addPet" + parameters: + - in: "body" + name: "body" + description: "Pet object that needs to be added to the store" + required: true + schema: + $ref: "#/definitions/Pet" + responses: + 405: + description: "Invalid input" + /pet/findByStatus: + $ref: "status.yaml#/paths/findByStatus" \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5060.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5060.yaml new file mode 100644 index 0000000..5c3c32a --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5060.yaml @@ -0,0 +1,10 @@ +--- +swagger: '2.0' +info: + title: Foobar Service + description: '

Example of a simple GET request via curl with bearer HTTP Authentication:

curl
+    -X GET "https://foobar.com/stuff" -H "Accept: application/json" -H "Authorization:
+    Bearer abc123.xyz.789"
' + version: '2.0' +paths: {} + diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5072/additional.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5072/additional.yaml new file mode 100644 index 0000000..7ee9426 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5072/additional.yaml @@ -0,0 +1,35 @@ +openapi: "3.0.0" +info: + description: "A sample API for " + version: "1.0.0" + title: "Sample" + contact: + name: "" + url: "http://website.com" + email: "admin@mail.com" +paths: + /: + post: + summary: Create/modify object + operationId: postObject + parameters: + - name: filterParams + in: query + description: Additional filter fields + required: false + schema: + type: object + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + additionalProperties: + type: string + responses: + '200': + description: Status message + content: + application/json: + schema: + type: object \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5072/empty.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5072/empty.yaml new file mode 100644 index 0000000..f7792c2 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5072/empty.yaml @@ -0,0 +1,33 @@ +openapi: "3.0.0" +info: + description: "A sample API for " + version: "1.0.0" + title: "Sample" + contact: + name: "" + url: "http://website.com" + email: "admin@mail.com" +paths: + /: + post: + summary: Create/modify object + operationId: postObject + parameters: + - name: filterParams + in: query + description: Additional filter fields + required: false + schema: + type: object + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + responses: + '200': + description: Status message + content: + application/json: + schema: + type: object \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5129.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5129.yaml new file mode 100644 index 0000000..0508ec8 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5129.yaml @@ -0,0 +1,26 @@ +openapi: "3.0.0" + +paths: + /aev: + get: + parameters: + - name: param + in: query + allowEmptyValue: true + schema: + type: string + responses: + 200: + description: ok + /aev/and/required: + get: + parameters: + - name: param + in: query + allowEmptyValue: true + required: true + schema: + type: string + responses: + 200: + description: ok \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5164.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5164.yaml new file mode 100644 index 0000000..69bb76f --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5164.yaml @@ -0,0 +1,23 @@ +openapi: "3.0.0" + +paths: + /: + post: + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + first: + type: object + example: + one: abc + two: 123 + second: + type: array + items: + type: string + example: + - "hi" \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5181.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5181.yaml new file mode 100644 index 0000000..5a0051a --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5181.yaml @@ -0,0 +1,27 @@ +info: + title: Required parameter missing, doesn't block request from executing. + version: '1' +openapi: 3.0.0 +servers: +- url: http://httpbin.org/anything +paths: + /foos: + post: + requestBody: + content: + application/x-www-form-urlencoded: + # application/json: + # application/xml: + schema: + properties: + foo: + type: string + bar: + type: string + required: + - foo + type: object + required: true # Note this doesn't have an impact + responses: + default: + description: ok \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5188.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5188.yaml new file mode 100644 index 0000000..cb4d620 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5188.yaml @@ -0,0 +1,24 @@ +openapi: 3.0.0 +servers: [] +info: + description: sdf + version: "1.0.0" + title: Swagger Petstore +paths: + /pet: + get: + tags: + - default + summary: + whatever: 123 + operationId: objectSummary + responses: + '405': + description: Invalid input + post: + tags: + - default + operationId: noSummary + responses: + '405': + description: Invalid input \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5452/openapi.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5452/openapi.yaml new file mode 100644 index 0000000..6ea7ff5 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5452/openapi.yaml @@ -0,0 +1,20 @@ +openapi: "3.0.0" +info: + title: Testcase API + version: 2.0.0 +paths: + '/endpoint': + get: + parameters: + - name: type + in: query + example: fruit + schema: + type: string + enum: + - fruit + - vegetable + - drink + responses: + '200': + description: 200 response diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5452/swagger.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5452/swagger.yaml new file mode 100644 index 0000000..dd428dd --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5452/swagger.yaml @@ -0,0 +1,19 @@ +swagger: "2.0" +info: + title: Testcase API + version: 2.0.0 +paths: + '/endpoint': + get: + parameters: + - name: type + in: query + x-example: fruit + type: string + enum: + - fruit + - vegetable + - drink + responses: + '200': + description: 200 response diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5453.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5453.yaml new file mode 100644 index 0000000..fc60404 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5453.yaml @@ -0,0 +1,10 @@ +openapi: 3.0.2 +info: + title: Response without a schema + version: 1.0.0 +paths: + /foo: + get: + responses: + '200': + description: OK diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5455.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5455.yaml new file mode 100644 index 0000000..70d9ae1 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5455.yaml @@ -0,0 +1,25 @@ +openapi: 3.0.2 +info: + title: test + version: 1.0.0 +paths: + /foo: + post: + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Foo' + responses: + 201: + description: Created + +components: + schemas: + Foo: + type: object + properties: + foo: + type: string + example: bar diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5458.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5458.yaml new file mode 100644 index 0000000..2ab2b4a --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5458.yaml @@ -0,0 +1,37 @@ +swagger: "2.0" +info: + title: test + version: 1.0.0 +paths: + /foo1: + get: + summary: Response without a schema + produces: + - application/json + responses: + 200: + description: Successful response + examples: + application/json: + foo: custom value + /foo2: + get: + summary: Response with schema + produces: + - application/json + responses: + 200: + description: Successful response + schema: + $ref: '#/definitions/Foo' + examples: + application/json: + foo: custom value + +definitions: + Foo: + type: object + properties: + foo: + type: string + example: bar diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5660-model.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5660-model.yaml new file mode 100644 index 0000000..35b04ef --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5660-model.yaml @@ -0,0 +1,14 @@ +openapi: 3.0.2 +info: + title: test + description: Nullable model itself + version: '1.0' +paths: {} +components: + schemas: + SomeObject: + type: object + properties: + name: + type: string + nullable: true diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5660-property.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5660-property.yaml new file mode 100644 index 0000000..19c81f7 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/5660-property.yaml @@ -0,0 +1,17 @@ +openapi: 3.0.2 +info: + title: test + description: Nullable object in model property + version: '1.0' +paths: {} +components: + schemas: + SomeObject: + type: object + properties: + meta: + type: object + properties: + tag: + type: string + nullable: true diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6016-oas2.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6016-oas2.yaml new file mode 100644 index 0000000..7953858 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6016-oas2.yaml @@ -0,0 +1,28 @@ +swagger: "2.0" +info: + description: "OAS2 sample with entries as property name" + version: "0.0.1" + title: "Swagger Sample" +paths: + /pet: + post: + summary: "Add a new pet to the store" + description: "" + parameters: + - in: "body" + name: "body" + schema: + $ref: "#/definitions/Pet" + responses: + "405": + description: "Invalid input" +definitions: + Pet: + type: "object" + properties: + id: + type: "integer" + entries: # <-- evaluate + type: "array" + items: + type: "string" diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6016-oas3.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6016-oas3.yaml new file mode 100644 index 0000000..475138c --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6016-oas3.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.2 +info: + title: OAS 3.0 sample with entries as property name + version: 0.1.0 +paths: + /test/: + get: + summary: Test + operationId: test_test__get + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Testmodel' +components: + schemas: + Testmodel: + title: Testmodel + required: + - name + - entries + type: object + properties: + name: + title: Name + type: string + entries: + title: Entries + type: integer diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6158.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6158.yaml new file mode 100644 index 0000000..2acc750 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6158.yaml @@ -0,0 +1,57 @@ +openapi: 3.0.0 +info: + title: "Swagger Test" + version: "1.0.0" +servers: + - url: https://api.example.com/v1 +paths: + /users: + get: + tags: + - User + summary: Get Users + responses: + 200: + description: User List + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + post: + tags: + - User + summary: Create a user + requestBody: + content: + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + 201: + description: Created successfully + put: + tags: + - User + summary: Update user + requestBody: + content: + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + 201: + description: Created successfully +components: + schemas: + User: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + required: + - name diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6183.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6183.yaml new file mode 100644 index 0000000..2fefc78 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6183.yaml @@ -0,0 +1,37 @@ +swagger: "2.0" +info: + title: Response headers test + version: 1.0.0 +host: httpbin.org +schemes: [https] +paths: + /response-headers: + get: + summary: Run the request using the default parameter values + parameters: + - in: query + name: X-Header1 + type: string + x-example: 'value1,value2' + required: true + - in: query + name: X-Header2 + type: string + x-example: 'value3, value4' + required: true + - in: query + name: X-Header3 + type: array + items: + type: string + x-example: [value5, value6] + collectionFormat: multi + required: true + - in: query + name: Access-Control-Expose-Headers + type: string + x-example: 'X-Header1, X-Header2, X-Header3, Access-Control-Expose-Headers' + required: true + responses: + 200: + description: ok diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6351.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6351.yaml new file mode 100644 index 0000000..853953e --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6351.yaml @@ -0,0 +1,13 @@ +openapi: 3.0.2 +info: + title: OAS 3.0 sample with multiple servers + version: 0.1.0 +servers: + - url: http://testserver1.com + - url: http://testserver2.com +paths: + /test/: + get: + responses: + '200': + description: Successful Response diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6369-oas2-display.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6369-oas2-display.yaml new file mode 100644 index 0000000..d45fe49 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6369-oas2-display.yaml @@ -0,0 +1,26 @@ +swagger: "2.0" +info: + description: "OAS 2.0 sample with Object Model deprecated: true" + version: "0.0.1" + title: "Swagger Sample" +paths: + /test/: + get: + responses: + '200': + description: Successful Response +definitions: + IdentificationProfile: + type: object + deprecated: true + properties: + code: + type: integer + format: int32 + message: + type: string + IDP2: + type: array + deprecated: true + items: + type: string diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6369-oas2-no-display.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6369-oas2-no-display.yaml new file mode 100644 index 0000000..8e87542 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6369-oas2-no-display.yaml @@ -0,0 +1,26 @@ +swagger: "2.0" +info: + description: "OAS 2.0 sample with Object Model deprecated: true" + version: "0.0.1" + title: "Swagger Sample" +paths: + /test/: + get: + responses: + '200': + description: Successful Response +definitions: + IdentificationProfile: + type: object + deprecated: false + properties: + code: + type: integer + format: int32 + message: + type: string + IDP2: + type: array + deprecated: true + items: + type: string diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6369-oas3-display.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6369-oas3-display.yaml new file mode 100644 index 0000000..88c83b1 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6369-oas3-display.yaml @@ -0,0 +1,28 @@ +openapi: 3.0.2 +info: + title: "OAS 3.0 sample with Object Model deprecated: true" + version: 0.1.0 +servers: + - url: http://testserver1.com +paths: + /test/: + get: + responses: + '200': + description: Successful Response +components: + schemas: + IdentificationProfile: + type: object + deprecated: true + properties: + code: + type: integer + format: int32 + message: + type: string + IDP2: + type: array + deprecated: true + items: + type: string diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6369-oas3-no-display.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6369-oas3-no-display.yaml new file mode 100644 index 0000000..3508f5a --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6369-oas3-no-display.yaml @@ -0,0 +1,28 @@ +openapi: 3.0.2 +info: + title: "OAS 3.0 sample with Object Model deprecated: true" + version: 0.1.0 +servers: + - url: http://testserver1.com +paths: + /test/: + get: + responses: + '200': + description: Successful Response +components: + schemas: + IdentificationProfile: + type: object + deprecated: false + properties: + code: + type: integer + format: int32 + message: + type: string + IDP2: + type: array + deprecated: true + items: + type: string diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6442.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6442.yaml new file mode 100644 index 0000000..b8c0d9a --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6442.yaml @@ -0,0 +1,52 @@ +openapi: 3.0.1 +info: + title: Example Swagger + version: '1.0' +servers: + - url: /api/v1 +paths: + /xmlTest: + get: + summary: subscribes to a siri vm stream + operationId: xmlTest + parameters: [] + responses: + '200': + description: Simple example + content: + application/xml: + schema: + type: object + xml: + name: root + properties: + x: + type: string + example: + x: what the f + examples: + x2: + summary: "xml not rendered via 'examples' keyword" + value: + x: should be xml + /xmlTest2: + get: + summary: subscribes to a siri vm stream + operationId: xmlTest2 + parameters: [] + responses: + '200': + description: Simple example + content: + application/xml: + schema: + type: object + xml: + name: root + properties: + x: + type: string + example: + x: what the f + example: + x: should be xml \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6475.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6475.yaml new file mode 100644 index 0000000..1d49c9a --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6475.yaml @@ -0,0 +1,57 @@ +openapi: 3.0.1 +info: + title: Example Swagger + version: '1.0' +servers: + - url: /api/v1 +paths: + /xmlTest/examples: + post: + summary: sample issues + operationId: xmlTest_examples + parameters: [] + requestBody: + description: Simple Test xml examples + content: + application/xml: + schema: + $ref: "#/components/schemas/Test" + examples: + test: + value: + x: should be xml + responses: + '200': + description: Simple Test xml examples + content: {} + /xmlTest/example: + post: + summary: sample issues + operationId: xmlTest_example + parameters: [] + requestBody: + description: Simple Test xml example + content: + application/xml: + schema: + $ref: "#/components/schemas/Test" + example: + x: should be xml + responses: + '200': + description: Simple Test xml example + content: {} +components: + schemas: + Test: + type: object + xml: + name: root + properties: + x: + type: string + other: + type: string + format: email + example: + x: what the f diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6540.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6540.yaml new file mode 100644 index 0000000..bea6184 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6540.yaml @@ -0,0 +1,85 @@ +openapi: 3.0.0 +info: + description: Test API + version: v1 + title: Test API +tags: + - name: Test + description: Test API +servers: + - url: /v1 +paths: + /test: + post: + tags: + - Test + summary: Test endpoint + description: Test + operationId: postTest + responses: + '200': + description: Returns response + content: + application/xml: + schema: + $ref: '#/components/schemas/test' +components: + schemas: + test: + type: object + properties: + a: + type: string + b: + type: integer + c: + type: array + items: + $ref: '#/components/schemas/Things' + d: + type: array + items: + anyOf: + - $ref: '#/components/schemas/TextObject' + - $ref: '#/components/schemas/ImageObject' + + + Things: + type: object + oneOf: + - $ref: '#/components/schemas/TextObject' + - $ref: '#/components/schemas/ImageObject' + + TextObject: + required: + - data + type: object + properties: + objectType: + type: string + example: Text + xml: + name: ObjectType + data: + type: string + example: This is a text + xml: + name: Data + description: Contains a text + + ImageObject: + required: + - data + type: object + properties: + objectType: + type: string + example: image + xml: + name: ObjectType + data: + type: string + example: This is a image + xml: + name: Data + description: Contains a image diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6627.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6627.yaml new file mode 100644 index 0000000..67fc073 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/6627.yaml @@ -0,0 +1,41 @@ +openapi: 3.0.1 +info: + title: XML array schema with array-level example + version: 1.0.0 + +paths: + /users: + get: + responses: + "200": + description: '' + content: + application/xml: + schema: + $ref: '#/components/schemas/Users' + +components: + schemas: + Users: + type: array + example: + - id: 123 + name: bob + - id: 456 + name: jane + xml: + name: Users + wrapped: true + items: + type: object + xml: + name: User + properties: + id: + type: integer + xml: + attribute: true + name: + type: string + xml: + attribute: true diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/7996-tags-externalDocs.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/7996-tags-externalDocs.yaml new file mode 100644 index 0000000..8c3eef4 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/7996-tags-externalDocs.yaml @@ -0,0 +1,28 @@ +openapi: 3.0.2 +info: + title: "This is an example highlighting a display issue related to `tags` and `description` entries therein. It contains tag descriptions with & without externalDocs." + version: 1.0.0 +tags: +- name: "foo" + description: "Foo: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex\ + ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + externalDocs: + description: "Find out more" + url: "http://swagger.io" +- name: "bar" + description: "Bar: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex \ +ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." +- name: "baz" + description: "Baz: has less description text" + externalDocs: + description: "Find out more about our store" + url: "http://swagger.io" +paths: + /foobar: + get: + tags: + - "foo" + - "bar" + responses: + '200': + description: OK \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/8217.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/8217.yaml new file mode 100644 index 0000000..fa3d8cf --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/8217.yaml @@ -0,0 +1,28 @@ +openapi: 3.0.1 +info: + title: Example Swagger with required default body parameter + version: 1.0.0 +paths: + /pet: + post: + operationId: addPet + requestBody: + content: + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/BodyParameter' + required: true + responses: + 405: + description: Invalid input + content: {} +components: + schemas: + BodyParameter: + required: + - bodyParameter + type: object + properties: + bodyParameter: + type: string + default: default \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/editor-1868.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/editor-1868.yaml new file mode 100644 index 0000000..ef0d8f7 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/bugs/editor-1868.yaml @@ -0,0 +1,13 @@ +swagger: "2.0" + +paths: + /: + get: + description: wow + +definitions: + MyModel: + type: object + properties: + a: + type: string \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/auth-bearer-flow.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/auth-bearer-flow.yaml new file mode 100644 index 0000000..c4fe7c7 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/auth-bearer-flow.yaml @@ -0,0 +1,20 @@ +openapi: 3.0.0 +info: + title: Bearer auth test + version: 1.0.0 +servers: + # - url: https://httpbin.org # live external url + - url: http://localhost:3231 # will need to mock +paths: + /get: + get: + responses: + '200': + description: ok +security: + - bearerAuth: [] +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/auth-code-flow-pkce-without-secret.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/auth-code-flow-pkce-without-secret.yaml new file mode 100644 index 0000000..dcc0b44 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/auth-code-flow-pkce-without-secret.yaml @@ -0,0 +1,25 @@ +openapi: 3.0.0 + +info: + version: "1.0" + title: PKCE Flow + +paths: + /: + get: + summary: dummy operation + responses: + "200": + description: OK + +components: + securitySchemes: + testAuthCodeFlow: + type: oauth2 + flows: + authorizationCode: + authorizationUrl: /oauth/authorize + tokenUrl: /oauth/token + scopes: + read: read whatever you want + write: write whatever you want diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/deep-linking.openapi.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/deep-linking.openapi.yaml new file mode 100644 index 0000000..0f7ef86 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/deep-linking.openapi.yaml @@ -0,0 +1,58 @@ +openapi: "3.0.0" + +paths: + /: + get: + operationId: "myOperation" + tags: ["myTag"] + summary: an operation + responses: + '200': + description: a pet to be returned + content: + application/json: + schema: + type: object + /withSpaces: + post: + operationId: "my Operation" + tags: ["my Tag"] + summary: an operation + responses: + '200': + description: a pet to be returned + content: + application/json: + schema: + type: object + /utf16fragments: + head: + operationId: "пошел" + tags: ["шеллы"] + summary: an operation + responses: + "200": + description: ok + /withUnderscores: + patch: + operationId: "underscore_Operation" + tags: ["underscore_Tag"] + summary: an operation + responses: + '200': + description: a pet to be returned + content: + application/json: + schema: + type: object + /noOperationId: + put: + tags: ["tagTwo"] + summary: some operations are anonymous... + responses: + '200': + description: a pet to be returned + content: + application/json: + schema: + type: object \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/deep-linking.swagger.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/deep-linking.swagger.yaml new file mode 100644 index 0000000..7395f4e --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/deep-linking.swagger.yaml @@ -0,0 +1,34 @@ +swagger: "2.0" + +paths: + /: + get: + operationId: "myOperation" + tags: ["myTag"] + summary: an operation + responses: + "200": + description: ok + /withSpaces: + post: + operationId: "my Operation" + tags: ["my Tag"] + summary: an operation + responses: + "200": + description: ok + /utf16fragments: + head: + operationId: "пошел" + tags: ["шеллы"] + /withUnderscores: + patch: + operationId: "underscore_Operation" + tags: ["underscore_Tag"] + summary: an operation + responses: + "200": + description: ok + /noOperationId: + put: + tags: ["tagTwo"] diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/external-docs.openapi.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/external-docs.openapi.yaml new file mode 100644 index 0000000..0195f8a --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/external-docs.openapi.yaml @@ -0,0 +1,148 @@ +openapi: 3.0.2 + +info: + title: External Docs + version: "1" + +externalDocs: + description: Read external docs + url: http://swagger.io + +tags: +- name: pet + description: Everything about your Pets + externalDocs: + description: Pet Documentation + url: http://swagger.io +- name: petWithoutDescription + externalDocs: + url: http://swagger.io + +paths: + /pet: + put: + externalDocs: + description: More details about putting a pet + url: http://swagger.io + tags: + - pet + summary: Update an existing pet + description: Update an existing pet by Id + operationId: updatePet + requestBody: + description: Update an existent pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid ID supplied + "404": + description: Pet not found + "405": + description: Validation exception + post: + externalDocs: + url: http://swagger.io + tags: + - pet + summary: Add a new pet to the store + description: Add a new pet to the store + operationId: addPet + requestBody: + description: Create a new pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + "405": + description: Invalid input + +components: + schemas: + Pet: + required: + - name + - photoUrls + type: object + description: This is a Pet + externalDocs: + description: More Docs About Pet + url: http://swagger.io + deprecated: true + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + photoUrls: + type: array + items: + type: string + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + Object: + type: object + externalDocs: + description: Object Docs + url: http://swagger.io + properties: + name: + type: string + ObjectWithoutDescription: + type: object + externalDocs: + url: http://swagger.io + properties: + name: + type: string + Primitive: + description: Just a string schema + type: string + externalDocs: + description: Primitive Docs + url: http://swagger.io + PrimitiveWithoutDescription: + description: Just a string schema + type: string + externalDocs: + url: http://swagger.io + Array: + description: Just an array schema + type: array + externalDocs: + description: Array Docs + url: http://swagger.io + items: + type: string + ArrayWithoutDescription: + description: Just an array schema + type: array + externalDocs: + url: http://swagger.io + items: + type: string diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/external-docs.swagger.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/external-docs.swagger.yaml new file mode 100644 index 0000000..10d5275 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/external-docs.swagger.yaml @@ -0,0 +1,116 @@ +swagger: "2.0" + +info: + title: External Docs + version: "1" + +externalDocs: + description: Read external docs + url: http://swagger.io + +tags: +- name: pet + description: Everything about your Pets + externalDocs: + description: Pet Documentation + url: http://swagger.io +- name: petWithoutDescription + externalDocs: + url: http://swagger.io + +paths: + /pet: + put: + externalDocs: + description: More details about putting a pet + url: http://swagger.io + tags: + - pet + summary: Update an existing pet + description: Update an existing pet by Id + operationId: updatePet + responses: + 200: + description: OK + post: + externalDocs: + url: http://swagger.io + tags: + - pet + summary: Add a new pet to the store + description: Add a new pet to the store + operationId: addPet + responses: + 201: + description: Created + +definitions: + Pet: + required: + - name + - photoUrls + type: object + description: This is a Pet + externalDocs: + description: More Docs About Pet + url: http://swagger.io + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + photoUrls: + type: array + items: + type: string + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + Object: + type: object + externalDocs: + description: Object Docs + url: http://swagger.io + properties: + name: + type: string + ObjectWithoutDescription: + type: object + externalDocs: + url: http://swagger.io + properties: + name: + type: string + Primitive: + description: Just a string schema + type: string + externalDocs: + description: Primitive Docs + url: http://swagger.io + PrimitiveWithoutDescription: + description: Just a string schema + type: string + externalDocs: + url: http://swagger.io + Array: + description: Just an array schema + type: array + externalDocs: + description: Array Docs + url: http://swagger.io + items: + type: string + ArrayWithoutDescription: + description: Just an array schema + type: array + externalDocs: + url: http://swagger.io + items: + type: string diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/models.openapi.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/models.openapi.yaml new file mode 100644 index 0000000..11d2a4a --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/models.openapi.yaml @@ -0,0 +1,93 @@ +openapi: "3.0.0" + +components: + schemas: + Order: + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + quantity: + type: integer + format: int32 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + type: boolean + default: false + xml: + name: Order + User: + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + format: int32 + description: User Status + xml: + name: User + Pet: + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + category: + $ref: '#/components/schemas/Category' + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: Pet + \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/models.swagger.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/models.swagger.yaml new file mode 100644 index 0000000..eb4cf68 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/models.swagger.yaml @@ -0,0 +1,92 @@ +swagger: "2.0" + +definitions: + Order: + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + quantity: + type: integer + format: int32 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + type: boolean + default: false + xml: + name: Order + User: + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + format: int32 + description: User Status + xml: + name: User + Pet: + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + category: + $ref: '#/definitions/Category' + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + $ref: '#/definitions/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: Pet + \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/multiple-examples-core.openapi.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/multiple-examples-core.openapi.yaml new file mode 100644 index 0000000..f6a39ce --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/multiple-examples-core.openapi.yaml @@ -0,0 +1,400 @@ +openapi: 3.0.0 +info: + title: "Multiple Examples: Core Document" + description: | + This document has examples for straightforward usage of `examples` in... + * Parameter Object positions + * Response Object positions + * Request Body Object positions + + It includes: + * cases for each JSON Schema type as an example value (except null) in each position + * variously-sized `examples` objects + * multi-paragraph descriptions within each example + + It **does not** include the following out-of-scope items: + * usage of `examples` within `Parameter.content` or `Response.content` + * `externalValue` (might change) + + It also lacks edge cases, which will be covered in the "Corner" Document: + * `examples` n=1, which presents an interesting UI problem w/ the dropdown + * `example` and `examples` both present + * example item value that doesn't match the input type + * e.g., `Parameter.type === "number"`, but `Parameter.examples.[key].value` is an object + * `null` as an example value + version: "1.0.2" +paths: + /String: + post: + summary: "Bonus: contains two requestBody media types" + parameters: + - in: query + name: message + required: true + description: This parameter just so happens to have a one-line description. + schema: + type: string + examples: + StringExampleA: + $ref: '#/components/examples/StringExampleA' + StringExampleB: + $ref: '#/components/examples/StringExampleB' + requestBody: + description: the wonderful payload of my request + content: + text/plain: + schema: + type: string + examples: + StringExampleA: + $ref: '#/components/examples/StringExampleA' + StringExampleB: + $ref: '#/components/examples/StringExampleB' + text/plain+other: + schema: + type: string + examples: + StringExampleA: + $ref: '#/components/examples/StringExampleA' + StringExampleB: + $ref: '#/components/examples/StringExampleB' + responses: + 200: + description: has two media types; the second has a third example! + content: + text/plain: + schema: + type: string + examples: + StringExampleA: + $ref: '#/components/examples/StringExampleA' + StringExampleB: + $ref: '#/components/examples/StringExampleB' + text/plain+other: + schema: + type: string + examples: + StringExampleA: + $ref: '#/components/examples/StringExampleA' + StringExampleB: + $ref: '#/components/examples/StringExampleB' + StringExampleC: + $ref: '#/components/examples/StringExampleC' + /Number: + post: + parameters: + - in: query + name: message + required: true + schema: + type: number + examples: + NumberExampleA: + $ref: '#/components/examples/NumberExampleA' + NumberExampleB: + $ref: '#/components/examples/NumberExampleB' + NumberExampleC: + $ref: '#/components/examples/NumberExampleC' + requestBody: + description: the wonderful payload of my request + content: + text/plain: + schema: + type: number + examples: + NumberExampleA: + $ref: '#/components/examples/NumberExampleA' + NumberExampleB: + $ref: '#/components/examples/NumberExampleB' + NumberExampleC: + $ref: '#/components/examples/NumberExampleC' + text/plain+other: + schema: + type: number + examples: + NumberExampleA: + $ref: '#/components/examples/NumberExampleA' + NumberExampleB: + $ref: '#/components/examples/NumberExampleB' + NumberExampleC: + $ref: '#/components/examples/NumberExampleC' + responses: + 200: + description: OK! + content: + text/plain: + schema: + type: number + examples: + NumberExampleA: + $ref: '#/components/examples/NumberExampleA' + NumberExampleB: + $ref: '#/components/examples/NumberExampleB' + text/plain+other: + schema: + type: number + examples: + NumberExampleA: + $ref: '#/components/examples/NumberExampleA' + NumberExampleB: + $ref: '#/components/examples/NumberExampleB' + NumberExampleC: + $ref: '#/components/examples/NumberExampleC' + /Boolean: + post: + parameters: + - in: query + name: message + required: true + schema: + type: boolean + examples: + BooleanExampleA: + $ref: '#/components/examples/BooleanExampleA' + BooleanExampleB: + $ref: '#/components/examples/BooleanExampleB' + requestBody: + description: the wonderful payload of my request + content: + text/plain: + schema: + type: boolean + examples: + BooleanExampleA: + $ref: '#/components/examples/BooleanExampleA' + BooleanExampleB: + $ref: '#/components/examples/BooleanExampleB' + text/plain+other: + schema: + type: boolean + examples: + BooleanExampleA: + $ref: '#/components/examples/BooleanExampleA' + BooleanExampleB: + $ref: '#/components/examples/BooleanExampleB' + responses: + 200: + description: OK! + content: + text/plain: + schema: + type: boolean + examples: + BooleanExampleA: + $ref: '#/components/examples/BooleanExampleA' + BooleanExampleB: + $ref: '#/components/examples/BooleanExampleB' + text/plain+other: + schema: + type: boolean + examples: + BooleanExampleA: + $ref: '#/components/examples/BooleanExampleA' + BooleanExampleB: + $ref: '#/components/examples/BooleanExampleB' + /Array: + post: + parameters: + - in: query + name: message + required: true + schema: + type: array + items: {} # intentionally empty; don't want to assert on the items + examples: + ArrayExampleA: + $ref: '#/components/examples/ArrayExampleA' + ArrayExampleB: + $ref: '#/components/examples/ArrayExampleB' + ArrayExampleC: + $ref: '#/components/examples/ArrayExampleC' + requestBody: + description: the wonderful payload of my request + content: + application/json: + schema: + type: array + items: {} # intentionally empty; don't want to assert on the items + examples: + ArrayExampleA: + $ref: '#/components/examples/ArrayExampleA' + ArrayExampleB: + $ref: '#/components/examples/ArrayExampleB' + ArrayExampleC: + $ref: '#/components/examples/ArrayExampleC' + responses: + 200: + description: OK! + content: + application/json: + schema: + type: array + items: {} # intentionally empty; don't want to assert on the items + examples: + ArrayExampleA: + $ref: '#/components/examples/ArrayExampleA' + ArrayExampleB: + $ref: '#/components/examples/ArrayExampleB' + ArrayExampleC: + $ref: '#/components/examples/ArrayExampleC' + /Object: + post: + parameters: + - in: query + name: data + required: true + schema: + type: object + examples: + ObjectExampleA: + $ref: '#/components/examples/ObjectExampleA' + ObjectExampleB: + $ref: '#/components/examples/ObjectExampleB' + requestBody: + description: the wonderful payload of my request + content: + application/json: + schema: + type: object + examples: + ObjectExampleA: + $ref: '#/components/examples/ObjectExampleA' + ObjectExampleB: + $ref: '#/components/examples/ObjectExampleB' + text/plain+other: + schema: + type: object + examples: + ObjectExampleA: + $ref: '#/components/examples/ObjectExampleA' + ObjectExampleB: + $ref: '#/components/examples/ObjectExampleB' + responses: + 200: + description: OK! + content: + application/json: + schema: + type: object + examples: + ObjectExampleA: + $ref: '#/components/examples/ObjectExampleA' + ObjectExampleB: + $ref: '#/components/examples/ObjectExampleB' + text/plain+other: + schema: + type: object + examples: + ObjectExampleA: + $ref: '#/components/examples/ObjectExampleA' + ObjectExampleB: + $ref: '#/components/examples/ObjectExampleB' +components: + examples: + StringExampleA: + value: "hello world" + summary: Don't just string me along... + description: | + A string in C is actually a character array. As an individual character variable can store only one character, we need an array of characters to store strings. Thus, in C string is stored in an array of characters. Each character in a string occupies one location in an array. The null character ‘\0’ is put after the last character. This is done so that program can tell when the end of the string has been reached. + + For example, the string “Hello” is stored as follows... + + ![](http://www.tutorialspoint.com/computer_programming/images/string_representation.jpg) + + Since the string contains 5 characters. it requires a character array of size 6 to store it. the last character in a string is always a NULL('\0') character. Always keep in mind that the '\0' is not included in the length if the string, so here the length of the string is 5 only. Notice above that the indexes of the string starts from 0 not one so don't confuse yourself with index and length of string. + + Thus, in C, a string is a one-dimensional array of characters terminated a null character. The terminating null character is important. In fact, a string not terminated by ‘\0’ is not really a string, but merely a collection of characters. + StringExampleB: + value: "The quick brown fox jumps over the lazy dog" + summary: "I'm a pangram!" + description: | + A pangram (Greek: παν γράμμα, pan gramma, "every letter") or holoalphabetic sentence is a sentence using every letter of a given alphabet at least once. Pangrams have been used to display typefaces, test equipment, and develop skills in handwriting, calligraphy, and keyboarding. + + The best-known English pangram is "The quick brown fox jumps over the lazy dog". It has been used since at least the late 19th century, was utilized by Western Union to test Telex / TWX data communication equipment for accuracy and reliability, and is now used by a number of computer programs (most notably the font viewer built into Microsoft Windows) to display computer fonts. + + Pangrams exist in practically every alphabet-based language. An example from German is _Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich_, which contains all letters, including every umlaut (ä, ö, ü) plus the ß. It has been used since before 1800. + + In a sense, the pangram is the opposite of the lipogram, in which the aim is to omit one or more letters. + + A perfect pangram contains every letter of the alphabet only once and + can be considered an anagram of the alphabet. The only perfect pangrams + that are known either use abbreviations, such as "Mr Jock, TV quiz PhD, + bags few lynx", or use words so obscure that the phrase is hard to + understand, such as "Cwm fjord bank glyphs vext quiz", where cwm is a + loan word from the Welsh language meaning a steep-sided valley, and vext + is an uncommon way to spell vexed. + StringExampleC: + value: "JavaScript rules" + summary: "A third example, for use in special places..." + NumberExampleA: + value: 7710263025 + summary: "World population" + description: | + In demographics, the world population is the total number of humans currently living, and was estimated to have reached 7.7 billion people as of April 2019. It took over 200,000 years of human history for the world's population to reach 1 billion; and only 200 years more to reach 7 billion. + + World population has experienced continuous growth since the end of the Great Famine of 1315–1317 and the Black Death in 1350, when it was near 370 million. The highest population growth rates – global population increases above 1.8% per year – occurred between 1955 and 1975, peaking to 2.1% between 1965 and 1970. The growth rate has declined to 1.2% between 2010 and 2015 and is projected to decline further in the course of the 21st century. However, the global population is still growing and is projected to reach about 10 billion in 2050 and more than 11 billion in 2100. + + Total annual births were highest in the late 1980s at about 139 million, and as of 2011 were expected to remain essentially constant at a level of 135 million, while deaths numbered 56 million per year and were expected to increase to 80 million per year by 2040. The median age of the world's population was estimated to be 30.4 years in 2018. + NumberExampleB: + value: 9007199254740991 + summary: "Number.MAX_SAFE_INTEGER" + description: | + The `MAX_SAFE_INTEGER` constant has a value of `9007199254740991` (9,007,199,254,740,991 or ~9 quadrillion). The reasoning behind that number is that JavaScript uses double-precision floating-point format numbers as specified in IEEE 754 and can only safely represent numbers between `-(2^53 - 1)` and `2^53 - 1`. + + Safe in this context refers to the ability to represent integers exactly and to correctly compare them. For example, `Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2` will evaluate to `true`, which is mathematically incorrect. See `Number.isSafeInteger()` for more information. + + Because `MAX_SAFE_INTEGER` is a static property of `Number`, you always use it as `Number.MAX_SAFE_INTEGER`, rather than as a property of a `Number` object you created. + NumberExampleC: + # `description` and `summary` intentionally omitted + value: 0 + BooleanExampleA: + value: true + summary: The truth will set you free + description: | + In some programming languages, any expression can be evaluated in a context that expects a Boolean data type. Typically (though this varies by programming language) expressions like the number zero, the empty string, empty lists, and null evaluate to false, and strings with content (like "abc"), other numbers, and objects evaluate to true. Sometimes these classes of expressions are called "truthy" and "falsey". + BooleanExampleB: + # `description` intentionally omitted + value: false + summary: Friends don't lie to friends + ArrayExampleA: + value: [a, b, c] + summary: A lowly array of strings + description: | + In computer science, a list or sequence is an abstract data type that represents a countable number of ordered values, where the same value may occur more than once. An instance of a list is a computer representation of the mathematical concept of a finite sequence; the (potentially) infinite analog of a list is a stream.[1]:§3.5 Lists are a basic example of containers, as they contain other values. If the same value occurs multiple times, each occurrence is considered a distinct item. + ArrayExampleB: + value: [1, 2, 3, 4] + summary: A lowly array of numbers + description: | + Many programming languages provide support for list data types, and have special syntax and semantics for lists and list operations. A list can often be constructed by writing the items in sequence, separated by commas, semicolons, and/or spaces, within a pair of delimiters such as parentheses '()', brackets '[]', braces '{}', or angle brackets '<>'. Some languages may allow list types to be indexed or sliced like array types, in which case the data type is more accurately described as an array. In object-oriented programming languages, lists are usually provided as instances of subclasses of a generic "list" class, and traversed via separate iterators. List data types are often implemented using array data structures or linked lists of some sort, but other data structures may be more appropriate for some applications. In some contexts, such as in Lisp programming, the term list may refer specifically to a linked list rather than an array. + + In type theory and functional programming, abstract lists are usually defined inductively by two operations: nil that yields the empty list, and cons, which adds an item at the beginning of a list. + ArrayExampleC: + # `summary` intentionally omitted + value: [] + description: An empty array value should clear the current value + ObjectExampleA: + value: + firstName: Kyle + lastName: Shockey + email: kyle.shockey@smartbear.com + summary: A user's contact info + description: Who is this guy, anyways? + ObjectExampleB: + value: + name: Abbey + type: kitten + color: calico + gender: female + age: 11 weeks + summary: A wonderful kitten's info + description: | + Today’s domestic cats are physically very similar to their wild + ancestors. “Domestic cats and wildcats share a majority of their + characteristics,” Lyons says, but there are a few key differences: + wildcats were and are typically larger than their domestic kin, with + brown, tabby-like fur. “Wildcats have to have camouflage that’s going to + keep them very inconspicuous in the wild,” Lyons says. “So you can’t + have cats with orange and white running around—they’re going to be + snatched up by their predators.” As cats were domesticated, they began + to be selected and bred for more interesting colorations, thus giving us + today’s range of beautiful cat breeds. diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/oas3-multiple-media-type.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/oas3-multiple-media-type.yaml new file mode 100644 index 0000000..280891a --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/oas3-multiple-media-type.yaml @@ -0,0 +1,50 @@ +openapi: 3.0.0 +info: + title: Switching between multiple content-type test + version: 1.0.0 +servers: + - url: https://httpbin.org +paths: + /post: + post: + requestBody: + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/Bar' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Foo' + application/json: + schema: + $ref: '#/components/schemas/FooBar' + responses: + '200': + description: ok + +components: + schemas: + Foo: + type: object + properties: + foo: + type: string + example: '' + Bar: + type: object + required: [bar] + properties: + bar: + type: integer + example: 1 + FooBar: + type: object + required: + - bar + properties: + foo: + type: string + example: '' + bar: + type: integer + example: 1 diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/oas3-multiple-servers-switch.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/oas3-multiple-servers-switch.yaml new file mode 100644 index 0000000..33e255a --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/oas3-multiple-servers-switch.yaml @@ -0,0 +1,16 @@ +openapi: 3.0.2 +servers: + - url: /test-url-switch-1 + - url: /test-url-switch-2 +info: + title: multi-server test, switch + version: 0.0.1 + description: |- + a simple test to select different servers +paths: + /: + get: + summary: an operation + responses: + "200": + description: OK diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/oas3-multiple-servers.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/oas3-multiple-servers.yaml new file mode 100644 index 0000000..3206a4a --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/oas3-multiple-servers.yaml @@ -0,0 +1,16 @@ +openapi: 3.0.2 +servers: + - url: /test-url-1 + - url: /test-url-2 +info: + title: multi-server test + version: 0.0.1 + description: |- + a simple test to select different servers +paths: + /: + get: + summary: an operation + responses: + "200": + description: OK diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/oas3-xml.json b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/oas3-xml.json new file mode 100644 index 0000000..b0a2f9e --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/oas3-xml.json @@ -0,0 +1,309 @@ +{ + "openapi": "3.0.0", + "info": { + "version": "1.0.0", + "title": "XML definition with oneOf & anyOf", + "description": "Definition to cover multiple XML examples" + }, + "paths": { + "/foo": { + "post": { + "requestBody": { + "content": { + "text/xml": { + "schema": { + "description": "XML schema with oneOf for /foo request and mediaType text/xml", + "oneOf": [ + { + "$ref": "#/components/schemas/OneOfOne" + }, + { + "$ref": "#/components/schemas/OneOfTwo" + }, + { + "$ref": "#/components/schemas/OneOfThree" + }, + { + "$ref": "#/components/schemas/OneOfFour" + } + ] + } + }, + "application/xml": { + "schema": { + "description": "fallback XML schema with mediaType application/xml", + "$ref": "#/components/schemas/OneOfTwo" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/xml": { + "schema": { + "description": "XML schema with mediaType text/xml", + "$ref": "#/components/schemas/foobarResponse" + } + } + } + } + } + } + }, + "/bar": { + "post": { + "requestBody": { + "content": { + "text/xml": { + "schema": { + "description": "XML schema with anyOf for /bar request and mediaType text/xml", + "anyOf": [ + { + "$ref": "#/components/schemas/OneOfOne" + }, + { + "$ref": "#/components/schemas/OneOfTwo" + }, + { + "$ref": "#/components/schemas/OneOfThree" + }, + { + "$ref": "#/components/schemas/OneOfFour" + } + ] + } + }, + "application/xml": { + "schema": { + "description": "fallback XML schema with mediaType application/xml", + "$ref": "#/components/schemas/OneOfTwo" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/xml": { + "schema": { + "description": "XML schema with mediaType text/xml", + "$ref": "#/components/schemas/foobarResponse" + } + } + } + } + } + } + }, + "/foobar": { + "post": { + "requestBody": { + "content": { + "application/xml": { + "schema": { + "description": "XML schema with oneOf for /foobar request and mediaType application/xml", + "oneOf": [ + { + "$ref": "#/components/schemas/OneOfOne" + }, + { + "$ref": "#/components/schemas/OneOfTwo" + }, + { + "$ref": "#/components/schemas/OneOfThree" + }, + { + "$ref": "#/components/schemas/OneOfFour" + } + ] + } + }, + "text/xml": { + "schema": { + "description": "fallback XML schema with mediaType text/xml", + "$ref": "#/components/schemas/OneOfThree" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/xml": { + "schema": { + "description": "XML schema with mediaType application/xml", + "$ref": "#/components/schemas/foobarResponse" + } + } + } + } + } + } + }, + "/barfoo": { + "post": { + "requestBody": { + "content": { + "application/xml": { + "schema": { + "description": "XML schema with anyOf for /barfoo request and mediaType application/xml", + "anyOf": [ + { + "$ref": "#/components/schemas/OneOfOne" + }, + { + "$ref": "#/components/schemas/OneOfTwo" + }, + { + "$ref": "#/components/schemas/OneOfThree" + }, + { + "$ref": "#/components/schemas/OneOfFour" + } + ] + } + }, + "text/xml": { + "schema": { + "description": "fallback XML schema with mediaType text/xml", + "$ref": "#/components/schemas/OneOfThree" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/xml": { + "schema": { + "description": "XML schema with mediaType application/xml", + "$ref": "#/components/schemas/foobarResponse" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "OneOfOne": { + "type": "object", + "properties": { + "Body": { + "type": "object", + "properties": { + "fooOne": {} + }, + "additionalProperties": false, + "xml": { + "prefix": "soapenv" + } + } + }, + "additionalProperties": false, + "xml": { + "prefix": "soapenv", + "namespace": "http://schemas.xmlsoap.org/soap/envelope/", + "name": "Envelope" + } + }, + "OneOfTwo": { + "type": "object", + "properties": { + "Body": { + "type": "object", + "properties": { + "fooTwo": {} + }, + "additionalProperties": false, + "xml": { + "prefix": "soapenv" + } + } + }, + "additionalProperties": false, + "xml": { + "prefix": "soapenv", + "namespace": "http://schemas.xmlsoap.org/soap/envelope/", + "name": "Envelope" + } + }, + "OneOfThree": { + "type": "object", + "properties": { + "Body": { + "type": "object", + "properties": { + "fooThree": {} + }, + "additionalProperties": false, + "xml": { + "prefix": "soapenv" + } + } + }, + "additionalProperties": false, + "xml": { + "prefix": "soapenv", + "namespace": "http://schemas.xmlsoap.org/soap/envelope/", + "name": "Envelope" + } + }, + "OneOfFour": { + "type": "object", + "properties": { + "Body": { + "type": "object", + "properties": { + "fooFour": {} + }, + "additionalProperties": false, + "xml": { + "prefix": "soapenv" + } + } + }, + "additionalProperties": false, + "xml": { + "prefix": "soapenv", + "namespace": "http://schemas.xmlsoap.org/soap/envelope/", + "name": "Envelope" + } + }, + "foobarResponse": { + "type": "object", + "properties": { + "Body": { + "type": "object", + "properties": { + "foobarResObj": { + "type": "object" + } + }, + "additionalProperties": false, + "xml": { + "prefix": "soapenv" + } + } + }, + "additionalProperties": false, + "xml": { + "prefix": "soapenv", + "namespace": "http://schemas.xmlsoap.org/soap/envelope/", + "name": "Envelope" + } + } + } + } +} diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/parameter-array-missing-items.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/parameter-array-missing-items.yaml new file mode 100644 index 0000000..0d008da --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/parameter-array-missing-items.yaml @@ -0,0 +1,24 @@ +openapi: 3.0.0 +info: + title: test + version: "1.0.0" +paths: + /example1: + get: + description: test fetching + parameters: + - in: query + name: basicName + content: + application/json: + schema: + type: object + properties: + color: + oneOf: + - type: array # invalid definition b/c type: array is missing 'items' + - type: string + - type: integer + responses: + '200': + description: successful fetch diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/parameter-order.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/parameter-order.yaml new file mode 100644 index 0000000..2f22742 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/parameter-order.yaml @@ -0,0 +1,63 @@ +openapi: 3.0.1 +info: + title: Example Swagger + version: '1.0' +servers: + - url: /api/v1 +paths: + /test/{id}/related/{relatedId}: + post: + parameters: + - in: path + name: relatedId + required: true + schema: + type: integer + default: 1 + description: The related ID + - in: header + name: TRACE-ID + required: true + schema: + type: integer + default: 1 + description: The trace ID + - in: cookie + name: debug + required: true + schema: + type: number + enum: + - 0 + - 1 + description: debug flag + - in: header + name: USER-ID + required: true + schema: + type: integer + default: 1 + description: The user ID + - in: query + name: asc + required: true + schema: + type: boolean + default: true + description: sort asc + - in: path + name: id + required: true + schema: + type: integer + default: 1 + description: The ID + requestBody: + description: Some + content: + application/json: + schema: + type: string + responses: + 200: + description: Some \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/petstore-only-pet.openapi.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/petstore-only-pet.openapi.yaml new file mode 100644 index 0000000..d83d7b1 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/petstore-only-pet.openapi.yaml @@ -0,0 +1,418 @@ +openapi: 3.0.2 +servers: + - url: /v3 +info: + description: |- + This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about + Swagger at [http://swagger.io](http://swagger.io). In the third iteration of the pet store, we've switched to the design first approach! + You can now help us improve the API whether it's by making changes to the definition itself or to the code. + That way, with time, we can improve the API in general, and expose some of the new features in OAS3. + + Some useful links: + - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore) + - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml) + version: 1.0.5-SNAPSHOT + title: Swagger Petstore - OpenAPI 3.0 + termsOfService: 'http://swagger.io/terms/' + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: 'http://www.apache.org/licenses/LICENSE-2.0.html' +tags: + - name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: 'http://swagger.io' +paths: + /pet: + post: + tags: + - pet + summary: Add a new pet to the store + description: Add a new pet to the store + operationId: addPet + responses: + '200': + description: Successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + description: Create a new pet in the store + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + put: + tags: + - pet + summary: Update an existing pet + description: Update an existing pet by Id + operationId: updatePet + responses: + '200': + description: Successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '405': + description: Validation exception + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + description: Update an existent pet in the store + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: false + explode: true + schema: + type: string + enum: + - available + - pending + - sold + default: available + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid status value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: >- + Multiple tags can be provided with comma separated strings. Use tag1, + tag2, tag3 for testing. + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: false + explode: true + schema: + type: array + items: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid tag value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + '/pet/{petId}': + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - api_key: [] + - petstore_auth: + - 'write:pets' + - 'read:pets' + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: '' + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + - name: name + in: query + description: Name of pet that needs to be updated + required: true + schema: + type: string + - name: status + in: query + description: Status of pet that needs to be updated + schema: + type: string + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + delete: + tags: + - pet + summary: Deletes a pet + description: '' + operationId: deletePet + parameters: + - name: api_key + in: header + description: '' + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid pet value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + '/pet/{petId}/uploadImage': + post: + tags: + - pet + summary: uploads an image + description: '' + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + - name: additionalMetadata + in: query + description: Additional Metadata + required: false + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary +externalDocs: + description: Find out more about Swagger + url: 'http://swagger.io' +components: + schemas: + Category: + x-swagger-router-model: io.swagger.petstore.model.Category + properties: + id: + type: integer + format: int64 + example: 1 + name: + type: string + example: Dogs + xml: + name: category + type: object + Tag: + x-swagger-router-model: io.swagger.petstore.model.Tag + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: tag + type: object + Pet: + x-swagger-router-model: io.swagger.petstore.model.Pet + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + category: + $ref: '#/components/schemas/Category' + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: '#/components/schemas/Tag' + xml: + name: tag + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + type: object + ApiResponse: + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + xml: + name: '##default' + type: object + requestBodies: + Pet: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + description: Pet object that needs to be added to the store + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: 'https://petstore.swagger.io/oauth/authorize' + scopes: + 'write:pets': modify pets in your account + 'read:pets': read your pets + api_key: + type: apiKey + name: api_key + in: header diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/request-body-upload-file.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/request-body-upload-file.yaml new file mode 100644 index 0000000..ec5d277 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/request-body-upload-file.yaml @@ -0,0 +1,105 @@ +openapi: 3.0.0 +info: + title: "Request body file upload" + description: |- + This document has examples for examining the `schema` or content type for request bodies requiring a file upload + * `application/octect-stream` content type (no matter what schema format) + * `audio/*` content type (no matter what schema format) + * `image/*` content type (no matter what schema format) + * `video/*` content type (no matter what schema format) + * schema format is `base64` (no matter what content type) + * schema format is `binary` (no matter what content type) + version: "1.0.0" +paths: + /upload-application-octet-stream: + post: + operationId: uploadApplicationOctetStream + requestBody: + content: + application/octet-stream: + schema: + type: string + responses: + '200': + description: successful operation + content: + text/plain: + schema: + type: string + /upload-image-png: + post: + operationId: uploadImagePng + requestBody: + content: + image/png: + schema: + type: string + responses: + '200': + description: successful operation + content: + text/plain: + schema: + type: string + /upload-audio-wav: + post: + operationId: uploadAudioWav + requestBody: + content: + audio/wav: + schema: + type: string + responses: + '200': + description: successful operation + content: + text/plain: + schema: + type: string + /upload-video-mpeg: + post: + operationId: uploadVideoMpeg + requestBody: + content: + video/mpeg: + schema: + type: string + responses: + '200': + description: successful operation + content: + text/plain: + schema: + type: string + /upload-schema-format-binary: + post: + operationId: uploadSchemaFormatBinary + requestBody: + content: + application/x-custom: + schema: + type: string + format: binary + responses: + '200': + description: successful operation + content: + text/plain: + schema: + type: string + /upload-schema-format-base64: + post: + operationId: uploadSchemaFormatBase64 + requestBody: + content: + application/x-custom: + schema: + type: string + format: base64 + responses: + '200': + description: successful operation + content: + text/plain: + schema: + type: string diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/request-body/multipart/default-views.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/request-body/multipart/default-views.yaml new file mode 100644 index 0000000..5db8f34 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/request-body/multipart/default-views.yaml @@ -0,0 +1,28 @@ +openapi: 3.0.3 +info: + title: multipart/form-data schema object + version: 0.0.1 +paths: + /test: + post: + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + parameters: + "$ref": "#/components/schemas/TestBody" + responses: + 200: + description: ok +components: + schemas: + TestBody: + type: object + properties: + stuff: + type: string diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/request-body/multipart/enum.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/request-body/multipart/enum.yaml new file mode 100644 index 0000000..cc935a1 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/request-body/multipart/enum.yaml @@ -0,0 +1,29 @@ +openapi: 3.0.3 +info: + title: asd + version: 0.0.1 +paths: + /test: + post: + requestBody: + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/TestBody' + responses: + 200: + description: ok +components: + schemas: + TestBody: + required: + - test_enum + type: object + properties: + test_enum: + allOf: + - $ref: "#/components/schemas/TestEnum" + TestEnum: + enum: + - A + - B diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/response-extension.openapi.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/response-extension.openapi.yaml new file mode 100644 index 0000000..31e1da1 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/response-extension.openapi.yaml @@ -0,0 +1,19 @@ +openapi: "3.0.0" + +paths: + /: + get: + operationId: "myOperation" + tags: ["myTag"] + summary: an operation + responses: + '200': + description: a pet to be returned + content: + application/json: + schema: + type: object + '404': + x-error: true + x-error-codes: [NOT_FOUND] + description: Not found diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/response-extension.swagger.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/response-extension.swagger.yaml new file mode 100644 index 0000000..a25c8f1 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/response-extension.swagger.yaml @@ -0,0 +1,15 @@ +swagger: "2.0" + +paths: + /: + get: + operationId: "myOperation" + tags: ["myTag"] + summary: an operation + responses: + "200": + description: ok + '404': + x-error: true + x-error-codes: [NOT_FOUND] + description: Not found diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/schema-form-core.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/schema-form-core.yaml new file mode 100644 index 0000000..4c3cdaa --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/schema-form-core.yaml @@ -0,0 +1,339 @@ +openapi: 3.0.0 +info: + title: "Schema in Parameters" + description: |- + This document has examples for examining the `schema` within a set of parameters + * String Enum (/pet/findByStatus) + * Array of Strings (/pet/findByTags) + * Array of Boolean (/petOwner/listOfServiceTrainer) + * Array of Objects (/petOwners/createWithList) + * Array of Enum (/petOwner/findByPreference) + + This document also covers a debounce test for `schema` type `string + * String (/petOwner) + + This documents does not cover: + * Array of Arrays + + Additional notes + * Provides additional coverage and examples not covered in the Multiple Examples Core Document (Test) + * Code component reference `JsonSchemaForm` + * `pet` and `tag` schemas are reduced from Swagger Petstore + version: "1.0.0" +paths: + /pet/findByStatus: + get: + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: false + explode: true + schema: + type: string + enum: + - available + - pending + - sold + default: available + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid status value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: >- + Multiple tags can be provided with comma separated strings. Use tag1, + tag2, tag3 for testing. + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: false + explode: true + schema: + type: array + items: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid tag value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + '/petOwner/{petOwnerId}': + get: + tags: + - petOwner + summary: Find pet owner by ID + description: Returns a single pet owner if ID found, list if no ID provided + operationId: getPetOwnerById + parameters: + - name: petOwnerId + in: path + description: ID of pet owner to return + required: false + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/PetOwner' + application/json: + schema: + $ref: '#/components/schemas/PetOwner' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + /petOwner/listOfServiceTrainer: + get: + tags: + - petOwner + summary: List of Service Trainers + description: >- + Expect boolean, but allow both true and false + operationId: listOfServiceTrainer + parameters: + - name: tags + in: query + description: Boolean to filter by + required: false + explode: true + schema: + type: array + items: + type: boolean + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/PetOwner' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PetOwner' + '400': + description: Invalid tag value + /petOwner/findByPreference: + get: + tags: + - petOwner + summary: Find by Pet Owner Preferences + description: >- + Expect enum + operationId: findByPreference + parameters: + - name: tags + in: query + description: Enum to filter by + required: false + explode: true + schema: + type: array + items: + type: string + enum: + - dog + - cat + - bird + - fish + - other + default: dog + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/PetOwner' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PetOwner' + '400': + description: Invalid tag value + components: + /petOwner/createWithList: + post: + tags: + - petOwner + summary: Creates list of pet owners with given input array + operationId: petOwnerCreateWithList + requestBody: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PetOwner' + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/PetOwner' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PetOwner' + '400': + description: Invalid values + schemas: + Pet: + x-swagger-router-model: io.swagger.petstore.model.Pet + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + # remove category property + petOwners: + type: array + items: + $ref: '#/components/schemas/PetOwner' + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: '#/components/schemas/Tag' + xml: + name: tag + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + type: object + # remove ApiResponse + PetOwner: + type: "object" + properties: + id: + type: "integer" + format: "int64" + example: 10 + petId: + type: "integer" + format: "int64" + example: 201 + petOwnerFirstName: + type: "string" + example: "John" + petOwnerLastName: + type: "string" + example: "Smith" + petOwnerContact: + type: "string" + example: "john.smith@fakeemail.co" + petOwnerStatus: + type: "integer" + format: "int32" + description: "Pet Owner Status" + example: 302 + petOwnerPreferences: + type: "string" + description: "Pet Owner Preferred Pet Types" + enum: + - "dog" + - "cat" + - "bird" + - "fish" + - "other" + petOwnerServiceTrainer: + type: "boolean" + description: "Pet Onwer is Service Trainer" + default: false + Tag: + x-swagger-router-model: io.swagger.petstore.model.Tag + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: tag + type: object + requestBodies: + Pet: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + description: Pet object that needs to be added to the store diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/schema-form-enum-boolean.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/schema-form-enum-boolean.yaml new file mode 100644 index 0000000..1c87d2a --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/schema-form-enum-boolean.yaml @@ -0,0 +1,87 @@ +swagger: "2.0" +info: + description: "Test Required Enum and Boolean with Execute" + version: "1.0.0" + title: "Swagger Petstore" + termsOfService: "http://swagger.io/terms/" + contact: + email: "apiteam@swagger.io" + license: + name: "Apache 2.0" + url: "http://www.apache.org/licenses/LICENSE-2.0.html" +host: "petstore.swagger.io" +basePath: "/v2" +tags: +- name: "pet" + description: "Everything about your Pets" + externalDocs: + description: "Find out more" + url: "http://swagger.io" +schemes: +- "https" +- "http" +paths: + /pet/findByStatus: + get: + tags: + - "pet" + summary: "Finds Pets by status" + description: "Multiple status values can be provided with comma separated strings" + operationId: "findPetsByStatus" + produces: + - "application/xml" + - "application/json" + parameters: + - name: "status" + in: "query" + description: "Status values that need to be considered for filter" + required: true + type: "array" + items: + type: "string" + enum: + - "available" + - "pending" + - "sold" + default: "available" + collectionFormat: "multi" + - name: "expectIsOptional" + in: "query" + description: "this field should be optional" + required: false + type: boolean + - name: "expectIsRequired" + in: "query" + description: "this field should be required" + required: true + type: boolean + responses: + "200": + description: "successful operation" + schema: + type: "array" + items: + $ref: "#/definitions/Pet" + "400": + description: "Invalid status value" +definitions: + Pet: + type: "object" + required: + - "name" + properties: + id: + type: "integer" + format: "int64" + name: + type: "string" + example: "doggie" + status: + type: "string" + description: "pet status in the store" + enum: + - "available" + - "pending" + - "sold" + xml: + name: "Pet" diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/schema-form-missing-values.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/schema-form-missing-values.yaml new file mode 100644 index 0000000..b1d7f13 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/schema-form-missing-values.yaml @@ -0,0 +1,119 @@ +openapi: 3.0.0 +info: + description: No type for schema + version: "1" + title: "No type" +paths: + /case-one-no-schema: + parameters: + - name: namespace + description: The custom resource's namespace + # schema: + in: path + required: true + get: + description: sf + responses: + default: + description: one + /case-one-no-type-or-format: + parameters: + - name: namespace + description: The custom resource's namespace + schema: + in: path + required: true + get: + description: sf + responses: + default: + description: one + /case-one-type-only-no-format: + parameters: + - name: namespace + description: The custom resource's namespace + schema: + type: integer + in: path + required: true + get: + description: sf + responses: + default: + description: one + /case-one-format-only-no-type: + parameters: + - name: namespace + description: The custom resource's namespace + schema: + format: int64 + in: path + required: true + get: + description: sf + responses: + default: + description: one + /case-two-no-schema: + get: + description: sf + responses: + default: + description: one + parameters: + - name: namespace + in: path + description: The custom resource's namespace + required: true + # schema: + /case-two-no-type-or-format: + get: + description: sf + responses: + default: + description: one + parameters: + - name: namespace + in: path + description: The custom resource's namespace + required: true + schema: + /case-two-type-only-no-format: + get: + description: sf + responses: + default: + description: one + parameters: + - name: namespace + in: path + description: The custom resource's namespace + required: true + schema: + type: integer + /case-two-format-only-no-type: + get: + description: sf + responses: + default: + description: one + parameters: + - name: namespace + in: path + description: The custom resource's namespace + required: true + schema: + format: int64 + /case-two-not-a-real-type: + get: + description: sf + responses: + default: + description: one + parameters: + - name: namespace + in: path + description: The custom resource's namespace + required: true + schema: + type: "NotARealType" diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/spec-parse-to-json.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/spec-parse-to-json.yaml new file mode 100644 index 0000000..8d0ea60 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/spec-parse-to-json.yaml @@ -0,0 +1,26 @@ +openapi: 3.0.0 + +info: + version: 1.0.0 + title: Native date formats in YAML + +paths: + /foo: + get: + description: Has date without quotes + responses: + '200': + description: Ok + content: + application/json: + schema: + type: object + properties: + without-quotes: + type: string + format: date + example: 1999-11-31 + with-quotes: + type: string + format: date + example: "1999-12-31" diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/syntax-highlighting-json-oas2.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/syntax-highlighting-json-oas2.yaml new file mode 100644 index 0000000..6b93d85 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/syntax-highlighting-json-oas2.yaml @@ -0,0 +1,97 @@ +swagger: '2.0' +info: + description: sample OAS 2 definition to test syntax highlighting + version: 1.0.0 + title: json syntax highlighting +host: "localhost:3200" +basePath: /v1 +schemes: + - https + - http +paths: + /setServices: + post: + summary: "simple service" + produces: + - application/json + parameters: + - in: body + name: body + required: true + schema: + $ref: '#/definitions/setServicesBody' + responses: + 200: + description: OK + schema: + $ref: '#/definitions/setServicesResponse' + 404: + description: "Page not found" +definitions: + setServicesBody: + type: object + required: + - appid + - key + - userid + - station_objectid + - details + properties: + appid: + type: string + example: "Website" + description: "application ID" + userid: + type: integer + example: "79daf5b4-aa4b-1452-eae5-42c231477ba7" + description: "user id available to test" + station_objectid: + type: string + example: "22a124b4-594b-4452-bdf5-fc3ef1477ba7" + description: "station id available to test" + details: + type: array + items: + type: object + properties: + station_serviceid: + type: integer + example: "34" + description: "optional service id" + name: + type: string + example: "hooray" + amount: + type: string + example: "0.00" + quantity: + type: integer + example: "999999" + date: + type: string + format: date-time + example: "2020-11-12 18:52:29" + setServicesResponse: + type: object + properties: + status: + type: boolean + example: true + count: + type: boolean + example: 1 + response: + type: object + properties: + status: + type: integer + example: 200 + station_serviceid: + type: integer + example: "3" + userid: + type: integer + example: "5ff06f632bb165394501b05d3a833355" + statusId: + type: string + example: "f0009babde9dbe204540d79cf754408e" diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/syntax-highlighting-json-oas3.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/syntax-highlighting-json-oas3.yaml new file mode 100644 index 0000000..183fa07 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/syntax-highlighting-json-oas3.yaml @@ -0,0 +1,94 @@ +openapi: 3.0.1 +info: + title: json syntax highlighting + description: sample OAS 3 definition to test syntax highlighting + version: 1.0.0 +servers: +- url: https://localhost:3200/v1 +- url: http://localhost:3200/v1 +paths: + /setServices: + post: + summary: simple service + requestBody: + content: + '*/*': + schema: + $ref: '#/components/schemas/setServicesBody' + required: true + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/setServicesResponse' + 404: + description: Page not found + content: {} + x-codegen-request-body-name: body +components: + schemas: + setServicesBody: + required: + - appid + - details + - station_objectid + - userid + type: object + properties: + appid: + type: string + description: application ID + example: Website + userid: + type: integer + description: user id available to test + station_objectid: + type: string + description: station id available to test + example: 22a124b4-594b-4452-bdf5-fc3ef1477ba7 + details: + type: array + items: + type: object + properties: + station_serviceid: + type: integer + description: optional service id + example: 34 + name: + type: string + example: hooray + amount: + type: string + example: "0.00" + quantity: + type: integer + example: 999999 + date: + type: string + format: date-time + setServicesResponse: + type: object + properties: + status: + type: boolean + example: true + count: + type: boolean + example: false + response: + type: object + properties: + status: + type: integer + example: 200 + station_serviceid: + type: integer + example: 3 + userid: + type: integer + statusId: + type: string + example: f0009babde9dbe204540d79cf754408e diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/try-it-out-enabled.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/try-it-out-enabled.yaml new file mode 100644 index 0000000..6b0bb00 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/try-it-out-enabled.yaml @@ -0,0 +1,13 @@ +openapi: 3.0.2 +info: + title: try it out enabled test + version: 0.0.1 + description: |- + a simple test to ensure tryItOutEnabled=true expands "Try it out" +paths: + /: + get: + summary: an operation + responses: + "200": + description: OK diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/try-it-out-schema-required-override-allowed.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/try-it-out-schema-required-override-allowed.yaml new file mode 100644 index 0000000..b0988db --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/try-it-out-schema-required-override-allowed.yaml @@ -0,0 +1,34 @@ +swagger: '2.0' +info: + version: '1.0' + title: "schema required properties should not be treated like required: true" +paths: + '/v1/any-path': + put: + summary: lorem + operationId: setDeliveryLocation + produces: + - application/json + parameters: + - in: body + name: body + description: ipsum + required: false + schema: + $ref: '#/definitions/TopModel' + responses: + '200': + description: successful operation +definitions: + TopModel: + type: object + properties: + testProperty: + $ref: '#/definitions/NestedModel' + NestedModel: + type: object + required: + - validated + properties: + validated: + type: boolean diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/urls/1.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/urls/1.yaml new file mode 100644 index 0000000..38f01c3 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/urls/1.yaml @@ -0,0 +1,12 @@ +swagger: "2.0" + +info: + title: One + +paths: + /: + get: + summary: an operation + responses: + "200": + description: OK \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/urls/2.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/urls/2.yaml new file mode 100644 index 0000000..77b0ab6 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/urls/2.yaml @@ -0,0 +1,12 @@ +openapi: "3.0.0" + +info: + title: Two + +paths: + /: + get: + summary: an operation + responses: + "200": + description: OK \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/urls/server-variables-1.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/urls/server-variables-1.yaml new file mode 100644 index 0000000..d997015 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/urls/server-variables-1.yaml @@ -0,0 +1,38 @@ +openapi: 3.0.1 +info: + title: Server Variables - One + description: sample OAS 3 definition to test server variables with urls + version: 1.0.0 +servers: +- url: "https://localhost:3200{basePath}" + variables: + basePath: + default: "/oneFirstUrl" +- url: "http://localhost:3201{basePath}" + variables: + basePath: + default: "/oneSecondUrl" +paths: + /a: + post: + summary: simple service A + requestBody: + content: + 'application/json': + schema: + properties: + foo: + type: string + bar: + type: string + required: + - foo + type: object + required: true + responses: + 200: + description: OK + content: + application/json: + schema: + type: 'string' diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/features/urls/server-variables-2.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/urls/server-variables-2.yaml new file mode 100644 index 0000000..b65ff9c --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/features/urls/server-variables-2.yaml @@ -0,0 +1,38 @@ +openapi: 3.0.1 +info: + title: Server Variables - Two + description: sample OAS 3 definition to test server variables with urls + version: 1.0.0 +servers: +- url: "https://localhost:3200{basePath}" + variables: + basePath: + default: "/twoFirstUrl" +- url: "http://localhost:3201{basePath}" + variables: + basePath: + default: "/twoSecondUrl" +paths: + /b: + post: + summary: simple service B + requestBody: + content: + 'application/json': + schema: + properties: + foo: + type: string + bar: + type: string + required: + - foo + type: object + required: true + responses: + 200: + description: OK + content: + application/json: + schema: + type: 'string' diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/petstore-expanded.openapi.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/petstore-expanded.openapi.yaml new file mode 100644 index 0000000..9864620 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/petstore-expanded.openapi.yaml @@ -0,0 +1,166 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + description: A sample API that uses a petstore as an example to demonstrate features in the OpenAPI 3.0 specification + termsOfService: http://swagger.io/terms/ + contact: + name: Swagger API Team + email: apiteam@swagger.io + url: http://swagger.io + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html +servers: + - url: http://petstore.swagger.io/api +security: + - Petstore: [] +paths: + /pets: + get: + description: | + Returns all pets from the system that the user has access to + Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia. + + Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. + operationId: findPets + parameters: + - name: tags + in: query + description: tags to filter by + required: false + style: form + schema: + type: array + items: + type: string + - name: limit + in: query + description: maximum number of results to return + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: pet response + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + post: + description: Creates a new pet in the store. Duplicates are allowed + operationId: addPet + requestBody: + description: Pet to add to the store + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NewPet' + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /pets/{id}: + get: + description: Returns a user based on a single ID, if the user does not have access to the pet + operationId: find pet by id + parameters: + - name: id + in: path + description: ID of pet to fetch + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: pet response + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + delete: + description: deletes a single pet based on the ID supplied + operationId: deletePet + parameters: + - name: id + in: path + description: ID of pet to delete + required: true + schema: + type: integer + format: int64 + responses: + '204': + description: pet deleted + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' +components: + schemas: + Pet: + allOf: + - $ref: '#/components/schemas/NewPet' + - required: + - id + properties: + id: + type: integer + format: int64 + + NewPet: + required: + - name + properties: + name: + type: string + tag: + type: string + + Error: + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + securitySchemes: + Petstore: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/api/oauth/dialog + scopes: + write:pets: modify pets in your account + read:pets: read your pets diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/petstore.swagger.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/petstore.swagger.yaml new file mode 100644 index 0000000..f842a6c --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/petstore.swagger.yaml @@ -0,0 +1,707 @@ +# As found on https://petstore.swagger.io, August 2018 +--- +swagger: '2.0' +info: + description: 'This is a sample server Petstore server. You can find out more about + Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For + this sample, you can use the api key `special-key` to test the authorization filters.' + version: 1.0.0 + title: Swagger Petstore + termsOfService: http://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html +host: petstore.swagger.io +basePath: "/v2" +tags: +- name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: http://swagger.io +- name: store + description: Access to Petstore orders +- name: user + description: Operations about user + externalDocs: + description: Find out more about our store + url: http://swagger.io +schemes: +- https +- http +paths: + "/pet": + post: + tags: + - pet + summary: Add a new pet to the store + description: '' + operationId: addPet + consumes: + - application/json + - application/xml + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: Pet object that needs to be added to the store + required: true + schema: + "$ref": "#/definitions/Pet" + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + put: + tags: + - pet + summary: Update an existing pet + description: '' + operationId: updatePet + consumes: + - application/json + - application/xml + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: Pet object that needs to be added to the store + required: true + schema: + "$ref": "#/definitions/Pet" + responses: + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '405': + description: Validation exception + security: + - petstore_auth: + - write:pets + - read:pets + "/pet/findByStatus": + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + produces: + - application/xml + - application/json + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + type: array + items: + type: string + enum: + - available + - pending + - sold + default: available + collectionFormat: multi + responses: + '200': + description: successful operation + schema: + type: array + items: + "$ref": "#/definitions/Pet" + '400': + description: Invalid status value + security: + - petstore_auth: + - write:pets + - read:pets + "/pet/findByTags": + get: + tags: + - pet + summary: Finds Pets by tags + description: Muliple tags can be provided with comma separated strings. Use + tag1, tag2, tag3 for testing. + operationId: findPetsByTags + produces: + - application/xml + - application/json + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + type: array + items: + type: string + collectionFormat: multi + responses: + '200': + description: successful operation + schema: + type: array + items: + "$ref": "#/definitions/Pet" + '400': + description: Invalid tag value + security: + - petstore_auth: + - write:pets + - read:pets + deprecated: true + "/pet/{petId}": + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + produces: + - application/xml + - application/json + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + type: integer + format: int64 + responses: + '200': + description: successful operation + schema: + "$ref": "#/definitions/Pet" + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - api_key: [] + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: '' + operationId: updatePetWithForm + consumes: + - application/x-www-form-urlencoded + produces: + - application/xml + - application/json + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + type: integer + format: int64 + - name: name + in: formData + description: Updated name of the pet + required: false + type: string + - name: status + in: formData + description: Updated status of the pet + required: false + type: string + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + delete: + tags: + - pet + summary: Deletes a pet + description: '' + operationId: deletePet + produces: + - application/xml + - application/json + parameters: + - name: api_key + in: header + required: false + type: string + - name: petId + in: path + description: Pet id to delete + required: true + type: integer + format: int64 + responses: + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - petstore_auth: + - write:pets + - read:pets + "/pet/{petId}/uploadImage": + post: + tags: + - pet + summary: uploads an image + description: '' + operationId: uploadFile + consumes: + - multipart/form-data + produces: + - application/json + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + type: integer + format: int64 + - name: additionalMetadata + in: formData + description: Additional data to pass to server + required: false + type: string + - name: file + in: formData + description: file to upload + required: false + type: file + responses: + '200': + description: successful operation + schema: + "$ref": "#/definitions/ApiResponse" + security: + - petstore_auth: + - write:pets + - read:pets + "/store/inventory": + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + produces: + - application/json + parameters: [] + responses: + '200': + description: successful operation + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + "/store/order": + post: + tags: + - store + summary: Place an order for a pet + description: '' + operationId: placeOrder + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: order placed for purchasing the pet + required: true + schema: + "$ref": "#/definitions/Order" + responses: + '200': + description: successful operation + schema: + "$ref": "#/definitions/Order" + '400': + description: Invalid Order + "/store/order/{orderId}": + get: + tags: + - store + summary: Find purchase order by ID + description: For valid response try integer IDs with value >= 1 and <= 10. Other + values will generated exceptions + operationId: getOrderById + produces: + - application/xml + - application/json + parameters: + - name: orderId + in: path + description: ID of pet that needs to be fetched + required: true + type: integer + maximum: 10 + minimum: 1 + format: int64 + responses: + '200': + description: successful operation + schema: + "$ref": "#/definitions/Order" + '400': + description: Invalid ID supplied + '404': + description: Order not found + delete: + tags: + - store + summary: Delete purchase order by ID + description: For valid response try integer IDs with positive integer value. + Negative or non-integer values will generate API errors + operationId: deleteOrder + produces: + - application/xml + - application/json + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + type: integer + minimum: 1 + format: int64 + responses: + '400': + description: Invalid ID supplied + '404': + description: Order not found + "/user": + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: Created user object + required: true + schema: + "$ref": "#/definitions/User" + responses: + default: + description: successful operation + "/user/createWithArray": + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithArrayInput + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: List of user object + required: true + schema: + type: array + items: + "$ref": "#/definitions/User" + responses: + default: + description: successful operation + "/user/createWithList": + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithListInput + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: List of user object + required: true + schema: + type: array + items: + "$ref": "#/definitions/User" + responses: + default: + description: successful operation + "/user/login": + get: + tags: + - user + summary: Logs user into the system + description: '' + operationId: loginUser + produces: + - application/xml + - application/json + parameters: + - name: username + in: query + description: The user name for login + required: true + type: string + - name: password + in: query + description: The password for login in clear text + required: true + type: string + responses: + '200': + description: successful operation + schema: + type: string + headers: + X-Rate-Limit: + type: integer + format: int32 + description: calls per hour allowed by the user + X-Expires-After: + type: string + format: date-time + description: date in UTC when token expires + '400': + description: Invalid username/password supplied + "/user/logout": + get: + tags: + - user + summary: Logs out current logged in user session + description: '' + operationId: logoutUser + produces: + - application/xml + - application/json + parameters: [] + responses: + default: + description: successful operation + "/user/{username}": + get: + tags: + - user + summary: Get user by user name + description: '' + operationId: getUserByName + produces: + - application/xml + - application/json + parameters: + - name: username + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + type: string + responses: + '200': + description: successful operation + schema: + "$ref": "#/definitions/User" + '400': + description: Invalid username supplied + '404': + description: User not found + put: + tags: + - user + summary: Updated user + description: This can only be done by the logged in user. + operationId: updateUser + produces: + - application/xml + - application/json + parameters: + - name: username + in: path + description: name that need to be updated + required: true + type: string + - in: body + name: body + description: Updated user object + required: true + schema: + "$ref": "#/definitions/User" + responses: + '400': + description: Invalid user supplied + '404': + description: User not found + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + produces: + - application/xml + - application/json + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + type: string + responses: + '400': + description: Invalid username supplied + '404': + description: User not found +securityDefinitions: + petstore_auth: + type: oauth2 + authorizationUrl: https://petstore.swagger.io/oauth/dialog + flow: implicit + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header +definitions: + Order: + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + quantity: + type: integer + format: int32 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + type: boolean + default: false + xml: + name: Order + User: + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + format: int32 + description: User Status + xml: + name: User + Category: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Category + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Tag + Pet: + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + category: + "$ref": "#/definitions/Category" + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + "$ref": "#/definitions/Tag" + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: Pet + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string +externalDocs: + description: Find out more about Swagger + url: http://swagger.io diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/security/anonymous.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/security/anonymous.yaml new file mode 100644 index 0000000..e71331c --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/security/anonymous.yaml @@ -0,0 +1,35 @@ +openapi: 3.0.0 +info: + title: Test anonymous operations + version: 1.0.0 +paths: + /withBoth: + get: + security: [{}, {"apikeyScheme": []}, {"apikeyScheme2": []}] + responses: + 200: + description: asdadasd + /onlyEmpty: + get: + security: [{}] + responses: + 200: + description: asdadasd + /required: + get: + security: [{"apikeyScheme": []}] + responses: + 200: + description: asdadasd +security: + - apikeyScheme: [] +components: + securitySchemes: + apikeyScheme: + name: test + type: apiKey + in: header + apikeyScheme2: + name: test2 + type: apiKey + in: header diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/security/sequential-import-chaining/injection.css b/frontend/web/api-doc/test/e2e-cypress/static/documents/security/sequential-import-chaining/injection.css new file mode 100644 index 0000000..edc480f --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/security/sequential-import-chaining/injection.css @@ -0,0 +1,7 @@ +* { + color: red !important; /* for humans */ +} + +h4 { + display: none; /* for machines, used to trace whether this sheet is applied */ +} diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/security/sequential-import-chaining/openapi.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/security/sequential-import-chaining/openapi.yaml new file mode 100644 index 0000000..e4e4ade --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/security/sequential-import-chaining/openapi.yaml @@ -0,0 +1,10 @@ +openapi: "3.0.0" + +info: + title: Sequential Import Chaining + description: > +

This h4 would be hidden by the injected CSS

+ + This document tests the ability of a ` diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/security/sequential-import-chaining/swagger.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/security/sequential-import-chaining/swagger.yaml new file mode 100644 index 0000000..5f9cc44 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/security/sequential-import-chaining/swagger.yaml @@ -0,0 +1,10 @@ +swagger: "2.0" + +info: + title: Sequential Import Chaining + description: > +

This h4 would be hidden by the injected CSS

+ + This document tests the ability of a ` diff --git a/frontend/web/api-doc/test/e2e-cypress/static/documents/security/xss-oauth2.yaml b/frontend/web/api-doc/test/e2e-cypress/static/documents/security/xss-oauth2.yaml new file mode 100644 index 0000000..4ff4cc7 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/documents/security/xss-oauth2.yaml @@ -0,0 +1,5 @@ +swagger: '2.0' +securityDefinitions: + a: + type: oauth2 + authorizationUrl: javascript:alert(document.domain)// diff --git a/frontend/web/api-doc/test/e2e-cypress/static/index.html b/frontend/web/api-doc/test/e2e-cypress/static/index.html new file mode 100644 index 0000000..0313735 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/index.html @@ -0,0 +1,77 @@ + + + + + + + Swagger UI + + + + + + + + +
+ + + + + + + diff --git a/frontend/web/api-doc/test/e2e-cypress/static/pages/5085/index.html b/frontend/web/api-doc/test/e2e-cypress/static/pages/5085/index.html new file mode 100644 index 0000000..df3c2a2 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/pages/5085/index.html @@ -0,0 +1,67 @@ + + + + + + + Swagger UI + + + + + + + + +
+ + + + + + + diff --git a/frontend/web/api-doc/test/e2e-cypress/static/pages/5138/api-with-examples.yaml b/frontend/web/api-doc/test/e2e-cypress/static/pages/5138/api-with-examples.yaml new file mode 100644 index 0000000..09003b6 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/pages/5138/api-with-examples.yaml @@ -0,0 +1,167 @@ +openapi: "3.0.0" +info: + title: Simple API overview + version: 2.0.0 +paths: + /: + get: + operationId: listVersionsv2 + summary: List API versions + responses: + '200': + description: |- + 200 response + content: + application/json: + examples: + foo: + value: { + "versions": [ + { + "status": "CURRENT", + "updated": "2011-01-21T11:33:21Z", + "id": "v2.0", + "links": [ + { + "href": "http://127.0.0.1:8774/v2/", + "rel": "self" + } + ] + }, + { + "status": "EXPERIMENTAL", + "updated": "2013-07-23T11:33:21Z", + "id": "v3.0", + "links": [ + { + "href": "http://127.0.0.1:8774/v3/", + "rel": "self" + } + ] + } + ] + } + '300': + description: |- + 300 response + content: + application/json: + examples: + foo: + value: | + { + "versions": [ + { + "status": "CURRENT", + "updated": "2011-01-21T11:33:21Z", + "id": "v2.0", + "links": [ + { + "href": "http://127.0.0.1:8774/v2/", + "rel": "self" + } + ] + }, + { + "status": "EXPERIMENTAL", + "updated": "2013-07-23T11:33:21Z", + "id": "v3.0", + "links": [ + { + "href": "http://127.0.0.1:8774/v3/", + "rel": "self" + } + ] + } + ] + } + /v2: + get: + operationId: getVersionDetailsv2 + summary: Show API version details + responses: + '200': + description: |- + 200 response + content: + application/json: + examples: + foo: + value: { + "version": { + "status": "CURRENT", + "updated": "2011-01-21T11:33:21Z", + "media-types": [ + { + "base": "application/xml", + "type": "application/vnd.openstack.compute+xml;version=2" + }, + { + "base": "application/json", + "type": "application/vnd.openstack.compute+json;version=2" + } + ], + "id": "v2.0", + "links": [ + { + "href": "http://127.0.0.1:8774/v2/", + "rel": "self" + }, + { + "href": "http://docs.openstack.org/api/openstack-compute/2/os-compute-devguide-2.pdf", + "type": "application/pdf", + "rel": "describedby" + }, + { + "href": "http://docs.openstack.org/api/openstack-compute/2/wadl/os-compute-2.wadl", + "type": "application/vnd.sun.wadl+xml", + "rel": "describedby" + }, + { + "href": "http://docs.openstack.org/api/openstack-compute/2/wadl/os-compute-2.wadl", + "type": "application/vnd.sun.wadl+xml", + "rel": "describedby" + } + ] + } + } + '203': + description: |- + 203 response + content: + application/json: + examples: + foo: + value: { + "version": { + "status": "CURRENT", + "updated": "2011-01-21T11:33:21Z", + "media-types": [ + { + "base": "application/xml", + "type": "application/vnd.openstack.compute+xml;version=2" + }, + { + "base": "application/json", + "type": "application/vnd.openstack.compute+json;version=2" + } + ], + "id": "v2.0", + "links": [ + { + "href": "http://23.253.228.211:8774/v2/", + "rel": "self" + }, + { + "href": "http://docs.openstack.org/api/openstack-compute/2/os-compute-devguide-2.pdf", + "type": "application/pdf", + "rel": "describedby" + }, + { + "href": "http://docs.openstack.org/api/openstack-compute/2/wadl/os-compute-2.wadl", + "type": "application/vnd.sun.wadl+xml", + "rel": "describedby" + } + ] + } + } diff --git a/frontend/web/api-doc/test/e2e-cypress/static/pages/5138/index.html b/frontend/web/api-doc/test/e2e-cypress/static/pages/5138/index.html new file mode 100644 index 0000000..0607f10 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/pages/5138/index.html @@ -0,0 +1,77 @@ + + + + + + + + Swagger UI + + + + + + +
+ + + + + + + diff --git a/frontend/web/api-doc/test/e2e-cypress/static/pages/5138/uspto.yaml b/frontend/web/api-doc/test/e2e-cypress/static/pages/5138/uspto.yaml new file mode 100644 index 0000000..8e9b159 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/pages/5138/uspto.yaml @@ -0,0 +1,210 @@ +openapi: 3.0.1 +servers: + - url: '{scheme}://developer.uspto.gov/ds-api' + variables: + scheme: + description: 'The Data Set API is accessible via https and http' + enum: + - 'https' + - 'http' + default: 'https' +info: + description: >- + The Data Set API (DSAPI) allows the public users to discover and search + USPTO exported data sets. This is a generic API that allows USPTO users to + make any CSV based data files searchable through API. With the help of GET + call, it returns the list of data fields that are searchable. With the help + of POST call, data can be fetched based on the filters on the field names. + Please note that POST call is used to search the actual data. The reason for + the POST call is that it allows users to specify any complex search criteria + without worry about the GET size limitations as well as encoding of the + input parameters. + version: 1.0.0 + title: USPTO Data Set API + contact: + name: Open Data Portal + url: 'https://developer.uspto.gov' + email: developer@uspto.gov +tags: + - name: metadata + description: Find out about the data sets + - name: search + description: Search a data set +paths: + /: + get: + tags: + - metadata + operationId: list-data-sets + summary: List available data sets + responses: + '200': + description: Returns a list of data sets + content: + application/json: + schema: + $ref: '#/components/schemas/dataSetList' + example: + { + "total": 2, + "apis": [ + { + "apiKey": "oa_citations", + "apiVersionNumber": "v1", + "apiUrl": "https://developer.uspto.gov/ds-api/oa_citations/v1/fields", + "apiDocumentationUrl": "https://developer.uspto.gov/ds-api-docs/index.html?url=https://developer.uspto.gov/ds-api/swagger/docs/oa_citations.json" + }, + { + "apiKey": "cancer_moonshot", + "apiVersionNumber": "v1", + "apiUrl": "https://developer.uspto.gov/ds-api/cancer_moonshot/v1/fields", + "apiDocumentationUrl": "https://developer.uspto.gov/ds-api-docs/index.html?url=https://developer.uspto.gov/ds-api/swagger/docs/cancer_moonshot.json" + } + ] + } + /{dataset}/{version}/fields: + get: + tags: + - metadata + summary: >- + Provides the general information about the API and the list of fields + that can be used to query the dataset. + description: >- + This GET API returns the list of all the searchable field names that are + in the oa_citations. Please see the 'fields' attribute which returns an + array of field names. Each field or a combination of fields can be + searched using the syntax options shown below. + operationId: list-searchable-fields + parameters: + - name: dataset + in: path + description: 'Name of the dataset.' + required: true + example: "oa_citations" + schema: + type: string + - name: version + in: path + description: Version of the dataset. + required: true + example: "v1" + schema: + type: string + responses: + '200': + description: >- + The dataset API for the given version is found and it is accessible + to consume. + content: + application/json: + schema: + type: string + '404': + description: >- + The combination of dataset name and version is not found in the + system or it is not published yet to be consumed by public. + content: + application/json: + schema: + type: string + /{dataset}/{version}/records: + post: + tags: + - search + summary: >- + Provides search capability for the data set with the given search + criteria. + description: >- + This API is based on Solr/Lucense Search. The data is indexed using + SOLR. This GET API returns the list of all the searchable field names + that are in the Solr Index. Please see the 'fields' attribute which + returns an array of field names. Each field or a combination of fields + can be searched using the Solr/Lucene Syntax. Please refer + https://lucene.apache.org/core/3_6_2/queryparsersyntax.html#Overview for + the query syntax. List of field names that are searchable can be + determined using above GET api. + operationId: perform-search + parameters: + - name: version + in: path + description: Version of the dataset. + required: true + schema: + type: string + default: v1 + - name: dataset + in: path + description: 'Name of the dataset. In this case, the default value is oa_citations' + required: true + schema: + type: string + default: oa_citations + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + type: object + additionalProperties: + type: object + '404': + description: No matching record found for the given criteria. + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + criteria: + description: >- + Uses Lucene Query Syntax in the format of + propertyName:value, propertyName:[num1 TO num2] and date + range format: propertyName:[yyyyMMdd TO yyyyMMdd]. In the + response please see the 'docs' element which has the list of + record objects. Each record structure would consist of all + the fields and their corresponding values. + type: string + default: '*:*' + start: + description: Starting record number. Default value is 0. + type: integer + default: 0 + rows: + description: >- + Specify number of rows to be returned. If you run the search + with default values, in the response you will see 'numFound' + attribute which will tell the number of records available in + the dataset. + type: integer + default: 100 + required: + - criteria +components: + schemas: + dataSetList: + type: object + properties: + total: + type: integer + apis: + type: array + items: + type: object + properties: + apiKey: + type: string + description: To be used as a dataset parameter value + apiVersionNumber: + type: string + description: To be used as a version parameter value + apiUrl: + type: string + format: uriref + description: "The URL describing the dataset's fields" + apiDocumentationUrl: + type: string + format: uriref + description: A URL to the API console for each API diff --git a/frontend/web/api-doc/test/e2e-cypress/static/pages/multiple-urls/index.html b/frontend/web/api-doc/test/e2e-cypress/static/pages/multiple-urls/index.html new file mode 100644 index 0000000..e7d4754 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/static/pages/multiple-urls/index.html @@ -0,0 +1,80 @@ + + + + + + + Swagger UI + + + + + + + + +
+ + + + + + + diff --git a/frontend/web/api-doc/test/e2e-cypress/support/commands.js b/frontend/web/api-doc/test/e2e-cypress/support/commands.js new file mode 100644 index 0000000..c1f5a77 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/support/commands.js @@ -0,0 +1,25 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add("login", (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This is will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/frontend/web/api-doc/test/e2e-cypress/support/index.js b/frontend/web/api-doc/test/e2e-cypress/support/index.js new file mode 100644 index 0000000..d5ef78f --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/support/index.js @@ -0,0 +1,32 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import "./commands" + +// Alternatively you can use CommonJS syntax: +// require('./commands') + + +// Remove fetch, so Cypress can intercept XHRs +// see https://github.com/cypress-io/cypress/issues/95 +Cypress.on("window:before:load", win => { + win.fetch = null +}) + +Cypress.on("uncaught:exception", (err, runnable) => { + console.log(JSON.stringify(err, null, 2)) + return true +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/a11y/response-tabs.js b/frontend/web/api-doc/test/e2e-cypress/tests/a11y/response-tabs.js new file mode 100644 index 0000000..e6f9a45 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/a11y/response-tabs.js @@ -0,0 +1,35 @@ +describe("Response tab elements", () => { + describe("ModelExample within Operation", () => { + it("should render Example tabpanel by default", () => { + cy + .visit("/?url=/documents/petstore-expanded.openapi.yaml") + .get("#operations-default-addPet") + .click() + .get("div[data-name=examplePanel]") + .first() + .should("have.attr", "aria-hidden", "false") + }) + it("should click Schema tab button and render Schema tabpanel for OpenAPI 3", () => { + cy + .visit("/?url=/documents/petstore-expanded.openapi.yaml") + .get("#operations-default-addPet") + .click() + .get("button.tablinks[data-name=model]") + .first() + .click() + .get("div[data-name=modelPanel]") + .first() + .should("have.attr", "aria-hidden", "false") + }) + it("should click Model tab button and render Model tabpanel for OpenAPI 2", () => { + cy + .visit("/?url=/documents/petstore.swagger.yaml") + .get("#operations-pet-addPet") + .click() + .get("button.tablinks[data-name=model]") + .click() + .get("div[data-name=modelPanel]") + .should("have.attr", "aria-hidden", "false") + }) + }) +}) \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/4442.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/4442.js new file mode 100644 index 0000000..ea1161d --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/4442.js @@ -0,0 +1,37 @@ +/** + * @prettier + */ + +describe("UI #4442: Parameter.content display and execution", function() { + it("should display textareas as static documentation according to the `example`", () => { + cy.visit("/?url=/documents/bugs/4442.yaml") + .get(`#operations-default-get_`) + .click() + .get(".btn.try-out__btn") + .click() + .get( + `div.json-schema-array > div:nth-child(1) > div > textarea` + ) + .should("have.value", `{\n "userId": 1,\n "currency": "USD"\n}`) + .get( + `div.json-schema-array > div:nth-child(2) > div > textarea` + ) + .should("have.value", `{\n "userId": 2,\n "currency": "CAD"\n}`) + }) + it("should serialize JSON into a query correctly", () => { + cy.visit("/?url=/documents/bugs/4442.yaml") + .get(`#operations-default-get_`) + .click() + .get(".btn.try-out__btn") + .click() + .get(".btn.execute") + .click() + .get(".request-url pre") + .should( + "have.text", + `http://localhost:3230/?users=${encodeURIComponent( + `[{"userId":1,"currency":"USD"},{"userId":2,"currency":"CAD"}]` + )}` + ) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/4641.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/4641.js new file mode 100644 index 0000000..1a4bb03 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/4641.js @@ -0,0 +1,99 @@ +const clickTryItOutAndExecute = () => { + return cy + .get(".btn.try-out__btn") // expand "try it out" + .click() + .get(".btn.execute") // execute request + .click() +} + +const fillInApiKeyAndAuthorise = apiKey => () => { + return cy + .get("section>input") // type api key into input + .type(apiKey) + .get(".auth-btn-wrapper > .authorize") // authorise button + .click() +} + +const clickLogoutAndReauthorise = () => { + return cy + .get(".auth-btn-wrapper button:nth-child(1)") // logout button + .click() + .get(".auth-btn-wrapper > .authorize") // authorise button + .click() +} + +describe("#4641: The Logout button in Authorize popup not clearing API Key", () => { + beforeEach(() => { + cy.server() + cy + .route({ + url: "/4641*", + response: "OK", + }) + .as("request") + }) + + it("should include the given api key in requests", () => { + cy + .visit("/?url=/documents/bugs/4641.yaml") + .get("button.btn.authorize") // open authorize popup + .click() + .get(".modal-ux-content > :nth-child(1)") // only deal with api_key_1 for this test + .within(fillInApiKeyAndAuthorise("my_api_key")) + .get(".close-modal") // close authorise popup button + .click() + .get("#operations-default-get_4641_1") // expand the route details onClick + .click() + .within(clickTryItOutAndExecute) + .wait("@request") + .its("request") + .then((req) => { + expect(req.headers, "request headers").to.have.property("api_key_1", "my_api_key") + }) + }) + + it("should not remember the previous auth value when you logout and reauthorise", () => { + cy + .visit("/?url=/documents/bugs/4641.yaml") + .get("button.btn.authorize") // open authorize popup + .click() + .get(".modal-ux-content > :nth-child(1)") // only deal with api_key_1 for this test + .within(fillInApiKeyAndAuthorise("my_api_key")) + .get(".modal-ux-content > :nth-child(1)") // only deal with api_key_1 for this test + .within(clickLogoutAndReauthorise) + .get(".close-modal") // close authorise popup button + .click() + .get("#operations-default-get_4641_1") // expand the route details onClick + .click() + .within(clickTryItOutAndExecute) + .wait("@request") + .its("request") + .then((req) => { + expect(req.headers, "request headers").not.to.have.property("api_key_1") + }) + }) + + it("should only forget the value of the auth the user logged out from", () => { + cy + .visit("/?url=/documents/bugs/4641.yaml") + .get("button.btn.authorize") // open authorize popup + .click() + .get(".modal-ux-content > :nth-child(1)") // deal with api_key_1 + .within(fillInApiKeyAndAuthorise("my_api_key")) + .get(".modal-ux-content > :nth-child(2)") // deal with api_key_2 + .within(fillInApiKeyAndAuthorise("my_second_api_key")) + .get(".modal-ux-content > :nth-child(1)") // deal with api_key_1 again + .within(clickLogoutAndReauthorise) + .get(".close-modal") // close authorise popup button + .click() + .get("#operations-default-get_4641_2") // expand the route details onClick + .click() + .within(clickTryItOutAndExecute) + .wait("@request") + .its("request") + .then((req) => { + expect(req.headers, "request headers").not.to.have.property("api_key_1") + expect(req.headers, "request headers").to.have.property("api_key_2", "my_second_api_key") + }) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/4838.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/4838.js new file mode 100644 index 0000000..bf96596 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/4838.js @@ -0,0 +1,11 @@ +import repeat from "lodash/repeat" + +describe("#4838: empty request bodies result in endless loading", () => { + it("should render model content changes correctly", () => { + cy + .visit("/?url=/documents/bugs/4838.yaml") + .get("#operations-Some-post_some_route") + .click() + .contains("This should be visible") + }) +}) \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/4865.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/4865.js new file mode 100644 index 0000000..85573ef --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/4865.js @@ -0,0 +1,19 @@ +describe("#4865: multiple invocations + OAS3 plugin", () => { + it("control: should render the OAS3 badge correctly", () => { + // This is a sanity check to make sure the badge is present. + // If this is failing, it's probably not related to #4865. + cy.visit("/?url=/documents/petstore-expanded.openapi.yaml") + .get("#swagger-ui") + .get("pre.version") + .contains("OAS3") + }) + + it("test: should render the OAS3 badge correctly after re-initializing the UI", () => { + cy.visit("/?url=/documents/petstore-expanded.openapi.yaml") + .window() + .then(win => win.onload()) // re-initializes Swagger UI + .get("#swagger-ui") + .get("pre.version") + .contains("OAS3") + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/4867.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/4867.js new file mode 100644 index 0000000..2a4ae3f --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/4867.js @@ -0,0 +1,17 @@ +describe("#4867: callback parameter rendering", () => { + it("should render parameters correctly", () => { + cy + .visit("/?url=/documents/bugs/4867.yaml") + .get("#operations-default-myOp") + .click() + .contains("Callbacks") + .click() + + .get(".callbacks-container .opblock-summary-path") + .should("have.attr", "data-path", "http://$request.query.url") + .click() + + .get(".parameters-container") + .contains("myParam") + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/4943.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/4943.js new file mode 100644 index 0000000..5359754 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/4943.js @@ -0,0 +1,20 @@ +describe("#4943: XML example not rendered correctly with oneOf", () => { + it("should render integer property correctly", () => { + cy + .visit("/?url=/documents/bugs/4943.yaml") + .get("#operations-Test-postTest") + .click() + .get(".microlight") + .contains("0") + }) + it("should render oneOf property correctly", () => { + cy + .visit("/?url=/documents/bugs/4943.yaml") + .get("#operations-Test-postTest") + .click() + .get(".try-out__btn") + .click() + .get(".microlight") + .contains("\n\t") + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5043.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5043.js new file mode 100644 index 0000000..f1fff97 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5043.js @@ -0,0 +1,30 @@ +describe("#5043: path-level $ref path items should inherit global consumes/produces", () => { + it("should render consumes options correctly", () => { + cy + .visit("/?url=/documents/bugs/5043/swagger.yaml") + .get("#operations-pet-findPetsByStatus") + .click() + .get(".try-out__btn") + .click() + .get(".content-type") + .contains("application/json") + .get(".content-type") + .contains("application/xml") + .get(".content-type") + .contains("text/csv") + }) + it("should render produces options correctly", () => { + cy + .visit("/?url=/documents/bugs/5043/swagger.yaml") + .get("#operations-pet-findPetsByStatus") + .click() + .get(".try-out__btn") + .click() + .get(".body-param-content-type select") + .contains("application/json") + .get(".body-param-content-type select") + .contains("application/xml") + .get(".body-param-content-type select") + .contains("text/csv") + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5060.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5060.js new file mode 100644 index 0000000..68ce6bc --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5060.js @@ -0,0 +1,15 @@ +describe("#5060: unwanted smart quotes in rendered Markdown", () => { + it("should not convert regular quotes to smart quotes", () => { + cy + .visit("/?url=/documents/bugs/5060.yaml") + .get("div.description") + .should($el => { + const text = $el.get(0).textContent + expect(text).to.include(`Example of a simple GET request via curl with bearer HTTP Authentication`) + expect(text).to.include(`curl -X GET "https://foobar.com/stuff"`) + expect(text).to.include(`-H "Accept: application/json"`) + expect(text).to.include(`-H "Authorization: Bearer abc123.xyz.789"`) + expect(text.indexOf(`“`)).to.equal(-1) + }) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5070.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5070.js new file mode 100644 index 0000000..a27ed78 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5070.js @@ -0,0 +1,32 @@ +describe("#5070: Required field not highlighted on click of Execute button (second time)", () => { + it("should not clear error class=invalid on input field (Swagger)", () => { + cy + .visit("/?url=/documents/petstore.swagger.yaml") + .get("#operations-pet-getPetById") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Execute without user input + .get(".execute.opblock-control__btn") + .click() + .get(".parameters-col_description input") + .should($el => { + expect($el).to.have.length(1) + const className = $el[0].className + expect(className).to.match(/invalid/i) + }) + // Cancel Try It Out + .get(".cancel") + .click() + // Expand Try It Out (Again) + .get(".try-out__btn") + .click() + .get(".parameters-col_description input") + .should($el => { + expect($el).to.have.length(1) + const className = $el[0].className + expect(className).to.match(/invalid/i) + }) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5072.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5072.js new file mode 100644 index 0000000..267b7c4 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5072.js @@ -0,0 +1,22 @@ +describe("#5072: x-www-form-urlencoded request body input when `properties` is missing", () => { + it("should provide a JSON input for an empty object schema", () => { + cy + .visit("?url=/documents/bugs/5072/empty.yaml") + .get("#operations-default-postObject") + .click() + .get(".try-out__btn") + .click() + .get(`.opblock-section-request-body textarea`) + .should("have.value", "{}") + }) + it("should provide a JSON input for an additionalProperties object schema", () => { + cy + .visit("?url=/documents/bugs/5072/additional.yaml") + .get("#operations-default-postObject") + .click() + .get(".try-out__btn") + .click() + .get(`.opblock-section-request-body textarea`) + .contains(`"additionalProp1": "string"`) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5129.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5129.js new file mode 100644 index 0000000..bd03bc5 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5129.js @@ -0,0 +1,121 @@ +describe("#5129: parameter required + allowEmptyValue interactions", () => { + describe("allowEmptyValue parameter", () => { + const opId = "#operations-default-get_aev" + it("should omit the parameter by default", () => { + cy + .visit("/?url=/documents/bugs/5129.yaml") + .get(opId) + .click() + .get(".btn.try-out__btn") + .click() + .get(".btn.execute") + .click() + .get(".request-url pre") + .should("have.text", "http://localhost:3230/aev") + }) + it("should include a value", () => { + cy + .visit("/?url=/documents/bugs/5129.yaml") + .get(opId) + .click() + .get(".btn.try-out__btn") + .click() + .get(`.parameters-col_description input[type=text]`) + .type("asdf") + .get(".btn.execute") + .click() + .get(".request-url pre") + .should("have.text", "http://localhost:3230/aev?param=asdf") + }) + it("should include an empty value when empty value box is checked", () => { + cy + .visit("/?url=/documents/bugs/5129.yaml") + .get(opId) + .click() + .get(".btn.try-out__btn") + .click() + .get(`.parameters-col_description input[type=checkbox]`) + .check() + .get(".btn.execute") + .click() + .get(".request-url pre") + .should("have.text", "http://localhost:3230/aev?param=") + }) + it("should include a value when empty value box is checked and then input is provided", () => { + cy + .visit("/?url=/documents/bugs/5129.yaml") + .get(opId) + .click() + .get(".btn.try-out__btn") + .click() + .get(`.parameters-col_description input[type=checkbox]`) + .check() + .get(`.parameters-col_description input[type=text]`) + .type("1234") + .get(".btn.execute") + .click() + .get(".request-url pre") + .should("have.text", "http://localhost:3230/aev?param=1234") + }) + }) + describe("allowEmptyValue + required parameter", () => { + const opId = "#operations-default-get_aev_and_required" + it("should refuse to execute by default", () => { + cy + .visit("/?url=/documents/bugs/5129.yaml") + .get(opId) + .click() + .get(".btn.try-out__btn") + .click() + .get(".btn.execute") + .click() + .wait(1000) + .get(".request-url pre") + .should("not.exist") + }) + it("should include a value", () => { + cy + .visit("/?url=/documents/bugs/5129.yaml") + .get(opId) + .click() + .get(".btn.try-out__btn") + .click() + .get(`.parameters-col_description input[type=text]`) + .type("asdf") + .get(".btn.execute") + .click() + .get(".request-url pre") + .should("have.text", "http://localhost:3230/aev/and/required?param=asdf") + }) + it("should include an empty value when empty value box is checked", () => { + cy + .visit("/?url=/documents/bugs/5129.yaml") + .get(opId) + .click() + .get(".btn.try-out__btn") + .click() + .get(`.parameters-col_description input[type=checkbox]`) + .check() + .get(".btn.execute") + .click() + .get(".request-url pre") + .should("have.text", "http://localhost:3230/aev/and/required?param=") + }) + it("should include a value when empty value box is checked and then input is provided", () => { + cy + .visit("/?url=/documents/bugs/5129.yaml") + .get(opId) + .click() + .get(".btn.try-out__btn") + .click() + .get(`.parameters-col_description input[type=checkbox]`) + .check() + .get(`.parameters-col_description input[type=text]`) + .type("1234") + .get(".btn.execute") + .click() + .get(".request-url pre") + .should("have.text", "http://localhost:3230/aev/and/required?param=1234") + }) + }) +}) \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5138.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5138.js new file mode 100644 index 0000000..0f826b1 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5138.js @@ -0,0 +1,10 @@ +describe("#5138: unwanted `url`/`urls` interactions", () => { + it("should stably render the first `urls` entry", () => { + cy + .visit("/pages/5138/") + .get("h2.title") + .contains("USPTO Data Set API") + .wait(3000) + .contains("USPTO Data Set API") + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5164.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5164.js new file mode 100644 index 0000000..e8900d8 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5164.js @@ -0,0 +1,19 @@ +describe("#5164: multipart property initial values", () => { + it("should provide correct initial values for objects and arrays", () => { + const correctObjectValue = JSON.stringify({ + "one": "abc", + "two": 123 + }, null, 2) + + cy + .visit("?url=/documents/bugs/5164.yaml") + .get("#operations-default-post_") + .click() + .get(".try-out__btn") + .click() + .get(`.parameters[data-property-name="first"] textarea`) + .should("have.value", correctObjectValue) + .get(`.parameters[data-property-name="second"] input`) + .should("have.value", "hi") + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5188.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5188.js new file mode 100644 index 0000000..b9c8f18 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5188.js @@ -0,0 +1,18 @@ +describe("#5188: non-string operation summary value", () => { + it("should gracefully handle an object value for an operation summary", () => { + cy + .visit("?url=/documents/bugs/5188.yaml") + .get("#operations-default-objectSummary") + .click() + .get(".opblock-summary-description") + .contains("[object Object]") + }) + it("should gracefully handle a missing value for an operation summary", () => { + cy + .visit("?url=/documents/bugs/5188.yaml") + .get("#operations-default-noSummary") + .click() + // check for response rendering; makes sure the Operation itself rendered + .contains("Invalid input") + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5452.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5452.js new file mode 100644 index 0000000..909b04a --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5452.js @@ -0,0 +1,38 @@ +/** + * @prettier + */ + +describe("#5452: element", () => { + cy.visit("/?url=/documents/bugs/5455.yaml") + .get("#operations-default-post_foo") + .click() + .get(".opblock-section-request-body > .opblock-description-wrapper") + .should("not.have.descendants", "select") + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5458.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5458.js new file mode 100644 index 0000000..3c0ccb6 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5458.js @@ -0,0 +1,22 @@ +// http://github.com/swagger-api/swagger-ui/issues/5458 + +const expectedValue = `{ + "foo": "custom value" +}` + +describe("#5458: Swagger 2.0 `Response.examples` mappings", () => { + it("should render a custom example when a schema is not defined", () => { + cy.visit("/?url=/documents/bugs/5458.yaml") + .get("#operations-default-get_foo1") + .click() + .get(".model-example .highlight-code") + .contains(expectedValue) + }) + it("should render a custom example when a schema is defined", () => { + cy.visit("/?url=/documents/bugs/5458.yaml") + .get("#operations-default-get_foo2") + .click() + .get(".model-example .highlight-code") + .contains(expectedValue) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5660.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5660.js new file mode 100644 index 0000000..3a77acc --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/5660.js @@ -0,0 +1,20 @@ +// http://github.com/swagger-api/swagger-ui/issues/5660 + +const expectedValue = "nullable: true" + +describe("#5660: Nullable object", () => { + it("should render `nullable` marker for object itself", () => { + cy.visit("/?url=/documents/bugs/5660-model.yaml") + .get("#model-SomeObject .model-toggle") + .click() + .get("#model-SomeObject > .model-box") + .contains(expectedValue) + }) + it("should render `nullable` marker for next object in property", () => { + cy.visit("/?url=/documents/bugs/5660-property.yaml") + .get("#model-SomeObject .model-toggle") + .click() + .get("#model-SomeObject > .model-box") + .contains(expectedValue) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6016.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6016.js new file mode 100644 index 0000000..d65142f --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6016.js @@ -0,0 +1,42 @@ +describe("Entries should be valid property name", () => { + it("should render a OAS3.0 definition that uses 'entries' as a 'components' property name", () => { + cy.visit("/?url=/documents/bugs/6016-oas3.yaml") + .get("#operations-tag-default") + .should("exist") + }) + it("should render expanded Operations of OAS3.0 definition that uses 'entries' as a 'components' property name", () => { + cy.visit("/?url=/documents/bugs/6016-oas3.yaml") + .get("#operations-default-test_test__get") + .click() + .get("#operations-default-test_test__get > div .opblock-body") + .should("exist") + + }) + it("should render expanded Models of OAS3.0 definition that uses 'entries' as a 'components' property name", () => { + cy.visit("/?url=/documents/bugs/6016-oas3.yaml") + .get("#model-Testmodel > span .model-box") + .click() + .get("div .model-box") + .should("exist") + }) + it("should render a OAS2.0 definition that uses 'entries' as a 'definitions' property name", () => { + cy.visit("/?url=/documents/bugs/6016-oas2.yaml") + .get("#operations-default-post_pet") + .should("exist") + }) + it("should render expanded Operations of OAS2.0 definition that uses 'entries' as a 'definitions' property name", () => { + cy.visit("/?url=/documents/bugs/6016-oas2.yaml") + .get("#operations-default-post_pet") + .click() + .get("#operations-default-post_pet > div .opblock-body") + .should("exist") + + }) + it("should render expanded Models of OAS2.0 definition that uses 'entries' as a 'defintions' property name", () => { + cy.visit("/?url=/documents/bugs/6016-oas2.yaml") + .get("#model-Pet > span .model-box") + .click() + .get("div .model-box") + .should("exist") + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6158.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6158.js new file mode 100644 index 0000000..098af8b --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6158.js @@ -0,0 +1,54 @@ +describe("#6158: read-only property is not hidden in `POST/PUT`", () => { + describe("POST", () => { + it("should hide property 'id'", () => { + cy.visit("/?url=/documents/bugs/6158.yaml") + .get("#operations-User-post_users") + .click() + .get(".parameters[data-property-name='id']") + .should("not.exist") + .get(".parameters[data-property-name='name']") + .should("exist") + }) + it("should hide property 'id' when trying it out", () => { + cy.visit("/?url=/documents/bugs/6158.yaml") + .get("#operations-User-post_users") + .click() + .get(".try-out__btn") + .click() + .get(".parameters[data-property-name='id']") + .should("not.exist") + .get("input[placeholder='id']") + .should("not.exist") + .get(".parameters[data-property-name='name']") + .should("exist") + .get("input[placeholder='name']") + .should("exist") + }) + }) + describe("PUT", () => { + it("should hide property 'id'", () => { + cy.visit("/?url=/documents/bugs/6158.yaml") + .get("#operations-User-put_users") + .click() + .get(".parameters[data-property-name='id']") + .should("not.exist") + .get(".parameters[data-property-name='name']") + .should("exist") + }) + it("should hide property 'id' when trying it out", () => { + cy.visit("/?url=/documents/bugs/6158.yaml") + .get("#operations-User-put_users") + .click() + .get(".try-out__btn") + .click() + .get(".parameters[data-property-name='id']") + .should("not.exist") + .get("input[placeholder='id']") + .should("not.exist") + .get(".parameters[data-property-name='name']") + .should("exist") + .get("input[placeholder='name']") + .should("exist") + }) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6183.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6183.js new file mode 100644 index 0000000..e169f25 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6183.js @@ -0,0 +1,45 @@ +describe("When trying it out", () => { + it("should render the response headers as comma separated lists", () => { + cy.intercept( + { + method: "GET", + url: /^\/response-headers/, + hostname: "httpbin.org", + }, + { + body: { + "Access-Control-Expose-Headers": "X-Header1, X-Header2, X-Header3, Access-Control-Expose-Headers", + "Content-Length": "289", + "Content-Type": "application/json", + "X-Header1": "value1,value2", + "X-Header2": "value3,value4", + "X-Header3": ["value5", "value6"] + }, + headers: { + "access-control-expose-headers": "X-Header1,X-Header2,X-Header3,Access-Control-Expose-Headers", + "content-type": "application/json", + "x-header1": "value1,value2", + "x-header2": "value3,value4", + "x-header3": "value5,value6", + } + }) + + cy.visit("/?url=/documents/bugs/6183.yaml") + .get("#operations-default-get_response_headers") + .click() + .get(".try-out__btn") + .click() + .get(".btn.execute") + .click() + .wait(1000) + .get(".response-col_description .microlight") + .find(("span:contains(\"value1,value2\")")) + .should("exist") + .get(".response-col_description .microlight") + .find(("span:contains(\"value3,value4\")")) + .should("exist") + .get(".response-col_description .microlight") + .find(("span:contains(\"value5,value6\")")) + .should("exist") + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6276.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6276.js new file mode 100644 index 0000000..e26dbd7 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6276.js @@ -0,0 +1,43 @@ +describe("#6276: Query parameter filter=true is filtering by the value 'true'", () => { + describe("With filter=true", () => { + it("should display the filter bar", () => { + cy.visit("/?url=/documents/petstore.swagger.yaml&filter=true") + .get(".operation-filter-input") + .should("exist") + .should("be.empty") + .get(".opblock-tag[data-tag='pet']") + .should("exist") + .get(".opblock-tag[data-tag='store']") + .should("exist") + .get(".opblock-tag[data-tag='user']") + .should("exist") + }) + }) + describe("With filter=false", () => { + it("should not display the filter bar", () => { + cy.visit("/?url=/documents/petstore.swagger.yaml&filter=false") + .get(".operation-filter-input") + .should("not.exist") + .get(".opblock-tag[data-tag='pet']") + .should("exist") + .get(".opblock-tag[data-tag='store']") + .should("exist") + .get(".opblock-tag[data-tag='user']") + .should("exist") + }) + }) + describe("With filter=pet", () => { + it("should display the filter bar and only show the operations tagged with pet", () => { + cy.visit("/?url=/documents/petstore.swagger.yaml&filter=pet") + .get(".operation-filter-input") + .should("exist") + .should("have.value", "pet") + .get(".opblock-tag[data-tag='pet']") + .should("exist") + .get(".opblock-tag[data-tag='store']") + .should("not.exist") + .get(".opblock-tag[data-tag='user']") + .should("not.exist") + }) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6351.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6351.js new file mode 100644 index 0000000..e1113b4 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6351.js @@ -0,0 +1,11 @@ +// http://github.com/swagger-api/swagger-ui/issues/6351 + +describe("#6351: Server dropdown should change when switched via oas3Actions.setSelectedServer", () => { + it("should show different selected server", () => { + cy.visit("/?url=/documents/bugs/6351.yaml") + .get("select").should("have.value", "http://testserver1.com") + .window() + .then(win => win.ui.oas3Actions.setSelectedServer("http://testserver2.com")) + .get("select").should("have.value", "http://testserver2.com") + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6369.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6369.js new file mode 100644 index 0000000..9182f4d --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6369.js @@ -0,0 +1,44 @@ +describe("#6369: Object model render of field: deprecated", () => { + describe("OAS3", () => { + it("should display row with td:deprecated when set to true", () => { + cy.visit("/?url=/documents/bugs/6369-oas3-display.yaml") + .get("#model-IdentificationProfile > .model-box") + .click() + .get("#model-IdentificationProfile .model-box .model .inner-object table") + .find("tr") + .should("have.length", 3) + .contains("td", "deprecated") + }) + it("should not display row with td:deprecated when set to false", () => { + cy.visit("/?url=/documents/bugs/6369-oas3-no-display.yaml") + .get("#model-IdentificationProfile > .model-box") + .click() + .get("#model-IdentificationProfile .model-box .model .inner-object table") + .find("tr") + .should("have.length", 2) + .get("#model-IdentificationProfile .model-box .model .inner-object table") + .find(("td:contains(\"deprecated\")")) + .should("not.exist") + }) + }) + describe ("OAS2", () => { + it("should display row with td:deprecated when set to true", () => { + cy.visit("/?url=/documents/bugs/6369-oas2-display.yaml") + .get("#model-IdentificationProfile > .model-box") + .click() + .get("#model-IdentificationProfile .model-box .model .inner-object") + .contains("td", "deprecated") + }) + it("should not display row with td:deprecated when set to false", () => { + cy.visit("/?url=/documents/bugs/6369-oas2-no-display.yaml") + .get("#model-IdentificationProfile > .model-box") + .click() + .get("#model-IdentificationProfile .model-box .model .inner-object table") + .find("tr") + .should("have.length", 2) + .get("#model-IdentificationProfile .model-box .model .inner-object table") + .find(("td:contains(\"deprecated\")")) + .should("not.exist") + }) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6442.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6442.js new file mode 100644 index 0000000..206055a --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6442.js @@ -0,0 +1,33 @@ +describe("#6442: 'Examples' keyword definitions can not be rendered as xml", () => { + it("should render response examples accourdingly to content-type xml", () => { + const xmlIndicator = "should be xml" + + cy + .visit("?url=/documents/bugs/6442.yaml") + .get("#operations-default-xmlTest") + .click() + .get(".responses-wrapper") + .within(() => { + cy + .get(".microlight") + .should("include.text", xmlIndicator) + }) + }) +}) + +describe("#6442: 'Example' keyword definitions can not be rendered as xml", () => { + it("should render response examples accourdingly to content-type xml", () => { + const xmlIndicator = "should be xml" + + cy + .visit("?url=/documents/bugs/6442.yaml") + .get("#operations-default-xmlTest2") + .click() + .get(".responses-wrapper") + .within(() => { + cy + .get(".microlight") + .should("include.text", xmlIndicator) + }) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6475.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6475.js new file mode 100644 index 0000000..05fade8 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6475.js @@ -0,0 +1,65 @@ +describe("#6475: 'Examples' keyword definitions can not be rendered as xml", () => { + it("should render requestBody examples preview accourdingly to content-type xml", () => { + const xmlIndicator = "should be xml" + + cy + .visit("?url=/documents/bugs/6475.yaml") + .get("#operations-default-xmlTest_examples") + .click() + .get(".opblock-section-request-body") + .within(() => { + cy + .get(".microlight") + .should("include.text", xmlIndicator) + }) + }) + it("should requestBody examples input accourdingly to content-type xml", () => { + const xmlIndicator = "should be xml" + + cy + .visit("?url=/documents/bugs/6475.yaml") + .get("#operations-default-xmlTest_examples") + .click() + .get(".btn.try-out__btn") + .click() + .get(".opblock-section-request-body") + .within(() => { + cy + .get("textarea") + .contains(xmlIndicator) + }) + }) +}) + +describe("#6475: 'Example' keyword definitions can not be rendered as xml", () => { + it("should render requestBody examples preview accourdingly to content-type xml", () => { + const xmlIndicator = "should be xml" + + cy + .visit("?url=/documents/bugs/6475.yaml") + .get("#operations-default-xmlTest_example") + .click() + .get(".opblock-section-request-body") + .within(() => { + cy + .get(".microlight") + .should("include.text", xmlIndicator) + }) + }) + it("should requestBody examples input accourdingly to content-type xml", () => { + const xmlIndicator = "should be xml" + + cy + .visit("?url=/documents/bugs/6475.yaml") + .get("#operations-default-xmlTest_example") + .click() + .get(".btn.try-out__btn") + .click() + .get(".opblock-section-request-body") + .within(() => { + cy + .get("textarea") + .contains(xmlIndicator) + }) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6540.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6540.js new file mode 100644 index 0000000..62f3948 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6540.js @@ -0,0 +1,11 @@ +describe("#6540: XML example not rendered correctly with oneOf", () => { + it("should render xml like json", () => { + const expected = "\n\n\tstring\n\t0\n\t\n\t\tText\n\t\tThis is a text\n\t\n\t\n\t\timage\n\t\tThis is a image\n\t\n\t\n\t\tText\n\t\tThis is a text\n\t\n\t\n\t\timage\n\t\tThis is a image\n\t\n" + cy + .visit("/?url=/documents/bugs/6540.yaml") + .get("#operations-Test-postTest") + .click() + .get(".microlight") + .contains(expected) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6627.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6627.js new file mode 100644 index 0000000..716b21f --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/6627.js @@ -0,0 +1,11 @@ +describe("#6627: XML example when defined as array", () => { + it("should render xml like json", () => { + const expected = "\n\n\t\n\t\n\t\n\t\n" + cy + .visit("/?url=/documents/bugs/6627.yaml") + .get("#operations-default-get_users") + .click() + .get(".microlight") + .contains(expected) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/7996.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/7996.js new file mode 100644 index 0000000..cb042de --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/7996.js @@ -0,0 +1,14 @@ +describe("#7996: tag description text fills container when externalDocs section absent", () => { + it("should show externalDocs div when externalDocs present in specification", () => { + cy + .visit("?url=/documents/bugs/7996-tags-externalDocs.yaml") + .get("#operations-tag-foo .info__externaldocs") + .should("exist") + }) + it("should have no externalDocs div when externalDocs absent from specification", () => { + cy + .visit("?url=/documents/bugs/7996-tags-externalDocs.yaml") + .get("#operations-tag-bar .info__externaldocs") + .should("not.exist") + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/8217.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/8217.js new file mode 100644 index 0000000..2c61c42 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/8217.js @@ -0,0 +1,24 @@ +describe("#8217: Reset Request Body not using default values", () => { + it("it reset the user edited value and executes with the default value in case of try out reset. (#6517)", () => { + cy + .visit("?url=/documents/bugs/8217.yaml") + .get("#operations-default-addPet") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // replace default sample with bad value + .get(`.parameters[data-property-name="bodyParameter"] input`) + .type("{selectall}not the default value") + // Reset Try It Out + .get(".try-out__btn.reset") + .click() + // Submit using default value + .get(".btn.execute") + .click() + // No required validation error on body parameter + .get(`.parameters[data-property-name="bodyParameter"] input`) + .should("have.value", "default") + .and("not.have.class", "invalid") + }) +}) \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/editor-1868.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/editor-1868.js new file mode 100644 index 0000000..cf78e0e --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/editor-1868.js @@ -0,0 +1,26 @@ +import repeat from "lodash/repeat" + +describe("Editor #1868: model changes break rendering", () => { + it("should render model content changes correctly", () => { + cy + .visit("/?url=/documents/bugs/editor-1868.yaml") + + .get(".model-toggle.collapsed") + .click() + + .get("#model-MyModel") + .contains("a") + + .window() + .then(win => { + // Simulate Swagger Editor updating a model + const content = win.ui.specSelectors.specStr() + win.ui.specActions.updateSpec(content + `\n b:\n type: string`) + }) + + .get("#model-MyModel") + .contains("a") + .get("#model-MyModel") + .contains("b") + }) +}) \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/bugs/swos-63.js b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/swos-63.js new file mode 100644 index 0000000..0e8199f --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/bugs/swos-63.js @@ -0,0 +1,35 @@ +describe("SWOS-63: Schema/Model labeling", () => { + describe("SchemaS/Models section", () => { + it("should render `Schemas` for OpenAPI 3", () => { + cy + .visit("/?url=/documents/petstore-expanded.openapi.yaml") + .get("section.models > h4") + .contains("Schemas") + }) + it("should render `Models` for OpenAPI 2", () => { + cy + .visit("/?url=/documents/petstore.swagger.yaml") + .get("section.models > h4") + .contains("Models") + }) + }) + describe("ModelExample within Operation", () => { + it("should render `Schemas` for OpenAPI 3", () => { + cy + .visit("/?url=/documents/petstore-expanded.openapi.yaml") + .get("#operations-default-findPets") + .click() + .get("button.tablinks[data-name=model]") + .contains("Schema") + }) + it("should render `Models` for OpenAPI 2", () => { + cy + .visit("/?url=/documents/petstore.swagger.yaml") + .get("section.models > h4") + .get("#operations-pet-addPet") + .click() + .get("button.tablinks[data-name=model]") + .contains("Model") + }) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/auth-bearer-flow.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/auth-bearer-flow.js new file mode 100644 index 0000000..49ffbac --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/auth-bearer-flow.js @@ -0,0 +1,51 @@ +describe("OAuth2 Bearer flow", () => { + beforeEach(() => { + const staticResponse = { + statusCode: 200, + body: { + name: "not a random secret for test", + } + } + cy.intercept("GET", "/get*", staticResponse).as( + "tokenRequest" + ) + }) + + it("should be focused on input field with aria-label", () => { + cy.visit( + "/?url=/documents/features/auth-bearer-flow.yaml" + ) + .get("button.authorize") + .click() + cy.focused() + .should("have.attr", "aria-label").and("eq", "auth-bearer-value") + }) + it("should make a header request with proper sample cURL header", () => { + cy.visit( + "/?url=/documents/features/auth-bearer-flow.yaml" + ) + .get("button.authorize") + .click() + .get("section > input") + .type("secret_token") + .get(".auth-btn-wrapper > .authorize") + .click() + .get("button.close-modal") + .click() + // Try-it-out + .get("#operations-default-get_get") + .click() + .get(".btn.try-out__btn") + .click() + .get(".btn.execute") + .click() + cy.wait("@tokenRequest") + .its("request") + .its("headers") + .its("authorization") + .should("equal", "Bearer secret_token") + .get(".curl") + .contains("Authorization: Bearer secret_token") + .should("be.visible") + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/auth-code-flow-pkce-without-secret.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/auth-code-flow-pkce-without-secret.js new file mode 100644 index 0000000..0cce470 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/auth-code-flow-pkce-without-secret.js @@ -0,0 +1,47 @@ +describe("Check client_secret for OAuth2 Authorization Code flow with and without PKCE (#6290)", () => { + it("should display client_secret field for authorization code flow with PKCE", () => { + cy.visit( + "/?url=/documents/features/auth-code-flow-pkce-without-secret.yaml" + ) + .window() + .then(win => { + // set auth config to use PKCE + let authConfigs = win.ui.authSelectors.getConfigs() + win.ui.authActions.configureAuth({ + ...authConfigs, + usePkceWithAuthorizationCodeGrant: true, + }) + }) + .get("button.authorize") + .click() + .get("h4") + .contains("authorizationCode with PKCE") + .get(".flow") + .contains("authorizationCode with PKCE") + .get("#client_secret") + .should("exist") + }) + + it("should display client_secret field for authorization code flow without PKCE", () => { + cy.visit( + "/?url=/documents/features/auth-code-flow-pkce-without-secret.yaml" + ) + .window() + .then(win => { + // set auth config to not use PKCE + let authConfigs = win.ui.authSelectors.getConfigs() + win.ui.authActions.configureAuth({ + ...authConfigs, + usePkceWithAuthorizationCodeGrant: false, + }) + }) + .get("button.authorize") + .click() + .get("h4") + .contains("authorizationCode") + .get(".flow") + .contains("authorizationCode") + .get("#client_secret") + .should("exist") + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/deep-linking.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/deep-linking.js new file mode 100644 index 0000000..9108c4f --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/deep-linking.js @@ -0,0 +1,292 @@ +describe("Deep linking feature", () => { + describe("in Swagger 2", () => { + const swagger2BaseUrl = "/?deepLinking=true&url=/documents/features/deep-linking.swagger.yaml" + + describe("regular Operation", () => { + OperationDeeplinkTestFactory({ + baseUrl: swagger2BaseUrl, + elementToGet: ".opblock-get", + correctElementId: "operations-myTag-myOperation", + correctFragment: "#/myTag/myOperation", + correctHref: "#/myTag/myOperation" + }) + }) + + describe("Operation with whitespace in tag+id", () => { + const elementToGet = ".opblock-post" + const correctFragment = "#/my%20Tag/my%20Operation" + + OperationDeeplinkTestFactory({ + baseUrl: swagger2BaseUrl, + elementToGet, + correctElementId: "operations-my_Tag-my_Operation", + correctFragment, + correctHref: "#/my%20Tag/my%20Operation" + }) + + const legacyFragment = "#/my_Tag/my_Operation" + + it("should expand the operation when reloaded and provided the legacy fragment", () => { + cy.visit(`${swagger2BaseUrl}${legacyFragment}`) + .reload() + .get(`${elementToGet}.is-open`) + .should("exist") + }) + + it.skip("should rewrite to the correct fragment when provided the legacy fragment", () => { + cy.visit(`${swagger2BaseUrl}${legacyFragment}`) + .reload() + .window() + .should("have.deep.property", "location.hash", correctFragment) + }) + }) + + describe("Operation with underscores in tag+id", () => { + OperationDeeplinkTestFactory({ + baseUrl: swagger2BaseUrl, + elementToGet: ".opblock-patch", + correctElementId: "operations-underscore_Tag-underscore_Operation", + correctFragment: "#/underscore_Tag/underscore_Operation", + correctHref: "#/underscore_Tag/underscore_Operation" + }) + }) + + describe("Operation with UTF-16 characters", () => { + OperationDeeplinkTestFactory({ + baseUrl: swagger2BaseUrl, + elementToGet: ".opblock-head", + correctElementId: "operations-шеллы-пошел", + correctFragment: "#/%D1%88%D0%B5%D0%BB%D0%BB%D1%8B/%D0%BF%D0%BE%D1%88%D0%B5%D0%BB", + correctHref: "#/шеллы/пошел" + }) + }) + + describe("Operation with no operationId", () => { + OperationDeeplinkTestFactory({ + baseUrl: swagger2BaseUrl, + elementToGet: ".opblock-put", + correctElementId: "operations-tagTwo-put_noOperationId", + correctFragment: "#/tagTwo/put_noOperationId", + correctHref: "#/tagTwo/put_noOperationId" + }) + }) + + describe("regular Tag", () => { + TagDeeplinkTestFactory({ + isTagCase: true, + baseUrl: swagger2BaseUrl, + elementToGet: `.opblock-tag[data-tag="myTag"][data-is-open="true"]`, + correctElementId: "operations-tag-myTag", + correctFragment: "#/myTag", + correctHref: "#/myTag" + }) + }) + + describe("Tag with whitespace", () => { + TagDeeplinkTestFactory({ + isTagCase: true, + baseUrl: swagger2BaseUrl, + elementToGet: `.opblock-tag[data-tag="my Tag"][data-is-open="true"]`, + correctElementId: "operations-tag-my_Tag", + correctFragment: "#/my%20Tag", + correctHref: "#/my%20Tag" + }) + }) + }) + describe("in OpenAPI 3", () => { + const openAPI3BaseUrl = "/?deepLinking=true&url=/documents/features/deep-linking.openapi.yaml" + + describe("regular Operation", () => { + OperationDeeplinkTestFactory({ + baseUrl: openAPI3BaseUrl, + elementToGet: ".opblock-get", + correctElementId: "operations-myTag-myOperation", + correctFragment: "#/myTag/myOperation", + correctHref: "#/myTag/myOperation" + }) + }) + + describe("Operation with whitespace in tag+id", () => { + const elementToGet = ".opblock-post" + const correctFragment = "#/my%20Tag/my%20Operation" + + OperationDeeplinkTestFactory({ + baseUrl: openAPI3BaseUrl, + elementToGet: ".opblock-post", + correctElementId: "operations-my_Tag-my_Operation", + correctFragment, + correctHref: "#/my%20Tag/my%20Operation" + }) + + const legacyFragment = "#/my_Tag/my_Operation" + + it("should expand the operation when reloaded and provided the legacy fragment", () => { + cy.visit(`${openAPI3BaseUrl}${legacyFragment}`) + .reload() + .get(`${elementToGet}.is-open`) + .should("exist") + }) + + + it.skip("should rewrite to the correct fragment when provided the legacy fragment", () => { + cy.visit(`${openAPI3BaseUrl}${legacyFragment}`) + .reload() + .window() + .should("have.deep.property", "location.hash", correctFragment) + }) + }) + + describe("Operation with underscores in tag+id", () => { + OperationDeeplinkTestFactory({ + baseUrl: openAPI3BaseUrl, + elementToGet: ".opblock-patch", + correctElementId: "operations-underscore_Tag-underscore_Operation", + correctFragment: "#/underscore_Tag/underscore_Operation", + correctHref: "#/underscore_Tag/underscore_Operation" + }) + }) + + describe("Operation with UTF-16 characters", () => { + OperationDeeplinkTestFactory({ + baseUrl: openAPI3BaseUrl, + elementToGet: ".opblock-head", + correctElementId: "operations-шеллы-пошел", + correctFragment: "#/%D1%88%D0%B5%D0%BB%D0%BB%D1%8B/%D0%BF%D0%BE%D1%88%D0%B5%D0%BB", + correctHref: "#/шеллы/пошел" + }) + }) + + describe("Operation with no operationId", () => { + OperationDeeplinkTestFactory({ + baseUrl: openAPI3BaseUrl, + elementToGet: ".opblock-put", + correctElementId: "operations-tagTwo-put_noOperationId", + correctFragment: "#/tagTwo/put_noOperationId", + correctHref: "#/tagTwo/put_noOperationId" + }) + }) + + describe("regular Tag", () => { + TagDeeplinkTestFactory({ + isTagCase: true, + baseUrl: openAPI3BaseUrl, + elementToGet: `.opblock-tag[data-tag="myTag"][data-is-open="true"]`, + correctElementId: "operations-tag-myTag", + correctFragment: "#/myTag", + correctHref: "#/myTag" + }) + }) + + describe("Tag with whitespace", () => { + TagDeeplinkTestFactory({ + isTagCase: true, + baseUrl: openAPI3BaseUrl, + elementToGet: `.opblock-tag[data-tag="my Tag"][data-is-open="true"]`, + correctElementId: "operations-tag-my_Tag", + correctFragment: "#/my%20Tag", + correctHref: "#/my%20Tag" + }) + }) + }) +}) + +function OperationDeeplinkTestFactory({ baseUrl, elementToGet, correctElementId, correctFragment, correctHref }) { + it("should generate a correct element ID", () => { + cy.visit(baseUrl) + .get(elementToGet) + .should("have.id", correctElementId) + }) + + it("should add the correct element fragment to the URL when expanded", () => { + cy.visit(baseUrl) + .get(elementToGet) + .click() + cy.location().should((loc) => { + expect(loc.hash).to.eq(correctFragment) + }) + }) + + it("should provide an anchor link that has the correct fragment as href", () => { + cy.visit(baseUrl) + .get(elementToGet) + .find("a") + .should("have.attr", "href", correctHref) + .click() + cy.location().should((loc) => { + expect(loc.hash).to.eq(correctFragment) + }) + }) + + it("should expand the operation when reloaded", () => { + cy.visit(`${baseUrl}${correctFragment}`) + .get(`${elementToGet}.is-open`) + .should("exist") + }) + + it("should retain the correct fragment when reloaded", () => { + cy.visit(`${baseUrl}${correctFragment}`) + .reload() + .should("exist") + cy.location().should((loc) => { + expect(loc.hash).to.eq(correctFragment) + }) + }) + + it("should expand a tag with docExpansion disabled", () => { + cy.visit(`${baseUrl}&docExpansion=none${correctFragment}`) + .get(`.opblock-tag-section.is-open`) + .should("have.length", 1) + }) + + it("should expand an operation with docExpansion disabled", () => { + cy.visit(`${baseUrl}&docExpansion=none${correctFragment}`) + .get(`.opblock.is-open`) + .should("have.length", 1) + }) +} + +function TagDeeplinkTestFactory({ baseUrl, elementToGet, correctElementId, correctFragment, correctHref, isTagCase = false }) { + it("should generate a correct element ID", () => { + cy.visit(baseUrl) + .get(elementToGet) + .should("have.id", correctElementId) + }) + + it("should add the correct element fragment to the URL when expanded", () => { + cy.visit(baseUrl) + .get(elementToGet) + .click() + .click() // tags need two clicks because they're expanded by default + cy.location().should((loc) => { + expect(loc.hash).to.eq(correctFragment) + }) + }) + + it("should provide an anchor link that has the correct fragment as href", () => { + cy.visit(baseUrl) + .get(elementToGet) + .find("a") + .should("have.attr", "href", correctHref) + }) + + it("should expand the tag when reloaded", () => { + cy.visit(`${baseUrl}${correctFragment}`) + .get(`${elementToGet}[data-is-open="true"]`) + .should("exist") + }) + + it("should retain the correct fragment when reloaded", () => { + cy.visit(`${baseUrl}${correctFragment}`) + .reload() + .should("exist") + cy.location().should((loc) => { + expect(loc.hash).to.eq(correctFragment) + }) + }) + + it("should expand a tag with docExpansion disabled", () => { + cy.visit(`${baseUrl}&docExpansion=none${correctFragment}`) + .get(`.opblock-tag-section.is-open`) + .should("have.length", 1) + }) +} \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/dynamic-default-oauth.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/dynamic-default-oauth.js new file mode 100644 index 0000000..dfb5d90 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/dynamic-default-oauth.js @@ -0,0 +1,26 @@ +describe("dynamic default oauth2RedirectUrl", () => { + it("should compute an oauth2RedirectUrl based on the browser's location at runtime", () => { + cy.visit("/") + .window() + .then(win => win.ui.getConfigs()) + .should("include", { oauth2RedirectUrl: "http://localhost:3230/oauth2-redirect.html" }) + }) + it("should compute an oauth2RedirectUrl based on the browser's location at runtime, including the path", () => { + cy.visit("/pages/5085/") + .window() + .then(win => win.ui.getConfigs()) + .should("include", { oauth2RedirectUrl: "http://localhost:3230/pages/5085/oauth2-redirect.html" }) + }) + it("should compute an oauth2RedirectUrl based on the browser's location at runtime, including the path, without confusing the file name for a folder name", () => { + cy.visit("/pages/5085/index.html") + .window() + .then(win => win.ui.getConfigs()) + .should("include", { oauth2RedirectUrl: "http://localhost:3230/pages/5085/oauth2-redirect.html" }) + }) + it("should compute an oauth2RedirectUrl based on the browser's location at runtime, including the path, even it does not end with a slash", () => { + cy.visit("/pages/5085") + .window() + .then(win => win.ui.getConfigs()) + .should("include", { oauth2RedirectUrl: "http://localhost:3230/pages/5085/oauth2-redirect.html" }) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/external-docs.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/external-docs.js new file mode 100644 index 0000000..e9d1c9a --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/external-docs.js @@ -0,0 +1,108 @@ +describe("External docs feature", () => { + describe("in Swagger 2", () => { + ExternalDocsTest("/?url=/documents/features/external-docs.swagger.yaml") + }) + describe("in OpenAPI 3", () => { + ExternalDocsTest("/?url=/documents/features/external-docs.openapi.yaml") + }) +}) + +function ExternalDocsTest(baseUrl) { + describe("for Root", () => { + it("should display link to external docs with description", () => { + cy.visit(baseUrl) + .get(".info__extdocs") + .should("exist") + .and("contain.text", "Read external docs") + .and("have.attr", "href", "http://swagger.io") + }) + + it("should display link to external docs without description", () => { + cy + .intercept({ + path: /^\/documents\/features\/external-docs\.(swagger|openapi)\.yaml\?intercept$/ + }, (req) => { + delete req.headers["if-none-match"] + delete req.headers["if-modified-since"] + req.continue((res) => { + res.send({body: res.body.replace(" description: Read external docs\n", "")}) + }) + }) + .visit(`${baseUrl}?intercept`) + .get(".info__extdocs") + .should("exist") + .and("contain.text", "http://swagger.io") + .and("have.attr", "href", "http://swagger.io") + }) + }) + + describe("for Tags", () => { + it("should display link to external docs with description", () => { + cy.visit(baseUrl) + .get(`.opblock-tag[data-tag="pet"] .info__externaldocs`) + .should("exist") + .find("a") + .should("contain.text", "Pet Documentation") + .and("have.attr", "href", "http://swagger.io") + }) + + it("should display link to external docs without description", () => { + cy.visit(baseUrl) + .get(`.opblock-tag[data-tag="petWithoutDescription"] .info__externaldocs`) + .should("exist") + .find("a") + .should("contain.text", "http://swagger.io") + .and("have.attr", "href", "http://swagger.io") + }) + }) + + describe("for Schemas", () => { + function SchemaTestFactory(type) { + return () => { + it("should display link with description", () => { + cy.visit(baseUrl) + .get(`.models #model-${type} button`) + .click() + .get(`.models #model-${type} .external-docs a`) + .should("contain.text", `${type} Docs`) + .and("have.attr", "href", "http://swagger.io") + }) + + it("should display link without description", () => { + cy.visit(baseUrl) + .get(`.models #model-${type}WithoutDescription button`) + .click() + .get(`.models #model-${type}WithoutDescription .external-docs a`) + .should("contain.text", "http://swagger.io") + .and("have.attr", "href", "http://swagger.io") + }) + } + } + + describe("Primitive Schema", SchemaTestFactory("Primitive")) + describe("Array Schema", SchemaTestFactory("Array")) + describe("Object Schema", SchemaTestFactory("Object")) + }) + + describe("for Operation", () => { + it("should display link to external docs with description", () => { + cy.visit(baseUrl) + .get("#operations-pet-updatePet button") + .click() + .get("#operations-pet-updatePet .opblock-external-docs-wrapper .opblock-external-docs__description") + .should("contain.text", "More details about putting a pet") + .get("#operations-pet-updatePet .opblock-external-docs-wrapper .opblock-external-docs__link") + .should("have.attr", "href", "http://swagger.io") + }) + + it("should display link to external docs without description", () => { + cy.visit(baseUrl) + .get("#operations-pet-addPet button") + .click() + .get("#operations-pet-addPet .opblock-external-docs-wrapper .opblock-external-docs__description") + .should("not.exist") + .get("#operations-pet-addPet .opblock-external-docs-wrapper .opblock-external-docs__link") + .should("have.attr", "href", "http://swagger.io") + }) + }) +} diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/model-collapse.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/model-collapse.js new file mode 100644 index 0000000..0c6d688 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/model-collapse.js @@ -0,0 +1,50 @@ +describe("Model collapse/expand feature", () => { + describe("in Swagger 2", () => { + const swagger2BaseUrl = "/?deepLinking=true&url=/documents/features/models.swagger.yaml" + const urlFragment = "#/definitions/Pet" + ModelCollapseTest(swagger2BaseUrl, urlFragment) + }) + describe("in OpenAPI 3", () => { + const openAPI3BaseUrl = "/?deepLinking=true&url=/documents/features/models.openapi.yaml" + ModelCollapseTest(openAPI3BaseUrl) + }) +}) + +function ModelCollapseTest(baseUrl, urlFragment) { + it("Models section should be expanded on load", () => { + cy.visit(baseUrl) + .get(".models") + .should("have.class", "is-open") + .get("#model-Pet") + .should("exist") + }) + + it("Models section should collapse and expand when toggled", () => { + cy.visit(baseUrl) + .get(".models h4 .models-control") + .click() + .get(".models") + .should("not.have.class", "is-open") + .get("#model-Order") + .should("not.exist") + .get(".models h4 .models-control") + .click() + .get(".models") + .should("have.class", "is-open") + .get("#model-Order") + .should("exist") + }) + + it("Model should collapse and expand when toggled clicking button", () => { + cy.visit(baseUrl) + .get("#model-User .model-box .model-box-control") + .click() + .get("#model-User .model-box .model .inner-object") + .should("exist") + .get("#model-User .model-box .model-box-control") + .first() + .click() + .get("#model-User .model-box .model .inner-object") + .should("not.exist") + }) +} diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/multiple-examples-core.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/multiple-examples-core.js new file mode 100644 index 0000000..15b4ebe --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/multiple-examples-core.js @@ -0,0 +1,662 @@ +/** + * @prettier + */ + +const { + ParameterPrimitiveTestCases, + RequestBodyPrimitiveTestCases, + ResponsePrimitiveTestCases, +} = require("../../helpers/multiple-examples") +describe("OpenAPI 3.0 Multiple Examples - core features", () => { + describe("/String", () => { + describe("in a parameter", () => { + ParameterPrimitiveTestCases({ + operationDomId: "#operations-default-post_String", + parameterName: "message", + exampleA: { + key: "StringExampleA", + value: "hello world", + }, + exampleB: { + key: "StringExampleB", + value: "The quick brown fox jumps over the lazy dog", + }, + customUserInput: "OpenAPIs.org <3", + }) + }) + describe("in a Request Body", () => { + RequestBodyPrimitiveTestCases({ + operationDomId: "#operations-default-post_String", + exampleA: { + key: "StringExampleA", + value: "hello world", + serializedValue: "hello world", + summary: "Don't just string me along...", + }, + exampleB: { + key: "StringExampleB", + value: "The quick brown fox jumps over the lazy dog", + serializedValue: "The quick brown fox jumps over the lazy dog", + summary: "I'm a pangram!", + }, + customUserInput: "OpenAPIs.org <3", + }) + }) + describe("in a Response", () => { + ResponsePrimitiveTestCases({ + operationDomId: "#operations-default-post_String", + exampleA: { + key: "StringExampleA", + value: "hello world", + summary: "Don't just string me along...", + }, + exampleB: { + key: "StringExampleB", + value: "The quick brown fox jumps over the lazy dog", + summary: "I'm a pangram!", + }, + exampleC: { + key: "StringExampleC", + value: "JavaScript rules", + summary: "A third example, for use in special places...", + }, + }) + }) + }) + describe("/Number", () => { + describe("in a parameter", () => { + ParameterPrimitiveTestCases({ + operationDomId: "#operations-default-post_Number", + parameterName: "message", + exampleA: { + key: "NumberExampleA", + value: "7710263025", + }, + exampleB: { + key: "NumberExampleB", + value: "9007199254740991", + }, + exampleC: { + key: "NumberExampleC", + value: "0", + }, + customUserInput: "9001", + }) + }) + describe("in a Request Body", () => { + RequestBodyPrimitiveTestCases({ + operationDomId: "#operations-default-post_Number", + exampleA: { + key: "NumberExampleA", + value: "7710263025", + summary: "World population", + }, + exampleB: { + key: "NumberExampleB", + value: "9007199254740991", + summary: "Number.MAX_SAFE_INTEGER", + }, + exampleC: { + key: "NumberExampleC", + value: "0", + }, + customUserInput: "1337", + }) + }) + describe("in a Response", () => { + ResponsePrimitiveTestCases({ + operationDomId: "#operations-default-post_Number", + exampleA: { + key: "NumberExampleA", + value: "7710263025", + summary: "World population", + }, + exampleB: { + key: "NumberExampleB", + value: "9007199254740991", + summary: "Number.MAX_SAFE_INTEGER", + }, + exampleC: { + key: "NumberExampleC", + value: "0", + }, + }) + }) + }) + describe("/Boolean", () => { + describe("in a parameter", () => { + it("should render and apply the first example and value by default", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Boolean") + .click() + // Assert on the initial dropdown value + .get("table.parameters .examples-select > select") + .find(":selected") + .should("have.text", "The truth will set you free") + // Assert on the initial JsonSchemaForm value + .get(".parameters-col_description > select") + .should("have.attr", "disabled") + .get(".parameters-col_description > select") + .find(":selected") + .should("have.text", "true") + // Execute + .get(".try-out__btn") + .click() + .get(".execute") + .click() + // Assert on the request URL + .get(".request-url") + .contains(`?message=true`) + }) + it("should render and apply the second value when chosen", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Boolean") + .click() + // Set the dropdown value, then assert on it + .get("table.parameters .examples-select > select") + .select("BooleanExampleB") + .find(":selected") + .should("have.text", "Friends don't lie to friends") + // Set the JsonSchemaForm value, then assert on it + .get(".parameters-col_description > select") + .find(":selected") + .should("have.text", "false") + // Execute + .get(".try-out__btn") + .click() + .get(".execute") + .click() + // Assert on the request URL + .get(".request-url") + .contains(`?message=false`) + }) + it("should track value changes against valid examples", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Boolean") + .click() + .get(".try-out__btn") + .click() + // Set the JsonSchemaForm value, then assert on it + .get(".parameters-col_description > select") + .select("false") + .find(":selected") + .should("have.text", "false") + // Assert on the dropdown value + .get("table.parameters .examples-select > select") + .find(":selected") + .should("have.text", "Friends don't lie to friends") + // Execute + .get(".execute") + .click() + // Assert on the request URL + .get(".request-url") + .contains(`?message=false`) + }) + }) + describe("in a Request Body", () => { + RequestBodyPrimitiveTestCases({ + operationDomId: "#operations-default-post_Boolean", + exampleA: { + key: "BooleanExampleA", + value: "true", + summary: "The truth will set you free", + }, + exampleB: { + key: "BooleanExampleB", + value: "false", + summary: "Friends don't lie to friends", + }, + customUserInput: "tralse", + }) + }) + describe("in a Response", () => { + it("should render and apply the first example and value by default", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Boolean") + .click() + // Assert on the initial dropdown value + .get(".responses-wrapper .examples-select > select") + .find(":selected") + .should("have.text", "The truth will set you free") + // Assert on the example value + .get(".example.microlight") + .should("have.text", "true") + }) + it("should render and apply the second value when chosen", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Boolean") + .click() + // Set the dropdown value, then assert on it + .get(".responses-wrapper .examples-select > select") + .select("BooleanExampleB") + .find(":selected") + .should("have.text", "Friends don't lie to friends") + // Assert on the example value + .get(".example.microlight") + .should("have.text", "false") + }) + }) + }) + describe("/Array", () => { + describe("in a Parameter", () => { + it("should have the first example's array entries by default", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Array") + .click() + .get(".json-schema-form-item > input") + .then((inputs) => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "a", + "b", + "c", + ]) + }) + .get(".parameters-col_description .examples-select > select") + .find(":selected") + .should("have.text", "A lowly array of strings") + }) + it("should switch to the second array's entries via dropdown", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Array") + .click() + .get(".parameters-col_description .examples-select > select") + .select("ArrayExampleB") + .get(".json-schema-form-item > input") + .then((inputs) => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "1", + "2", + "3", + "4", + ]) + }) + .get(".parameters-col_description .examples-select > select") + .find(":selected") + .should("have.text", "A lowly array of numbers") + }) + it("should not allow modification of values in static mode", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Array") + .click() + .get(".parameters-col_description .examples-select > select") + .select("ArrayExampleB") + // Add a new item + .get(".json-schema-form-item > input") + .should("have.attr", "disabled") + }) + it("should allow modification of values in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Array") + .click() + .get(".try-out__btn") + .click() + .get(".parameters-col_description .examples-select > select") + .select("ArrayExampleB") + // Add a new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > input") + .type("{selectall}5") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then((inputs) => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "1", + "2", + "3", + "4", + "5", + ]) + }) + .get(".parameters-col_description .examples-select > select") + .find(":selected") + .should("have.text", "[Modified value]") + }) + + it("should retain a modified value, and support returning to it", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Array") + .click() + .get(".try-out__btn") + .click() + .get(".parameters-col_description .examples-select > select") + .select("ArrayExampleB") + // Add a new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > input") + .type("{selectall}5") + // Reset to an example + .get(".parameters-col_description .examples-select > select") + .select("ArrayExampleB") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then((inputs) => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "1", + "2", + "3", + "4", + ]) + }) + .get(".parameters-col_description .examples-select > select") + .find(":selected") + .should("have.text", "A lowly array of numbers") + // Return to the modified value + .get(".parameters-col_description .examples-select > select") + .select("__MODIFIED__VALUE__") + // Assert that our modified value is back + .get(".json-schema-form-item > input") + .then((inputs) => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "1", + "2", + "3", + "4", + "5", + ]) + }) + .get(".parameters-col_description .examples-select > select") + .find(":selected") + .should("have.text", "[Modified value]") + }) + }) + describe("in a Request Body", () => { + it("should have the first example's array entries by default", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Array") + .click() + // Check HighlightCode value + .get(".opblock-section-request-body .highlight-code") + .should("include.text", JSON.stringify(["a", "b", "c"], null, 2)) + // Check dropdown value + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("have.text", "A lowly array of strings") + // Switch to Try-It-Out + .get(".try-out__btn") + .click() + // Check textarea value + .get(".opblock-section-request-body textarea") + .should("have.value", JSON.stringify(["a", "b", "c"], null, 2)) + // Check dropdown value + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("have.text", "A lowly array of strings") + }) + it("should switch to the second array's entries via dropdown", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Array") + .click() + .get(".opblock-section-request-body .examples-select > select") + .select("ArrayExampleB") + .get(".opblock-section-request-body .highlight-code") + .should("include.text", JSON.stringify([1, 2, 3, 4], null, 2)) + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("have.text", "A lowly array of numbers") + // Switch to Try-It-Out + .get(".try-out__btn") + .click() + // Check textarea value + .get(".opblock-section-request-body textarea") + .should("include.text", JSON.stringify([1, 2, 3, 4], null, 2)) + // Check dropdown value + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("have.text", "A lowly array of numbers") + }) + it("should allow modification of values", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Array") + .click() + // Switch to Try-It-Out + .get(".try-out__btn") + .click() + // Choose the second example + .get(".opblock-section-request-body .examples-select > select") + .select("ArrayExampleB") + // Change the value + .get(".opblock-section-request-body textarea") + .type(`{leftarrow}{leftarrow},{enter} 5`) + // Check that [Modified value] is displayed in dropdown + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("have.text", "[Modified value]") + // Check textarea value + .get(".opblock-section-request-body textarea") + .should("include.text", JSON.stringify([1, 2, 3, 4, 5], null, 2)) + }) + + it("should retain a modified value, and support returning to it", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Array") + .click() + // Switch to Try-It-Out + .get(".try-out__btn") + .click() + // Choose the second example as the example to start with + .get(".opblock-section-request-body .examples-select > select") + .select("ArrayExampleB") + // Change the value + .get(".opblock-section-request-body textarea") + .type(`{leftarrow}{leftarrow},{enter} 5`) + // Check that [Modified value] is displayed in dropdown + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("have.text", "[Modified value]") + // Check textarea value + .get(".opblock-section-request-body textarea") + .should("include.text", JSON.stringify([1, 2, 3, 4, 5], null, 2)) + // Choose the second example + .get(".opblock-section-request-body .examples-select > select") + .select("ArrayExampleB") + // Check that the example is displayed in dropdown + .get(".opblock-section-request-body .examples-select > select") + .find(":selected") + .should("have.text", "A lowly array of numbers") + // Check textarea value + .get(".opblock-section-request-body textarea") + .should("include.text", JSON.stringify([1, 2, 3, 4], null, 2)) + // Switch back to the modified value + .get(".opblock-section-request-body .examples-select > select") + .select("__MODIFIED__VALUE__") + // Check textarea value + .get(".opblock-section-request-body textarea") + .should("include.text", JSON.stringify([1, 2, 3, 4, 5], null, 2)) + }) + }) + describe("in a Response", () => { + it("should render and apply the first example and value by default", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Array") + .click() + // Assert on the initial dropdown value + .get(".responses-wrapper .examples-select > select") + .find(":selected") + .should("have.text", "A lowly array of strings") + // Assert on the example value + .get(".example.microlight") + .should("include.text", JSON.stringify(["a", "b", "c"], null, 2)) + }) + it("should render and apply the second value when chosen", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Array") + .click() + // Set the dropdown value, then assert on it + .get(".responses-wrapper .examples-select > select") + .select("ArrayExampleB") + .find(":selected") + .should("have.text", "A lowly array of numbers") + // Assert on the example value + .get(".example.microlight") + .should("include.text", JSON.stringify([1, 2, 3, 4], null, 2)) + }) + }) + }) + describe("/Object", () => { + describe("in a Parameter", () => { + ParameterPrimitiveTestCases({ + operationDomId: "#operations-default-post_Object", + parameterName: "data", + customUserInput: `{{} "openapiIsCool": true }`, + customExpectedUrlSubstring: "?openapiIsCool=true", + exampleA: { + key: "ObjectExampleA", + serializedValue: + "firstName=Kyle&lastName=Shockey&email=kyle.shockey%40smartbear.com", + value: JSON.stringify( + { + firstName: "Kyle", + lastName: "Shockey", + email: "kyle.shockey@smartbear.com", + }, + null, + 2 + ), + }, + exampleB: { + key: "ObjectExampleB", + serializedValue: + "name=Abbey&type=kitten&color=calico&gender=female&age=11%20weeks", + value: JSON.stringify( + { + name: "Abbey", + type: "kitten", + color: "calico", + gender: "female", + age: "11 weeks", + }, + null, + 2 + ), + }, + }) + }) + describe("in a Request Body", () => { + const exampleA = JSON.stringify( + { + firstName: "Kyle", + lastName: "Shockey", + email: "kyle.shockey@smartbear.com", + }, + null, + 2 + ) + const exampleB = JSON.stringify( + { + name: "Abbey", + type: "kitten", + color: "calico", + gender: "female", + age: "11 weeks", + }, + null, + 2 + ) + RequestBodyPrimitiveTestCases({ + operationDomId: "#operations-default-post_Object", + primaryMediaType: "application/json", + // ↓ not a typo, Cypress requires escaping { when using `cy.type` + customUserInput: `{{} "openapiIsCool": true }`, + customExpectedUrlSubstring: "?openapiIsCool=true", + customUserInputExpectedCurlSubstring: `{ "openapiIsCool": true }`, + exampleA: { + key: "ObjectExampleA", + serializedValue: exampleA, + value: exampleA, + summary: "A user's contact info", + }, + exampleB: { + key: "ObjectExampleB", + serializedValue: exampleB, + value: exampleB, + summary: "A wonderful kitten's info", + }, + }) + it("should display an error message when input validation fails", () => { + cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml") + // Expand the operation + .get("#operations-default-post_Object") + .click() + // Switch to Try-It-Out + .get(".try-out__btn") + .click() + // Set an invalid value + .get(".parameters-container > div > table > tbody > tr > td.parameters-col_description > div:nth-child(2) > textarea") + .type("{{{{ [[[[ <<<< invalid JSON here.") + // Execute the operation + .get(".execute") + .click() + // Verify that an error is shown + .get(".validation-errors") + .contains("Parameter string value must be valid JSON") + }) + }) + describe("in a Response", () => { + ResponsePrimitiveTestCases({ + operationDomId: "#operations-default-post_Object", + exampleA: { + key: "ObjectExampleA", + value: JSON.stringify( + { + firstName: "Kyle", + lastName: "Shockey", + email: "kyle.shockey@smartbear.com", + }, + null, + 2 + ), + summary: "A user's contact info", + }, + exampleB: { + key: "ObjectExampleB", + value: JSON.stringify( + { + name: "Abbey", + type: "kitten", + color: "calico", + gender: "female", + age: "11 weeks", + }, + null, + 2 + ), + summary: "A wonderful kitten's info", + }, + }) + }) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-multiple-media-type.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-multiple-media-type.js new file mode 100644 index 0000000..fc2ac52 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-multiple-media-type.js @@ -0,0 +1,184 @@ +// https://github.com/swagger-api/swagger-ui/issues/6201 +// https://github.com/swagger-api/swagger-ui/issues/6250 +// https://github.com/swagger-api/swagger-ui/issues/6476 + +describe("OpenAPI 3.0 Multiple Media Types with different schemas", () => { + const mediaTypeFormData = "multipart/form-data" + const mediaTypeUrlencoded = "application/x-www-form-urlencoded" + const mediaTypeJson = "application/json" + + beforeEach(() => { + cy.intercept({ + method: "POST", + url: "/post", + hostname: "httpbin.org", + }, {}) + + cy.visit( + "/?url=/documents/features/oas3-multiple-media-type.yaml" + ) + .get("#operations-default-post_post") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // @alias Execute Button + cy.get(".execute.opblock-control__btn").as("executeBtn") + // @alias Media Type Dropdown + cy.get(".opblock-section-request-body .content-type").as("selectMediaType") + }) + + // In all cases, + // - assume that examples are populated based on schema (not explicitly tested) + // - assume validation passes based on successful "execute" + // - expect final cURL command result doees not contain unexpected artifacts from other content-type schemas + describe("multipart/form-data (only 'bar')", () => { + it("should execute multipart/form-data", () => { + cy.get("@selectMediaType") + .select(mediaTypeUrlencoded) + .get("@executeBtn") + .click() + .get("@selectMediaType") + .select(mediaTypeFormData) + .get("@executeBtn") + .click() + // cURL component + .get(".responses-wrapper .curl-command") + .should("exist") + .get(".responses-wrapper .curl-command span") + .should("contains.text", "bar") + .should("not.contains.text", "foo") + }) + it("should execute application/x-www-form-urlencoded THEN execute multipart/form-data", () => { + cy.get("@selectMediaType") + .select(mediaTypeUrlencoded) + .get("@executeBtn") + .click() + .get("@selectMediaType") + .select(mediaTypeFormData) + .get("@executeBtn") + .click() + // cURL component + .get(".responses-wrapper .curl-command") + .should("exist") + .get(".responses-wrapper .curl-command span") + .should("contains.text", "bar") + .should("not.contains.text", "foo") + }) + it("should execute application/json THEN execute multipart/form-data", () => { + cy.get("@selectMediaType") + .select(mediaTypeJson) + .get("@executeBtn") + .click() + .get("@selectMediaType") + .select(mediaTypeFormData) + .get("@executeBtn") + .click() + // cURL component + .get(".responses-wrapper .curl-command") + .should("exist") + .get(".responses-wrapper .curl-command span") + .should("contains.text", "bar") + .should("not.contains.text", "foo") + }) + }) + + describe("application/x-www-form-urlencoded (only 'foo')", () => { + it("should execute application/x-www-form-urlencoded", () => { + cy.get("@selectMediaType") + .select(mediaTypeUrlencoded) + .get("@executeBtn") + .click() + // cURL component + .get(".responses-wrapper .curl-command") + .should("exist") + .get(".responses-wrapper .curl-command span") + .should("contains.text", "foo") + .should("not.contains.text", "bar") + }) + it("should execute multipart/form-data THEN execute application/x-www-form-urlencoded", () => { + cy.get("@selectMediaType") + .select(mediaTypeFormData) + .get("@executeBtn") + .click() + .get("@selectMediaType") + .select(mediaTypeUrlencoded) + .get("@executeBtn") + .click() + // cURL component + .get(".responses-wrapper .curl-command") + .should("exist") + .get(".responses-wrapper .curl-command span") + .should("contains.text", "foo") + .should("not.contains.text", "bar") + }) + it("should execute application/json THEN execute application/x-www-form-urlencoded", () => { + cy.get("@selectMediaType") + .select(mediaTypeJson) + .get("@executeBtn") + .click() + .get("@selectMediaType") + .select(mediaTypeUrlencoded) + .get("@executeBtn") + .click() + // cURL component + .get(".responses-wrapper .curl-command") + .should("exist") + .get(".responses-wrapper .curl-command span") + .should("contains.text", "foo") + .should("not.contains.text", "bar") + }) + }) + + describe("application/json (both 'foo' and 'bar')", () => { + // note: form input for "application/json" is a string; not multiple form fields + it("should execute application/json", () => { + // final curl should have both "bar" and "foo" + cy.get("@selectMediaType") + .select(mediaTypeJson) + .get("@executeBtn") + .click() + .get("@executeBtn") + .click() + // cURL component + .get(".responses-wrapper .curl-command") + .should("exist") + .get(".responses-wrapper .curl-command span") + .should("contains.text", "foo") + .should("contains.text", "bar") + }) + it("should execute multipart/form-data THEN execute application/json", () => { + cy.get("@selectMediaType") + .select(mediaTypeFormData) + .get("@executeBtn") + .click() + .get("@selectMediaType") + .select(mediaTypeJson) + .get("@executeBtn") + .click() + // cURL component + .get(".responses-wrapper .curl-command") + .should("exist") + .get(".responses-wrapper .curl-command span") + .should("contains.text", "foo") + .should("contains.text", "bar") + }) + it("should execute application/x-www-form-urlencoded THEN execute application/json", () => { + // final curl should have both "bar" and "foo" + cy.get("@selectMediaType") + .select(mediaTypeUrlencoded) + .get("@executeBtn") + .click() + .get("@selectMediaType") + .select(mediaTypeJson) + .get("@executeBtn") + .click() + // cURL component + .get(".responses-wrapper .curl-command") + .should("exist") + .get(".responses-wrapper .curl-command span") + .should("contains.text", "foo") + .should("contains.text", "bar") + }) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-multiple-servers.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-multiple-servers.js new file mode 100644 index 0000000..5bb08e1 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-multiple-servers.js @@ -0,0 +1,82 @@ +/** + * @prettier + */ + +describe("OpenAPI 3.0 Multiple Servers", () => { + it("should render and execute for server '/test-url-1'", () => { + cy.visit( + "/?url=/documents/features/oas3-multiple-servers.yaml" + ) + .get(".scheme-container .schemes .servers label > select") + .select("/test-url-1") + .get("#operations-default-get_") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Execute + .get(".execute.opblock-control__btn") + .click() + .get(".responses-wrapper .request-url") + .should("contains.text", "/test-url-1") + }) + it("should render and execute for server '/test-url-2'", () => { + cy.visit( + "/?url=/documents/features/oas3-multiple-servers.yaml" + ) + .get(".scheme-container .schemes .servers label > select") + .select("/test-url-2") + .get("#operations-default-get_") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Execute + .get(".execute.opblock-control__btn") + .click() + .get(".responses-wrapper .request-url") + .should("contains.text", "/test-url-2") + }) + it("should render and execute for server '/test-url-1' after sequence: select '/test-url-2' -> Try-It-Out -> select '/test-url-1'", () => { + cy.visit( + "/?url=/documents/features/oas3-multiple-servers.yaml" + ) + .get(".scheme-container .schemes .servers label > select") + .select("/test-url-2") + .get("#operations-default-get_") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Select a different server + .get(".scheme-container .schemes .servers label > select") + .select("/test-url-1") + // Execute + .get(".execute.opblock-control__btn") + .click() + .get(".responses-wrapper .request-url") + .should("contains.text", "/test-url-1") + }) + it("should render and execute for server '/test-url-switch-1' after changing api definition", () => { + cy.visit( + "/?url=/documents/features/oas3-multiple-servers.yaml" + ) + .get(".scheme-container .schemes .servers label > select") + .select("/test-url-2") + cy.visit( + "/?url=/documents/features/oas3-multiple-servers-switch.yaml" + ) + .get(".scheme-container .schemes .servers label > select") + .select("/test-url-switch-2") + .get("#operations-default-get_") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Execute + .get(".execute.opblock-control__btn") + .click() + .get(".responses-wrapper .request-url") + .should("contains.text", "/test-url-switch-2") + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-request-body-allow-empty-values.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-request-body-allow-empty-values.js new file mode 100644 index 0000000..325a32d --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-request-body-allow-empty-values.js @@ -0,0 +1,145 @@ +/** + * @prettier + */ + +describe("OpenAPI 3.0 Allow Empty Values in Request Body", () => { + it("should not apply or render to required fields", () => { + cy.visit( + "/?url=/documents/features/petstore-only-pet.openapi.yaml" + ) + .get("#operations-pet-addPet") + .click() + .get(".opblock-section .opblock-section-request-body .body-param-content-type > select") + .select("application/x-www-form-urlencoded") + // Expand Try It Out + .get(".try-out__btn") + .click() + // Request Body + .get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(2) > .parameters-col_description .parameter__empty_value_toggle input") + .should("not.exist") + }) + + it("by default, should be checked for all non-required fields", () => { + cy.visit( + "/?url=/documents/features/petstore-only-pet.openapi.yaml" + ) + .get("#operations-pet-addPet") + .click() + .get(".opblock-section .opblock-section-request-body .body-param-content-type > select") + .select("application/x-www-form-urlencoded") + // Expand Try It Out + .get(".try-out__btn") + .click() + // Request Body + .get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(5) > .parameters-col_description .parameter__empty_value_toggle input") + .should("be.checked") + .get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(6) > .parameters-col_description .parameter__empty_value_toggle input") + .should("be.checked") + }) + + it("checkbox should be toggle-able", () => { + cy.visit( + "/?url=/documents/features/petstore-only-pet.openapi.yaml" + ) + .get("#operations-pet-addPet") + .click() + .get(".opblock-section .opblock-section-request-body .body-param-content-type > select") + .select("application/x-www-form-urlencoded") + // Expand Try It Out + .get(".try-out__btn") + .click() + // Request Body + .get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(5) > .parameters-col_description .parameter__empty_value_toggle input") + .should("be.checked") + .uncheck() + .get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(5) > .parameters-col_description .parameter__empty_value_toggle input") + .should("not.be.checked") + }) + + it("on execute, should allow send with all empty values", () => { + cy.visit( + "/?url=/documents/features/petstore-only-pet.openapi.yaml" + ) + .get("#operations-pet-addPet") + .click() + .get(".opblock-section .opblock-section-request-body .body-param-content-type > select") + .select("application/x-www-form-urlencoded") + // Expand Try It Out + .get(".try-out__btn") + .click() + // add item to pass required validation + .get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(4) > .parameters-col_description button") + .click() + // Execute + .get(".execute.opblock-control__btn") + .click() + // cURL component + .get(".responses-wrapper .curl-command") + .should("exist") + .get(".responses-wrapper .curl-command span") + .should("contains.text", "tags=&status=") + }) + + it("on execute, should allow send with some empty values", () => { + cy.visit( + "/?url=/documents/features/petstore-only-pet.openapi.yaml" + ) + .get("#operations-pet-addPet") + .click() + .get(".opblock-section .opblock-section-request-body .body-param-content-type > select") + .select("application/x-www-form-urlencoded") + // Expand Try It Out + .get(".try-out__btn") + .click() + // Request Body + .get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(5) > .parameters-col_description .parameter__empty_value_toggle input") + .uncheck() + // add item to pass required validation + .get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(4) > .parameters-col_description button") + .click() + .get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(4) input") + .clear() + // Execute + .get(".execute.opblock-control__btn") + .click() + // cURL component + .get(".responses-wrapper .curl-command") + .should("exist") + .get(".responses-wrapper .curl-command span") + .should("contains.text", "&status=") + .should("not.contains.text", "tags=") + }) + + it("on execute, should allow send with skip all empty values", () => { + cy.visit( + "/?url=/documents/features/petstore-only-pet.openapi.yaml" + ) + .get("#operations-pet-addPet") + .click() + .get(".opblock-section .opblock-section-request-body .body-param-content-type > select") + .select("application/x-www-form-urlencoded") + // Expand Try It Out + .get(".try-out__btn") + .click() + // Request Body + .get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(5) > .parameters-col_description .parameter__empty_value_toggle input") + .uncheck() + .get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(6) > .parameters-col_description .parameter__empty_value_toggle input") + .uncheck() + // add item to pass required validation + .get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(4) > .parameters-col_description button") + .click() + .get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(4) input") + .clear() + // Execute + .get(".execute.opblock-control__btn") + .click() + // cURL component + .get(".responses-wrapper .curl-command") + .should("exist") + .get(".responses-wrapper .curl-command span") + .should("not.contains.text", "tags=") + .should("not.contains.text", "status=") + }) + +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-request-body-default-views.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-request-body-default-views.js new file mode 100644 index 0000000..d75c86a --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-request-body-default-views.js @@ -0,0 +1,16 @@ +describe("OAS3 default views", () => { + describe("multipart/form-data", () => { + it("should display calculated object string, when no examples provided (#7268)", () => { + cy.visit( + "/?url=/documents/features/request-body/multipart/default-views.yaml", + ) + .get("#operations-default-post_test") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + .get(".parameters-col_description textarea") + .should("contains.text", "\"stuff\": \"string\"") + }) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-request-body-required.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-request-body-required.js new file mode 100644 index 0000000..7fdd705 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-request-body-required.js @@ -0,0 +1,265 @@ +/** + * @prettier + */ + +describe("OpenAPI 3.0 Validation for Required Request Body and Request Body Fields", () => { + describe("Request Body required bug/5181", () => { + beforeEach(() => { + cy.intercept( + { + method: "POST", + url: "/anything/foos", + hostname: "httpbin.org", + }, + {} + ) + }) + + it("on execute, if empty value, SHOULD render class 'invalid' and should NOT render cURL component", () => { + cy.visit("/?url=/documents/bugs/5181.yaml") + .get("#operations-default-post_foos") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // get input + .get( + ".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(1) > .parameters-col_description input" + ) + .should("not.have.class", "invalid") + // Execute + .get(".execute.opblock-control__btn") + .click() + // class "invalid" should now exist (and render red, which we won't check) + .get( + ".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(1) > .parameters-col_description input" + ) + .should("have.class", "invalid") + // cURL component should not exist + .get(".responses-wrapper .curl-command") + .should("not.exist") + }) + it("on execute, if value exists, should NOT render class 'invalid' and SHOULD render cURL component", () => { + cy.visit("/?url=/documents/bugs/5181.yaml") + .get("#operations-default-post_foos") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // get input + .get( + ".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(1) > .parameters-col_description input" + ) + .type("abc") + // Execute + .get(".execute.opblock-control__btn") + .click() + .get( + ".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(1) > .parameters-col_description input" + ) + .should("not.have.class", "invalid") + // cURL component should exist + .get(".responses-wrapper .curl-command") + .should("exist") + }) + }) + + describe("Request Body required fields - application/json", () => { + it("on execute, if empty value, SHOULD render class 'invalid' and should NOT render cURL component", () => { + cy.visit("/?url=/documents/features/petstore-only-pet.openapi.yaml") + .get("#operations-pet-addPet") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // get and clear textarea + .get( + ".opblock-body .opblock-section .opblock-section-request-body .body-param textarea" + ) + .should("not.have.class", "invalid") + .clear() + // Execute + .get(".execute.opblock-control__btn") + .click() + // class "invalid" should now exist (and render red, which we won't check) + .get( + ".opblock-body .opblock-section .opblock-section-request-body .body-param textarea" + ) + .should("have.class", "invalid") + // cURL component should not exist + .get(".responses-wrapper .curl-command") + .should("not.exist") + }) + it("on execute, if value exists, even if just single space, should NOT render class 'invalid' and SHOULD render cURL component that contains the single space", () => { + cy.visit("/?url=/documents/features/petstore-only-pet.openapi.yaml") + .get("#operations-pet-addPet") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // get, clear, then modify textarea + .get( + ".opblock-body .opblock-section .opblock-section-request-body .body-param textarea" + ) + .clear() + .type(" ") + // Execute + .get(".execute.opblock-control__btn") + .click() + .get( + ".opblock-body .opblock-section .opblock-section-request-body .body-param textarea" + ) + .should("not.have.class", "invalid") + // cURL component should exist + .get(".responses-wrapper .curl-command") + .should("exist") + .get(".responses-wrapper .curl-command span") + .should("contains.text", "' '") + }) + }) + + /* + petstore ux notes: + - required field, but if example value exists, will populate the field. So this test will clear the example value. + - "add item" will insert an empty array, and display an input text box. This establishes a value for the field. + */ + describe("Request Body required fields - application/x-www-form-urlencoded", () => { + it("on execute, if empty value, SHOULD render class 'invalid' and should NOT render cURL component", () => { + cy.visit("/?url=/documents/features/petstore-only-pet.openapi.yaml") + .get("#operations-pet-addPet") + .click() + .get( + ".opblock-section .opblock-section-request-body .body-param-content-type > select" + ) + .select("application/x-www-form-urlencoded") + // Expand Try It Out + .get(".try-out__btn") + .click() + // get and clear input populated from example value + .get( + ".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(2) > .parameters-col_description input" + ) + .clear() + // Execute + .get(".execute.opblock-control__btn") + .click() + // class "invalid" should now exist (and render red, which we won't check) + .get( + ".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(2) > .parameters-col_description input" + ) + .should("have.class", "invalid") + // cURL component should not exist + .get(".responses-wrapper .curl-command") + .should("not.exist") + }) + it("on execute, if all values exist, even if array exists but is empty, should NOT render class 'invalid' and SHOULD render cURL component", () => { + cy.visit("/?url=/documents/features/petstore-only-pet.openapi.yaml") + .get("#operations-pet-addPet") + .click() + .get( + ".opblock-section .opblock-section-request-body .body-param-content-type > select" + ) + .select("application/x-www-form-urlencoded") + // Expand Try It Out + .get(".try-out__btn") + .click() + // add item to get input + .get( + ".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(4) > .parameters-col_description button" + ) + .click() + // Execute + .get(".execute.opblock-control__btn") + .click() + .get( + ".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(2) > .parameters-col_description input" + ) + .should("have.value", "doggie") + .should("not.have.class", "invalid") + .get( + ".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(4) > .parameters-col_description input" + ) + .should("have.value", "string") + .should("not.have.class", "invalid") + // cURL component should exist + .get(".responses-wrapper .curl-command") + .should("exist") + }) + }) + + describe("Request Body: switching between Content Types", () => { + it("after application/json 'invalid' error, on switch content type to application/x-www-form-urlencoded, SHOULD be free of errors", () => { + cy.visit("/?url=/documents/features/petstore-only-pet.openapi.yaml") + .get("#operations-pet-addPet") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // get and clear textarea + .get( + ".opblock-body .opblock-section .opblock-section-request-body .body-param textarea" + ) + .should("not.have.class", "invalid") + .clear() + // Execute + .get(".execute.opblock-control__btn") + .click() + .get( + ".opblock-body .opblock-section .opblock-section-request-body .body-param textarea" + ) + .should("have.class", "invalid") + // switch content type + .get( + ".opblock-section .opblock-section-request-body .body-param-content-type > select" + ) + .select("application/x-www-form-urlencoded") + .get( + ".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(2) > .parameters-col_description input" + ) + .should("not.have.class", "invalid") + // add item to get input, just an extra confirmation of non-invalid class + .get( + ".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(4) > .parameters-col_description button" + ) + .click() + .get( + ".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(4) > .parameters-col_description input" + ) + .should("not.have.class", "invalid") + }) + it("after application/x-www-form-urlencoded 'invalid' error, on switch content type to application/json, SHOULD be free of errors", () => { + cy.visit("/?url=/documents/features/petstore-only-pet.openapi.yaml") + .get("#operations-pet-addPet") + .click() + .get( + ".opblock-section .opblock-section-request-body .body-param-content-type > select" + ) + .select("application/x-www-form-urlencoded") + // Expand Try It Out + .get(".try-out__btn") + .click() + // get and clear input + .get( + ".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(2) > .parameters-col_description input" + ) + .clear() + // Execute + .get(".execute.opblock-control__btn") + .click() + // class "invalid" should now exist (and render red, which we won't check) + .get( + ".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(2) > .parameters-col_description input" + ) + .should("have.class", "invalid") + // switch content type + .get( + ".opblock-section .opblock-section-request-body .body-param-content-type > select" + ) + .select("application/json") + .get( + ".opblock-body .opblock-section .opblock-section-request-body .body-param textarea" + ) + .should("not.have.class", "invalid") + }) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-user-edit-request-body-flows.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-user-edit-request-body-flows.js new file mode 100644 index 0000000..2a213a7 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-user-edit-request-body-flows.js @@ -0,0 +1,106 @@ +function getExpandedTryout(page = null, operationId = "#operations-pet-addPet") { + return (page || cy.visit( + "/?url=/documents/features/petstore-only-pet.openapi.yaml", + )) + .get(operationId) + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() +} + +const getRequestBodyFromCY = (page = null, operationId = "#operations-pet-addPet") => + getExpandedTryout(page, operationId) + // get textarea + .get(".opblock-body .opblock-section .opblock-section-request-body .body-param textarea") + +const xmlIndicator = "\n" +const userEditXmlSample = xmlIndicator + + "\n" + + "\t420\n" + + "\tdoggie<3\n" + + "\t\n" + + "\t\t99999999999\n" + + "\t\tDogiiiiiiiieeee\n" + + "\t\n" + + "\t\n" + + "\t\tstring\n" + + "\t\n" + + "\t\n" + + "\t\t\n" + + "\t\t\t0\n" + + "\t\t\tstring\n" + + "\t\t\n" + + "\t\n" + + "\tavailable\n" + + "" + +describe("OAS3 Request Body user edit flows", () => { + // Case: Copy xml from email, paste into request body editor, change media-type to xml + it("it should never overwrite user edited value in case of media-type change", () => { + getRequestBodyFromCY() + // replace default sample with xml edited value + .type(`{selectall}${userEditXmlSample}`) + // change media type to xml, because I have forgotten it + .get(".opblock-section .opblock-section-request-body .body-param-content-type > select") + .select("application/xml") + // Ensure user edited body is not overwritten + .get(".opblock-section-request-body") + .within(() => { + cy + .get("textarea") + .should(($div) => { + expect($div.get(0).textContent).to.eq(userEditXmlSample) + }) + }) + }) + // Case: User really wants to try out the brand new xml content-type + it("it should overwrite default value in case of content-type change, even within request body editor(#6836)", () => { + getRequestBodyFromCY() + // change media type to xml, because I have forgotten it (sry really wanted to try out the new xml content-type) + .get(".opblock-section .opblock-section-request-body .body-param-content-type > select") + .select("application/xml") + // Ensure default value is xml after content type change + .get(".opblock-section-request-body") + .within(() => { + cy + .get("textarea") + .should(($div) => { + expect($div.get(0).textContent).to.contain(xmlIndicator) + }) + }) + }) + // Case: User wants to get the default value back + it("it reset the user edited value and render the default value in case of try out reset. (#6517)", () => { + getRequestBodyFromCY() + // replace default sample with bad value + .type("{selectall}ups that should not have happened") + // Cancel Try It Out + .get(".try-out__btn.reset") + .click() + // Ensure default value is xml after content type change + .get(".opblock-section-request-body") + .within(() => { + cy + .get("textarea") + .should(($div) => { + expect($div.get(0).textContent).to.not.contain("ups that should not have happened") + }) + }) + }) + describe("multipart/", () => { + // Case: User wants to execute operation with media-type multipart/ with a enum property. The user expects the first enum value to be used when executed. + it("should use the first enum value on execute if not changed by user (#6976)", () => { + // test/e2e-cypress/static/documents/features/request-body/multipart/enum.yaml + getExpandedTryout( + cy.visit( + "/?url=/documents/features/request-body/multipart/enum.yaml", + ), "#operations-default-post_test") + .get(".execute") + .click() + // Assert on the request URL + .get(".curl") + .contains("test_enum=A") + }) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-xml.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-xml.js new file mode 100644 index 0000000..e7f2836 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/oas3-xml.js @@ -0,0 +1,70 @@ +describe("XML schema rendering examples", () => { + it("Should render RequestBody example value when schema contains `oneOf` for mediaType `text/xml`", () => { + cy.visit("?url=/documents/features/oas3-xml.json") + .get("#operations-default-post_foo") + .click() + .get("label > .content-type-wrapper > .content-type option:selected") + .should("have.text", "text/xml") + .get(".body-param__example") + .should("contains.text", "") + }) + it("Should render RequestBody example value when schema contains `anyOf` for mediaType `text/xml`", () => { + cy.visit("?url=/documents/features/oas3-xml.json") + .get("#operations-default-post_bar") + .click() + .get("label > .content-type-wrapper > .content-type option:selected") + .should("have.text", "text/xml") + .get(".body-param__example") + .should("contains.text", "") + }) + it("Should render RequestBody example value when schema contains `oneOf` for mediaType `application/xml`", () => { + cy.visit("?url=/documents/features/oas3-xml.json") + .get("#operations-default-post_foobar") + .click() + .get("label > .content-type-wrapper > .content-type option:selected") + .should("have.text", "application/xml") + .get(".body-param__example") + .should("contains.text", "") + }) + it("Should render RequestBody example value when schema contains `anyOf` for mediaType `application/xml`", () => { + cy.visit("?url=/documents/features/oas3-xml.json") + .get("#operations-default-post_barfoo") + .click() + .get("label > .content-type-wrapper > .content-type option:selected") + .should("have.text", "application/xml") + .get(".body-param__example") + .should("contains.text", "") + }) + it("Should render RequestBody example value when switching mediaType to `text/xml` with singular content schema", () => { + cy.visit("?url=/documents/features/oas3-xml.json") + .get("#operations-default-post_barfoo") + .click() + .get("label > .content-type-wrapper > .content-type") + .select("text/xml") + .get(".body-param__example") + .should("contains.text", "") + }) + it("Should render RequestBody example value when switching mediaType to `application/xml` with singular content schema", () => { + cy.visit("?url=/documents/features/oas3-xml.json") + .get("#operations-default-post_foo") + .click() + .get("label > .content-type-wrapper > .content-type") + .select("application/xml") + .get(".body-param__example") + .should("contains.text", "") + }) + it("Should render Response example value for mediaType `application/xml`", () => { + cy.visit("?url=/documents/features/oas3-xml.json") + .get("#operations-default-post_foo") + .click() + .get(".response-col_description > .model-example") + .should("contains.text", "") + }) + it("Should render Response example value for mediaType `text/xml`", () => { + cy.visit("?url=/documents/features/oas3-xml.json") + .get("#operations-default-post_foobar") + .click() + .get(".response-col_description > .model-example") + .should("contains.text", "") + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/oauth2-flows/application.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/oauth2-flows/application.js new file mode 100644 index 0000000..739cfc7 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/oauth2-flows/application.js @@ -0,0 +1,66 @@ +describe("OAuth2 Application flow", function() { + beforeEach(() => { + cy.server() + cy.route({ + url: "**/oauth/*", + method: "POST" + }).as("tokenRequest") + }) + + // https://github.com/swagger-api/swagger-ui/issues/6395 + it("should have first authorization input autofocused", () => { + cy + .visit("/?url=http://localhost:3231/swagger.yaml") + .get(".btn.authorize") + .click() + + cy.focused() + .should("have.id", "oauth_username") + }) + + it("should make an application flow Authorization header request", () => { + cy + .visit("/?url=http://localhost:3231/swagger.yaml") + .get(".btn.authorize") + .click() + + .get("div.modal-ux-content > div:nth-child(2)").within(() => { + cy.get("#client_id") + .clear() + .type("confidentialApplication") + + .get("#client_secret") + .clear() + .type("topSecret") + + .get("button.btn.modal-btn.auth.authorize.button") + .click() + }) + + cy.get("button.close-modal") + .click() + + .get("#operations-default-get_application") + .click() + + .get(".btn.try-out__btn") + .click() + + .get(".btn.execute") + .click() + + cy.get("@tokenRequest") + .its("request") + .its("body") + .should("equal", "grant_type=client_credentials") + + cy.get("@tokenRequest") + .its("request") + .its("headers") + .its("authorization") + .should("equal", "Basic Y29uZmlkZW50aWFsQXBwbGljYXRpb246dG9wU2VjcmV0") + + .get(".live-responses-table .response-col_status") + .contains("200") + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/oauth2-flows/password.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/oauth2-flows/password.js new file mode 100644 index 0000000..872899a --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/oauth2-flows/password.js @@ -0,0 +1,122 @@ +describe("OAuth2 Password flow", function() { + beforeEach(() => { + cy.server() + cy.route({ + url: "**/oauth/*", + method: "POST" + }).as("tokenRequest") + }) + + it("should make a password flow Authorization header request", () => { + cy + .visit("/?url=http://localhost:3231/swagger.yaml") + .get(".btn.authorize") + .click() + + .get("#oauth_username") + .type("swagger") + + .get("#oauth_password") + .type("password") + + .get("#password_type") + .select("basic") + + .get("#client_id") + .clear() + .type("application") + + .get("#client_secret") + .clear() + .type("secret") + + .get("div.modal-ux-content > div:nth-child(1) > div > div:nth-child(2) > div > div.auth-btn-wrapper > button.btn.modal-btn.auth.authorize.button") + .click() + + .get("button.close-modal") + .click() + + .get("#operations-default-get_password") + .click() + + .get(".btn.try-out__btn") + .click() + + .get(".btn.execute") + .click() + + cy.get("@tokenRequest") + .its("request") + .its("body") + .should("include", "grant_type=password") + .should("include", "username=swagger") + .should("include", "password=password") + .should("not.include", "client_id") + .should("not.include", "client_secret") + + cy.get("@tokenRequest") + .its("request") + .its("headers") + .its("authorization") + .should("equal", "Basic YXBwbGljYXRpb246c2VjcmV0") + + .get(".live-responses-table .response-col_status") + .contains("200") + }) + + it("should make a Password flow request-body request", () => { + cy + .visit("/?url=http://localhost:3231/swagger.yaml") + .get(".btn.authorize") + .click() + + .get("#oauth_username") + .type("swagger") + + .get("#oauth_password") + .type("password") + + .get("#password_type") + .select("request-body") + + .get("#client_id") + .clear() + .type("application") + + .get("#client_secret") + .clear() + .type("secret") + + .get("div.modal-ux-content > div:nth-child(1) > div > div:nth-child(2) > div > div.auth-btn-wrapper > button.btn.modal-btn.auth.authorize.button") + .click() + + .get("button.close-modal") + .click() + + .get("#operations-default-get_password") + .click() + + .get(".btn.try-out__btn") + .click() + + .get(".btn.execute") + .click() + + cy.get("@tokenRequest") + .its("request") + .its("body") + .should("include", "grant_type=password") + .should("include", "username=swagger") + .should("include", "password=password") + .should("include", "client_id=application") + .should("include", "client_secret=secret") + + cy.get("@tokenRequest") + .its("request") + .its("headers") + .should("not.have.property", "authorization") + + .get(".live-responses-table .response-col_status") + .contains("200") + }) +}) \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/parameter-array-missing-items.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/parameter-array-missing-items.js new file mode 100644 index 0000000..b6b3b22 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/parameter-array-missing-items.js @@ -0,0 +1,10 @@ +describe("Parameter - Invalid definition with missing array 'items' (#7375)", () => { + it("should render gracefully with fallback to default value", () => { + cy.visit("/?url=/documents/features/parameter-array-missing-items.yaml") + .get("#operations-default-get_example1") + .click() + .get("tbody > tr > .parameters-col_description textarea") + .should("exist") + .should("contains.text", "{}") + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/parameter-order.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/parameter-order.js new file mode 100644 index 0000000..f650929 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/parameter-order.js @@ -0,0 +1,30 @@ +describe("Parameter order", () => { + + it("should be well ordered", () => { + cy.visit("/?url=/documents/features/parameter-order.yaml") + .get("#operations-default-post_test__id__related__relatedId_") + .click() + .get(".parameters > tbody") + .children() + .each((tr, i, arr) => { + const parameterTableRows = Array.from(arr) + expect(tr).to.have.attr("data-param-in") + if (i === 0) { + return + } + const inValue = tr[0].getAttribute("data-param-in") + if (!inValue) { + return + } + const beforeInValue = parameterTableRows[i - 1].getAttribute("data-param-in") + const sameAsBefore = beforeInValue === inValue + if (sameAsBefore) { + expect(parameterTableRows[i - 1]).to.have.attr("data-param-in", inValue) + return + } + for (let x = i + 1; x < parameterTableRows.length; x++) { + expect(parameterTableRows[x]).to.not.have.attr("data-param-in", beforeInValue) + } + }) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/plugins/topbar/linking-to-configured-urls.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/plugins/topbar/linking-to-configured-urls.js new file mode 100644 index 0000000..fc24aac --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/plugins/topbar/linking-to-configured-urls.js @@ -0,0 +1,23 @@ +describe("Loading specs by url.primaryName param", () => { + describe("with no param", () => { + it("should load the default spec", () => { + cy.visit("/pages/multiple-urls/index.html") + .get("span.url") + .contains("/documents/petstore-expanded.openapi.yaml") + }) + }) + describe("with an invalid param", () => { + it("should fall back to the default spec", () => { + cy.visit("/pages/multiple-urls/index.html?urls.primaryName=undefinedUrlName") + .get("span.url") + .contains("/documents/petstore-expanded.openapi.yaml") + }) + }) + describe("with a valid url.primaryName param", () => { + it("should render the requested spec", () => { + cy.visit("/pages/multiple-urls/index.html?urls.primaryName=Petstore Swagger") + .get("span.url") + .contains("/documents/petstore.swagger.yaml") + }) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/request-body-upload-file.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/request-body-upload-file.js new file mode 100644 index 0000000..09b1565 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/request-body-upload-file.js @@ -0,0 +1,114 @@ +/** + * @prettier + */ + +describe("OpenAPI 3.0 Request Body upload file button", () => { + describe("application/octet-stream", () => { + it("should display description with the correct content type", () => { + cy.visit("/?url=/documents/features/request-body-upload-file.yaml") + .get("#operations-default-uploadApplicationOctetStream") + .click() + .get(".opblock-section-request-body .opblock-description-wrapper i") + .should("have.text", "Example values are not available for application/octet-stream media types.") + }) + it("should display a file upload button", () => { + cy.visit("/?url=/documents/features/request-body-upload-file.yaml") + .get("#operations-default-uploadApplicationOctetStream") + .click() + .get(".try-out__btn") + .click() + .get(".opblock-section-request-body .opblock-description-wrapper input") + .should("have.prop", "type", "file") + }) + }) + describe("image/png", () => { + it("should display description with the correct content type", () => { + cy.visit("/?url=/documents/features/request-body-upload-file.yaml") + .get("#operations-default-uploadImagePng") + .click() + .get(".opblock-section-request-body .opblock-description-wrapper i") + .should("have.text", "Example values are not available for image/png media types.") + }) + it("should display a file upload button", () => { + cy.visit("/?url=/documents/features/request-body-upload-file.yaml") + .get("#operations-default-uploadApplicationOctetStream") + .click() + .get(".try-out__btn") + .click() + .get(".opblock-section-request-body .opblock-description-wrapper input") + .should("have.prop", "type", "file") + }) + }) + describe("audio/wav", () => { + it("should display description with the correct content type", () => { + cy.visit("/?url=/documents/features/request-body-upload-file.yaml") + .get("#operations-default-uploadAudioWav") + .click() + .get(".opblock-section-request-body .opblock-description-wrapper i") + .should("have.text", "Example values are not available for audio/wav media types.") + }) + it("should display a file upload button", () => { + cy.visit("/?url=/documents/features/request-body-upload-file.yaml") + .get("#operations-default-uploadApplicationOctetStream") + .click() + .get(".try-out__btn") + .click() + .get(".opblock-section-request-body .opblock-description-wrapper input") + .should("have.prop", "type", "file") + }) + }) + describe("video/mpeg", () => { + it("should display description with the correct content type", () => { + cy.visit("/?url=/documents/features/request-body-upload-file.yaml") + .get("#operations-default-uploadVideoMpeg") + .click() + .get(".opblock-section-request-body .opblock-description-wrapper i") + .should("have.text", "Example values are not available for video/mpeg media types.") + }) + it("should display a file upload button", () => { + cy.visit("/?url=/documents/features/request-body-upload-file.yaml") + .get("#operations-default-uploadApplicationOctetStream") + .click() + .get(".try-out__btn") + .click() + .get(".opblock-section-request-body .opblock-description-wrapper input") + .should("have.prop", "type", "file") + }) + }) + describe("schema format binary", () => { + it("should display description with the correct content type", () => { + cy.visit("/?url=/documents/features/request-body-upload-file.yaml") + .get("#operations-default-uploadSchemaFormatBinary") + .click() + .get(".opblock-section-request-body .opblock-description-wrapper i") + .should("have.text", "Example values are not available for application/x-custom media types.") + }) + it("should display a file upload button", () => { + cy.visit("/?url=/documents/features/request-body-upload-file.yaml") + .get("#operations-default-uploadSchemaFormatBinary") + .click() + .get(".try-out__btn") + .click() + .get(".opblock-section-request-body .opblock-description-wrapper input") + .should("have.prop", "type", "file") + }) + }) + describe("schema format base64", () => { + it("should display description with the correct content type", () => { + cy.visit("/?url=/documents/features/request-body-upload-file.yaml") + .get("#operations-default-uploadSchemaFormatBase64") + .click() + .get(".opblock-section-request-body .opblock-description-wrapper i") + .should("have.text", "Example values are not available for application/x-custom media types.") + }) + it("should display a file upload button", () => { + cy.visit("/?url=/documents/features/request-body-upload-file.yaml") + .get("#operations-default-uploadSchemaFormatBinary") + .click() + .get(".try-out__btn") + .click() + .get(".opblock-section-request-body .opblock-description-wrapper input") + .should("have.prop", "type", "file") + }) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/response-extension.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/response-extension.js new file mode 100644 index 0000000..c1b8553 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/response-extension.js @@ -0,0 +1,49 @@ +describe("Response extension feature", () => { + describe("in Swagger 2", () => { + const swagger2BaseUrl = "/?showExtensions=true&docExpansion=full&url=/documents/features/response-extension.swagger.yaml" + + describe("without x- values", () => { + it("should omit response extensions section", () => { + cy.visit(swagger2BaseUrl) + .get("tr.response[data-code='200'] td.response-col_description div.response__extension") + .should("not.exist") + }) + }) + + describe("with x- values", () => { + it("should list each value", () => { + const page = cy.visit(swagger2BaseUrl) + + page.get("tr.response[data-code='404'] td.response-col_description div.response__extension:nth-child(2)") + .should("have.text", "x-error: true") + + page.get("tr.response[data-code='404'] td.response-col_description div.response__extension:nth-child(3)") + .should("have.text", "x-error-codes: List [ \"NOT_FOUND\" ]") + }) + }) + }) + + describe("in OpenAPI 3", () => { + const openAPI3BaseUrl = "/?showExtensions=true&docExpansion=full&url=/documents/features/response-extension.openapi.yaml" + + describe("without x- values", () => { + it("should omit response extensions section", () => { + cy.visit(openAPI3BaseUrl) + .get("tr.response[data-code='200'] td.response-col_description div.response__extension") + .should("not.exist") + }) + }) + + describe("with x- values", () => { + it("should list each value", () => { + const page = cy.visit(openAPI3BaseUrl) + + page.get("tr.response[data-code='404'] td.response-col_description div.response__extension:nth-child(2)") + .should("have.text", "x-error: true") + + page.get("tr.response[data-code='404'] td.response-col_description div.response__extension:nth-child(3)") + .should("have.text", "x-error-codes: List [ \"NOT_FOUND\" ]") + }) + }) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/schema-form-enum-boolean.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/schema-form-enum-boolean.js new file mode 100644 index 0000000..d05da03 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/schema-form-enum-boolean.js @@ -0,0 +1,129 @@ +/** + * @prettier + */ + +describe("JSON Schema Form: Enum & Boolean in a Parameter", () => { + beforeEach(() => { + cy.visit( + "/?url=/documents/features/schema-form-enum-boolean.yaml" + ) + .get("#operations-pet-findPetsByStatus") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // @alias Execute Button + cy.get(".execute.opblock-control__btn").as("executeBtn") + // @alias Parameters + cy.get(".opblock-section tbody > tr > .parameters-col_description > select") + .eq(0) + .as("enumIsRequired") + cy.get(".opblock-section tbody > tr > .parameters-col_description > select") + .eq(1) + .as("booleanIsOptional") + cy.get(".opblock-section tbody > tr > .parameters-col_description > select") + .eq(2) + .as("booleanIsRequired") + }) + + it("should render @enumIsRequired with list of three options", () => { + cy.get("@enumIsRequired") + .should("contains.text", "available") + .should("contains.text", "pending") + .should("contains.text", "sold") + .should("not.contains.text", "--") + .find("option") + .should("have.length", 3) + }) + it("should render @booleanIsOptional with default empty string value (display '--')", () => { + cy.get("@booleanIsOptional") + .should("have.value", "") + .should("contains.text", "--") + }) + it("should render @booleanIsRequired with default empty string value (display '--')", () => { + cy.get("@booleanIsRequired") + .should("have.value", "") + .should("contains.text", "--") + }) + it("should NOT be able to execute with empty @enumIsRequired and @booleanIsRequired values", () => { + // Execute + cy.get("@executeBtn") + .click() + cy.get("@enumIsRequired") + .should("have.class", "invalid") + cy.get("@booleanIsRequired") + .should("have.class", "invalid") + // cURL component + cy.get(".responses-wrapper .curl-command") + .should("not.exist") + }) + it("should NOT be able to execute with empty @booleanIsRequired value, but valid @enumIsRequired", () => { + cy.get("@enumIsRequired") + .select("pending") + // Execute + cy.get("@executeBtn") + .click() + cy.get("@enumIsRequired") + .should("not.have.class", "invalid") + cy.get("@booleanIsRequired") + .should("have.class", "invalid") + // cURL component + cy.get(".responses-wrapper .curl-command") + .should("not.exist") + }) + it("should NOT be able to execute with empty @enumIsRequired value, but valid @booleanIsRequired", () => { + cy.get("@booleanIsRequired") + .select("false") + // Execute + cy.get("@executeBtn") + .click() + cy.get("@enumIsRequired") + .should("have.class", "invalid") + cy.get("@booleanIsRequired") + .should("not.have.class", "invalid") + // cURL component + cy.get(".responses-wrapper .curl-command") + .should("not.exist") + }) + it("should execute, if @booleanIsOptional value is 'false'", () => { + cy.get("@enumIsRequired") + .select("pending") + cy.get("@booleanIsRequired") + .select("false") + cy.get("@booleanIsOptional") + .select("false") + // Execute + cy.get("@executeBtn") + .click() + cy.get("@enumIsRequired") + .should("not.have.class", "invalid") + cy.get("@booleanIsRequired") + .should("not.have.class", "invalid") + .should("not.contains.text", "expectIsOptional") + // cURL component + cy.get(".responses-wrapper .curl-command") + .should("exist") + .get(".responses-wrapper .curl-command span") + .should("contains.text", "expectIsOptional=false") + }) + it("should execute, but NOT send @booleanIsOptional value if not provided", () => { + cy.get("@enumIsRequired") + .select("pending") + cy.get("@booleanIsRequired") + .select("false") + // Execute + cy.get("@executeBtn") + .click() + cy.get("@enumIsRequired") + .should("not.have.class", "invalid") + cy.get("@booleanIsRequired") + .should("not.have.class", "invalid") + .should("not.contains.text", "expectIsOptional") + // cURL component + cy.get(".responses-wrapper .curl-command") + .should("exist") + .get(".responses-wrapper .curl-command span") + .should("not.contains.text", "expectIsOptional") + }) + +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/schema-form.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/schema-form.js new file mode 100644 index 0000000..b3e2598 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/schema-form.js @@ -0,0 +1,830 @@ +/** + * @prettier + */ + +describe("OpenAPI 3.0 Additional JsonSchemaForm in a Parameter", () => { + describe("incomplete API definition with missing schema key or schema value(s)", () => { + describe("parameter exists as global", () => { + it("should render when parameter exists as global, but missing schema key", () => { + cy.visit("/?url=/documents/features/schema-form-missing-values.yaml") + .get("#operations-default-get_case_one_no_schema") + .click() + .get(".opblock-description .renderedMarkdown p") + .should("have.text", "sf") + }) + it("should render when parameter exists as global, but missing all schema values", () => { + cy.visit("/?url=/documents/features/schema-form-missing-values.yaml") + .get("#operations-default-get_case_one_no_type_or_format") + .click() + .get(".opblock-description .renderedMarkdown p") + .should("have.text", "sf") + }) + it("should render when parameter exists as global, schema key exists, but missing schema values: format", () => { + cy.visit("/?url=/documents/features/schema-form-missing-values.yaml") + .get("#operations-default-get_case_one_format_only_no_type") + .click() + .get(".opblock-description .renderedMarkdown p") + .should("have.text", "sf") + }) + it("should render when parameter exists as global, schema key exists, but missing schema value: type", () => { + cy.visit("/?url=/documents/features/schema-form-missing-values.yaml") + .get("#operations-default-get_case_one_type_only_no_format") + .click() + .get(".opblock-description .renderedMarkdown p") + .should("have.text", "sf") + }) + }) + describe("parameter exists in method", () => { + it("should render when parameter exists in method, but missing schema key", () => { + cy.visit("/?url=/documents/features/schema-form-missing-values.yaml") + .get("#operations-default-get_case_two_no_schema") + .click() + .get(".opblock-description .renderedMarkdown p") + .should("have.text", "sf") + }) + it("should render when parameter exists in method, schema key exists, but missing all schema values", () => { + cy.visit("/?url=/documents/features/schema-form-missing-values.yaml") + .get("#operations-default-get_case_two_no_type_or_format") + .click() + .get(".opblock-description .renderedMarkdown p") + .should("have.text", "sf") + }) + it("should render when parameter exists in method, schema key exists, but missing schema value: format", () => { + cy.visit("/?url=/documents/features/schema-form-missing-values.yaml") + .get("#operations-default-get_case_one_type_only_no_format") + .click() + .get(".opblock-description .renderedMarkdown p") + .should("have.text", "sf") + }) + it("should render when parameter exists in method, schema key exists, but missing schema value: type", () => { + cy.visit("/?url=/documents/features/schema-form-missing-values.yaml") + .get("#operations-default-get_case_one_format_only_no_type") + .click() + .get(".opblock-description .renderedMarkdown p") + .should("have.text", "sf") + }) + }) + }) + describe("/Array", () => { + describe("in a Parameter", () => { + it("should allow modification of values in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Array") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + .get(".parameters-col_description .examples-select > select") + .select("ArrayExampleB") + // Add a new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > input") + .type("{selectall}5") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then((inputs) => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "1", + "2", + "3", + "4", + "5", + ]) + }) + .get(".parameters-col_description .examples-select > select") + .find(":selected") + .should("have.text", "[Modified value]") + }) + it("should allow removal of added value in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Array") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + .get(".parameters-col_description .examples-select > select") + .select("ArrayExampleB") + // Add a new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > input") + .type("{selectall}5") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then((inputs) => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "1", + "2", + "3", + "4", + "5", + ]) + }) + .get(".parameters-col_description .examples-select > select") + .find(":selected") + .should("have.text", "[Modified value]") + // Remove the last item that was just added + .get( + ".json-schema-form-item:last-of-type > .json-schema-form-item-remove" + ) + .click() + .get(".json-schema-form-item > input") + .then((inputs) => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "1", + "2", + "3", + "4", + ]) + }) + }) + it("should allow removal of nth of values in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Array") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + .get(".parameters-col_description .examples-select > select") + .select("ArrayExampleB") + // Add a new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > input") + .type("{selectall}5") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then((inputs) => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "1", + "2", + "3", + "4", + "5", + ]) + }) + .get(".parameters-col_description .examples-select > select") + .find(":selected") + .should("have.text", "[Modified value]") + // Remove the second item in list + .get( + ".json-schema-form-item:nth-child(2) > .json-schema-form-item-remove" + ) + .click() + .get(".json-schema-form-item > input") + .then((inputs) => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "1", + "3", + "4", + "5", + ]) + }) + }) + it("should allow execution of operation in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Array") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + .get(".parameters-col_description .examples-select > select") + .select("ArrayExampleB") + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + }) + it("should add empty item and allow execution of operation in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Array") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + .get(".parameters-col_description .examples-select > select") + .select("ArrayExampleB") + // Add a new item + .get(".json-schema-form-item-add") + .click() + // Execute without prior typing a value + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + }) + }) + }) + describe("Petstore", () => { + describe("/pet/findByStatus", () => { + it("should render the operation, execute with default value", () => { + cy.visit("/?url=/documents/features/schema-form-core.yaml") + .get("#operations-default-findPetsByStatus") + .click() + // Expand operation + .get(".opblock-title span") + .should("have.text", "Parameters") + // Expand Try It Out + .get(".try-out__btn") + .click() + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "available") + }) + it("should render the operation, modify value, and execute with modified value", () => { + cy.visit("/?url=/documents/features/schema-form-core.yaml") + .get("#operations-default-findPetsByStatus") + .click() + // Expand operation + .get(".opblock-title span") + .should("have.text", "Parameters") + // Expand Try It Out + .get(".try-out__btn") + .click() + // Select + .get(".parameters-col_description > select") + .select("pending") + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "pending") + }) + }) + describe("/pet/findByTags", () => { + it("should allow modification of values in Try-It-Out", () => { + cy.visit("/?url=/documents/features/schema-form-core.yaml") + .get("#operations-pet-findPetsByTags") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Add a new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item > input") + .type("{selectall}spotted") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then((inputs) => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "spotted", + ]) + }) + }) + it("should allow removal of added value in Try-It-Out", () => { + cy.visit("/?url=/documents/features/schema-form-core.yaml") + .get("#operations-pet-findPetsByTags") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Add a new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > input") + .type("{selectall}spotted") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then((inputs) => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "spotted", + ]) + }) + // Remove the last item that was just added + .get( + ".json-schema-form-item:last-of-type > .json-schema-form-item-remove" + ) + .click() + .get(".json-schema-form-item > input") + .should("not.exist") + }) + it("should allow removal of nth of values in Try-It-Out", () => { + cy.visit("/?url=/documents/features/schema-form-core.yaml") + .get("#operations-pet-findPetsByTags") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Add a new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > input") + .type("{selectall}spotted") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then((inputs) => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "spotted", + ]) + }) + // Add a 2nd new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > input") + .type("{selectall}large") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then((inputs) => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "spotted", + "large", + ]) + }) + // Add a 3rd new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > input") + .type("{selectall}puppy") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then((inputs) => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "spotted", + "large", + "puppy", + ]) + }) + // Remove the second item in list + .get( + ".json-schema-form-item:nth-child(2) > .json-schema-form-item-remove" + ) + .click() + .get(".json-schema-form-item > input") + .then((inputs) => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "spotted", + "puppy", + ]) + }) + }) + it("should allow execution of operation without modifications in Try-It-Out (debounce)", () => { + cy.visit("/?url=/documents/features/schema-form-core.yaml") + .get("#operations-pet-findPetsByTags") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "findByTags") + }) + it("should add empty item and allow execution of operation in Try-It-Out (debounce)", () => { + cy.visit("/?url=/documents/features/schema-form-core.yaml") + .get("#operations-pet-findPetsByTags") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Add a new item + .get(".json-schema-form-item-add") + .click() + // Execute without prior typing a value + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "findByTags") + }) + it("should add modified item and allow execution of operation in Try-It-Out", () => { + cy.visit("/?url=/documents/features/schema-form-core.yaml") + .get("#operations-pet-findPetsByTags") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Add a new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item > input") + .type("{selectall}spotted") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then((inputs) => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "spotted", + ]) + }) + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "spotted") + }) + it("should add 3 modified items, remove the middle child, and allow execution of operation Try-It-Out", () => { + cy.visit("/?url=/documents/features/schema-form-core.yaml") + .get("#operations-pet-findPetsByTags") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Add a new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > input") + .type("{selectall}spotted") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then((inputs) => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "spotted", + ]) + }) + // Add a 2nd new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > input") + .type("{selectall}large") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then((inputs) => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "spotted", + "large", + ]) + }) + // Add a 3rd new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > input") + .type("{selectall}puppy") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then((inputs) => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "spotted", + "large", + "puppy", + ]) + }) + // Remove the second item in list + .get( + ".json-schema-form-item:nth-child(2) > .json-schema-form-item-remove" + ) + .click() + .get(".json-schema-form-item > input") + .then((inputs) => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "spotted", + "puppy", + ]) + }) + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "tags=spotted&tags=puppy") + .should("not.have.text", "large") + }) + }) + describe("/petOwner/{petOwnerId}", () => { + // This is a (GET) debounce test for schema type: string + it("should render the operation, and allow execute of operation with empty value (debounce)", () => { + cy.visit("/?url=/documents/features/schema-form-core.yaml") + .get("#operations-petOwner-getPetOwnerById") + .click() + // Expand operation + .get(".opblock-title span") + .should("have.text", "Parameters") + // Expand Try It Out + .get(".try-out__btn") + .click() + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "petOwner") + }) + it("should render the operation, and input field, and allow execute of operation", () => { + cy.visit("/?url=/documents/features/schema-form-core.yaml") + .get("#operations-petOwner-getPetOwnerById") + .click() + // Expand operation + .get(".opblock-title span") + .should("have.text", "Parameters") + // Expand Try It Out + .get(".try-out__btn") + .click() + .get(".parameters-col_description > input") + .type("123") + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "petOwner") + .should("contain.text", "123") + }) + }) + describe("/petOwner/listOfServiceTrainer", () => { + it("should allow execution of operation with value=true in Try-It-Out", () => { + cy.visit("/?url=/documents/features/schema-form-core.yaml") + .get("#operations-petOwner-listOfServiceTrainer") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // add 1st item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item > select") + .select("true") + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "tags=true") + }) + it("should allow execution of operation with value=false in Try-It-Out", () => { + cy.visit("/?url=/documents/features/schema-form-core.yaml") + .get("#operations-petOwner-listOfServiceTrainer") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // add 1st item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item > select") + .select("false") + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "tags=false") + }) + it("should allow execution of operation with value=true&value=false in Try-It-Out", () => { + cy.visit("/?url=/documents/features/schema-form-core.yaml") + .get("#operations-petOwner-listOfServiceTrainer") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // add 1st item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item > select") + .select("true") + // add 2nd item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > select") + .select("false") + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "tags=true&tags=false") + }) + it("should allow execution of operation with value=false after removing value=true in Try-It-Out", () => { + cy.visit("/?url=/documents/features/schema-form-core.yaml") + .get("#operations-petOwner-listOfServiceTrainer") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // add 1st item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item > select") + .select("true") + // add 2nd item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > select") + .select("false") + // remove 1st item + .get( + ".json-schema-form-item:nth-child(1) > .json-schema-form-item-remove" + ) + .click() + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "tags=false") + }) + it("should allow execution of operation with value=(empty) in Try-It-Out", () => { + cy.visit("/?url=/documents/features/schema-form-core.yaml") + .get("#operations-petOwner-listOfServiceTrainer") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "listOfServiceTrainer") + }) + }) + describe("/petOwner/findByPreference", () => { + it("should allow execution of operation with value=(empty) in Try-It-Out", () => { + cy.visit("/?url=/documents/features/schema-form-core.yaml") + .get("#operations-petOwner-findByPreference") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "findByPreference") + }) + it("should allow execution of operation with selected value in Try-It-Out", () => { + cy.visit("/?url=/documents/features/schema-form-core.yaml") + .get("#operations-petOwner-findByPreference") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Select + .get(".parameters-col_description > select") + .select("dog") + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "findByPreference") + .should("contain.text", "dog") + }) + it("should allow execution of operation with multiple selected values in Try-It-Out", () => { + cy.visit("/?url=/documents/features/schema-form-core.yaml") + .get("#operations-petOwner-findByPreference") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Select + .get(".parameters-col_description > select") + .select(["dog", "cat"]) + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "findByPreference") + .should("contain.text", "dog") + .should("contain.text", "cat") + }) + }) + describe("/petOwner/createWithList", () => { + it("should allow execution of operation with default text in textArea in Try-It-Out", () => { + cy.visit("/?url=/documents/features/schema-form-core.yaml") + .get("#operations-petOwner-petOwnerCreateWithList") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "createWithList") + }) + it("should allow execution of operation with cleared textArea in Try-It-Out", () => { + cy.visit("/?url=/documents/features/schema-form-core.yaml") + .get("#operations-petOwner-petOwnerCreateWithList") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + .get(".body-param__text") + .clear() + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "createWithList") + }) + it("should allow execution of operation with modified textArea in Try-It-Out", () => { + cy.visit("/?url=/documents/features/schema-form-core.yaml") + .get("#operations-petOwner-petOwnerCreateWithList") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + .get(".body-param__text") + .clear() + // note: adding this much type adds 6+ seconds to test + .type( + `[ + { + "id": 10, + "petId": 201, + "petOwnerFirstName": "John", + }, + { + "id": 11, + "petId": 201, + "petOwnerFirstName": "Jane", + } + ]` + ) + .should("contain.text", "Jane") + .should("contain.text", "201") + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "createWithList") + // Compare Curl + .get(".curl") + .should("contain.text", "Jane") + .should("contain.text", "201") + }) + }) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/spec-parse-to-json.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/spec-parse-to-json.js new file mode 100644 index 0000000..db23e4d --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/spec-parse-to-json.js @@ -0,0 +1,31 @@ +/** + * @prettier + */ + +describe("Parse YAML as YAML@1.2 with json_schema for all JSON-supported types", () => { + it("should have date string even without quotes", () => { + cy.visit("/?url=/documents/features/spec-parse-to-json.yaml") + .get("#operations-default-get_foo") + .click() + // Responses -> example value tab + .get(".language-json > :nth-child(3)") + .should("have.text", "\"without-quotes\"") + .get(".language-json > :nth-child(5)") + .should("have.text", "\"1999-11-31\"") + // Responses -> schema tab + .get(".model-example > .tab > :nth-child(2)") + .click() + .get(":nth-child(1) > :nth-child(2) > .model > :nth-child(1)") + .click() + .get( + ":nth-child(1) > :nth-child(2) > .model > :nth-child(1) > .prop > .property" + ) // first element, without-quotes + .should("have.text", "example: 1999-11-31") + .get(":nth-child(2) > :nth-child(2) > .model > :nth-child(1)") + .click() + .get( + ":nth-child(2) > :nth-child(2) > .model > :nth-child(1) > .prop > .property" + ) // second element, with quotes + .should("have.text", "example: 1999-12-31") + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/syntax-highlighting-json.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/syntax-highlighting-json.js new file mode 100644 index 0000000..236aaba --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/syntax-highlighting-json.js @@ -0,0 +1,40 @@ +/** + * @prettier + */ +describe("Syntax Highlighting for JSON value cases", () => { + // expect span contains entire string sample + // fail case is if the string sample gets broken up into segments + // due react-syntax-highlighter attempting to escape characters into multiple segments + describe("OAS 2", () => { + it("should render full syntax highlighted string in Request (param body) example", () => { + cy.visit("/?url=/documents/features/syntax-highlighting-json-oas2.yaml") + .get("#operations-default-post_setServices") + .click() + .get(".body-param__example > .language-json > :nth-child(10)") + .should("have.text", "\"79daf5b4-aa4b-1452-eae5-42c231477ba7\"") + }) + it("should render full syntax highlighted string in Response example", () => { + cy.visit("/?url=/documents/features/syntax-highlighting-json-oas2.yaml") + .get("#operations-default-post_setServices") + .click() + .get(".example > .language-json > :nth-child(28)") + .should("have.text", "\"5ff06f632bb165394501b05d3a833355\"") + }) + }) + describe("OAS 3", () => { + it("should render full syntax highlighted string in Request example", () => { + cy.visit("/?url=/documents/features/syntax-highlighting-json-oas3.yaml") + .get("#operations-default-post_setServices") + .click() + .get(".body-param__example > .language-json > :nth-child(15)") + .should("have.text", "\"22a124b4-594b-4452-bdf5-fc3ef1477ba7\"") + }) + it("should render full syntax highlighted string in Response example", () => { + cy.visit("/?url=/documents/features/syntax-highlighting-json-oas3.yaml") + .get("#operations-default-post_setServices") + .click() + .get(".example > .language-json > :nth-child(33)") + .should("have.text", "\"f0009babde9dbe204540d79cf754408e\"") + }) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/try-it-out-enabled.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/try-it-out-enabled.js new file mode 100644 index 0000000..bf49e65 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/try-it-out-enabled.js @@ -0,0 +1,22 @@ +describe("Try it out enabled configuration", () => { + it("should enable the try it out section when true", () => { + + cy + .visit("?tryItOutEnabled=true&url=/documents/features/try-it-out-enabled.yaml") + .get("#operations-default-get_") + .click() + .get(".try-out__btn") + .should("have.text","Cancel") + }) + + it("should disable the try it out section when false", () => { + + cy + .visit("?tryItOutEnabled=false&url=/documents/features/try-it-out-enabled.yaml") + .get("#operations-default-get_") + .click() + .get(".try-out__btn") + .should("have.text","Try it out ") + }) + }) + \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/try-it-out-schema-required-override-allowed.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/try-it-out-schema-required-override-allowed.js new file mode 100644 index 0000000..624e6ed --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/try-it-out-schema-required-override-allowed.js @@ -0,0 +1,15 @@ +describe("Try It Out: schema required properties can be overridden", () => { + it("should execute", () => { + cy + .visit("?tryItOutEnabled=true&url=/documents/features/try-it-out-schema-required-override-allowed.yaml") + .get("#operations-default-setDeliveryLocation") + .click() + .get(".body-param__text") + .should("include.value", "testProperty") + .clear() // swagger-ui will auto insert "{}" into textarea + .get(".execute-wrapper > .btn") + .click() + .get(".curl-command") + .should("exist") + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/features/urls.js b/frontend/web/api-doc/test/e2e-cypress/tests/features/urls.js new file mode 100644 index 0000000..d661939 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/features/urls.js @@ -0,0 +1,111 @@ +describe("configuration options: `urls` and `urls.primaryName`", () => { + describe("`urls` only", () => { + it("should render a list of URLs correctly", () => { + cy.visit("/?configUrl=/configs/urls.yaml") + .get("select") + .children() + .should("have.length", 2) + .get("select > option") + .eq(0) + .should("have.text", "One") + .should("have.attr", "value", "/documents/features/urls/1.yaml") + .get("select > option") + .eq(1) + .should("have.text", "Two") + .should("have.attr", "value", "/documents/features/urls/2.yaml") + }) + + it("should render the first URL in the list", () => { + cy.visit("/?configUrl=/configs/urls.yaml") + .get("h2.title") + .should("have.text", "One") + .window() + .then(win => win.ui.specSelectors.url()) + .should("equal", "/documents/features/urls/1.yaml") + }) + }) + + it("should respect a `urls.primaryName`", () => { + cy.visit("/?configUrl=/configs/urls-primary-name.yaml") + .get("select") + .should("have.value", "/documents/features/urls/2.yaml") + .get("h2.title") + .should("have.text", "Two") + .window() + .then(win => win.ui.specSelectors.url()) + .should("equal", "/documents/features/urls/2.yaml") + }) +}) + +describe("urls with server variables", () => { + it("should compute a url and default server variables", () => { + cy.visit("/?configUrl=/configs/urls-server-variables.yaml") + .get("code") + .should("have.text", "https://localhost:3200/oneFirstUrl") + .get("tr > :nth-child(1)") + .should("have.text", "basePath") + .get("input") + .should("have.value", "/oneFirstUrl") + }) + it("should change server variables", () => { + cy.visit("/?configUrl=/configs/urls-server-variables.yaml") + .get("code") + .should("have.text", "https://localhost:3200/oneFirstUrl") + .get("tr > :nth-child(1)") + .should("have.text", "basePath") + .get("input") + .should("have.value", "/oneFirstUrl") + .get(".servers > label > select") + .eq(0) + .select(1) + .get("input") + .should("have.value", "/oneSecondUrl") + }) + it("should select and compute second url", () => { + cy.visit("/?configUrl=/configs/urls-server-variables.yaml") + .get("select > option") + .eq(1) + .should("have.text", "Two") + .get("select") + .eq(0) + .select(1) + .get("code") + .should("have.text", "https://localhost:3200/twoFirstUrl") + .get("input") + .should("have.value", "/twoFirstUrl") + }) + it("should select second url, then toggle back to first url", () => { + cy.visit("/?configUrl=/configs/urls-server-variables.yaml") + .get("select > option") + .get("select") + .eq(0) + .select(1) + .get("input") + .should("have.value", "/twoFirstUrl") + // toggle url back + .get("select") + .eq(0) + .select(0) + .get("code") + .should("have.text", "https://localhost:3200/oneFirstUrl") + .get("input") + .should("have.value", "/oneFirstUrl") + }) + it("should change server variables, then select second url, and maintain server variables index", () => { + cy.visit("/?configUrl=/configs/urls-server-variables.yaml") + .get(".servers > label >select") + .eq(0) + .select(1) + .get("input") + .should("have.value", "/oneSecondUrl") + // change url + .get("select > option") + .get("select") + .eq(0) + .select(1) + .get("input") + .should("have.value", "/twoSecondUrl") + .get("input") + .should("have.value", "/twoSecondUrl") + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/security/anonymous.js b/frontend/web/api-doc/test/e2e-cypress/tests/security/anonymous.js new file mode 100644 index 0000000..312e38a --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/security/anonymous.js @@ -0,0 +1,22 @@ +describe("#6767: Operation should be considered anonymous if its security only includes empty object (this was decided by implementation choice and may change or be extended in the future)", () => { + it("Should consider method anonymous if security contains only empty object", () => { + cy + .visit("/?url=/documents/security/anonymous.yaml") + .get("#operations-default-get_onlyEmpty .authorization__btn") + .should("not.exist") + }) + + it("Should consider method as secured if security contains no empty object", () => { + cy + .visit("/?url=/documents/security/anonymous.yaml") + .get("#operations-default-get_required .authorization__btn") + .should("exist") + }) + + it("Should consider method as secured if security contains empty object but has at least one more security defined", () => { + cy + .visit("/?url=/documents/security/anonymous.yaml") + .get("#operations-default-get_withBoth .authorization__btn") + .should("exist") + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/security/oauth2.js b/frontend/web/api-doc/test/e2e-cypress/tests/security/oauth2.js new file mode 100644 index 0000000..4d01ba3 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/security/oauth2.js @@ -0,0 +1,23 @@ +describe("XSS: OAuth2 authorizationUrl sanitization", () => { + it("should filter out a javascript URL", () => { + cy.visit("/?url=/documents/security/xss-oauth2.yaml") + .window() + .then(win => { + let args = null + const stub = cy.stub(win, "open", (...callArgs) => { + args = callArgs + }).as("windowOpen") + + cy.get(".authorize") + .click() + .get(".modal-btn.authorize") + .click() + .wait(100) + .then(() => { + console.log(args) + expect(args[0]).to.match(/^about:blank/) + }) + + }) + }) +}) diff --git a/frontend/web/api-doc/test/e2e-cypress/tests/security/sequential-import-chaining.js b/frontend/web/api-doc/test/e2e-cypress/tests/security/sequential-import-chaining.js new file mode 100644 index 0000000..90887de --- /dev/null +++ b/frontend/web/api-doc/test/e2e-cypress/tests/security/sequential-import-chaining.js @@ -0,0 +1,58 @@ +describe("Security: CSS Sequential Import Chaining", () => { + describe("in OpenAPI 3.0", () => { + describe("CSS Injection via Markdown", () => { + it("should filter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + diff --git a/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4196.yaml b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4196.yaml new file mode 100644 index 0000000..42bac23 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4196.yaml @@ -0,0 +1,85 @@ +openapi: 3.0.0 +info: + title: Demo API + description: First test + termsOfService: 'http://demo.io/terms-of-service/' + contact: + name: Demo Support + email: support@demo.io + version: 1.0.0 +servers: + - url: '{server}/v1' + variables: + server: + default: https://api.demo.io + description: the API endpoint + +paths: + /session: + put: + summary: Returns a new authentication token + tags: + - session + security: + - basicAuth: [] + responses: + '201': + description: A session object + content: + application/json: + schema: + allOf: + - type: object + properties: + user_id: + type: string + format: uuid + readOnly: true + example: 110e8400-e29b-11d4-a716-446655440000 + - $ref: '#/components/schemas/Session' + '401': + $ref: '#/components/responses/Unauthorized' + +components: + securitySchemes: + basicAuth: + type: http + scheme: basic + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + schemas: + Session: + required: + - token + properties: + token: + type: string + readOnly: true + example: >- + eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.EkN-DOsnsuRjRO6BxXemmJDm3HbxrbRzXglbN2S4sOkopdU4IsDxTI8jO19W_A4K8ZPJijNLis4EZsHeY559a4DFOd50_OqgHGuERTqYZyuhtF39yxJPAjUESwxk2J5k_4zM3O-vtd1Ghyo4IbqKKSy6J9mTniYJPenn5-HIirE + + Error: + required: + - message + properties: + message: + description: a human readable message explaining the error + type: string + reason: + description: a functional key about the error + type: string + responses: + Unauthorized: + description: Not authenticated + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + Default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' diff --git a/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4374.yaml b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4374.yaml new file mode 100644 index 0000000..61ecf81 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4374.yaml @@ -0,0 +1,228 @@ +--- +openapi: 3.0.0 +servers: +- url: http://localhost:3204/ +info: + description: 'This is a sample server Petstore server. You can find out more about + Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For + this sample, you can use the api key `special-key` to test the authorization filters.' + version: 1.0.0 + title: Swagger Petstore + termsOfService: http://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html +tags: +- name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: http://swagger.io +- name: store + description: Access to Petstore orders +- name: user + description: Operations about user + externalDocs: + description: Find out more about our store + url: http://swagger.io +paths: + "/pet/findByStatus": + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + explode: true + schema: + type: array + items: + type: string + enum: + - available + - pending + - sold + default: available + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + "$ref": "#/components/schemas/Pet" + application/json: + schema: + type: array + items: + "$ref": "#/components/schemas/Pet" + '400': + description: Invalid status value + security: + - petstore_auth: + - write:pets + - read:pets +externalDocs: + description: Find out more about Swagger + url: http://swagger.io +components: + schemas: + Order: + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + quantity: + type: integer + format: int32 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + type: boolean + default: false + xml: + name: Order + Category: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Category + User: + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + format: int32 + description: User Status + xml: + name: User + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Tag + Pet: + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + category: + "$ref": "#/components/schemas/Category" + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + "$ref": "#/components/schemas/Tag" + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: Pet + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + requestBodies: + Pet: + content: + application/json: + schema: + "$ref": "#/components/schemas/Pet" + application/xml: + schema: + "$ref": "#/components/schemas/Pet" + description: Pet object that needs to be added to the store + required: true + UserArray: + content: + application/json: + schema: + type: array + items: + "$ref": "#/components/schemas/User" + description: List of user object + required: true + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: http://petstore.swagger.io/oauth/dialog + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header diff --git a/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4409.yaml b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4409.yaml new file mode 100644 index 0000000..fe39884 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4409.yaml @@ -0,0 +1,24 @@ +openapi: 3.0.0 +servers: + - url: 'https://__host__/' +info: + description: Test API + version: '1.0' + title: Test API + contact: + name: Sample Person + email: Sample.Person@adp.com +tags: + - name: Test API with Schema + description: Test API with Schema +paths: + /myApi: + get: + operationId: some guid + responses: + '200': + description: Returns documents + description: Returns documents + tags: + - Test API with Schema + summary: Test API with Schema diff --git a/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4445.yaml b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4445.yaml new file mode 100644 index 0000000..72ec7dd --- /dev/null +++ b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4445.yaml @@ -0,0 +1,64 @@ +openapi: 3.0.0 +info: + title: Callback Example + version: 1.0.0 +paths: + /streams: + post: + description: subscribes a client to receive out-of-band data + parameters: + - name: callbackUrl + in: query + required: true + description: | + the location where data will be sent. Must be network accessible + by the source server + schema: + type: string + format: uri + example: https://tonys-server.com + responses: + '201': + description: subscription successfully created + content: + application/json: + schema: + description: subscription information + required: + - subscriptionId + properties: + subscriptionId: + description: this unique identifier allows management of the subscription + type: string + example: 2531329f-fb09-4ef7-887e-84e648214436 + callbacks: + # the name `onData` is a convenience locator + onData: + $ref: '#/components/callbacks/onData' +components: + callbacks: + onData: + # when data is sent, it will be sent to the `callbackUrl` provided + # when making the subscription PLUS the suffix `/data` + '{$request.query.callbackUrl}/data': + post: + requestBody: + description: subscription payload + content: + application/json: + schema: + properties: + timestamp: + type: string + format: date-time + userData: + type: string + responses: + '202': + description: | + Your server implementation should return this HTTP status code + if the data was received successfully + '204': + description: | + Your server should return this HTTP status code if no longer interested + in further updates diff --git a/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4485/book.yaml b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4485/book.yaml new file mode 100644 index 0000000..5d42f06 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4485/book.yaml @@ -0,0 +1,15 @@ +--- +post: + description: Book + operationId: buy + summary: Buy a book + tags: + - Book + consumes: + - application/json + - application/xml + parameters: + - name: requestBody + in: body + description: Buy a Book + required: true diff --git a/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4485/main.yaml b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4485/main.yaml new file mode 100644 index 0000000..af774a7 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4485/main.yaml @@ -0,0 +1,5 @@ +--- +swagger: '2.0' +paths: + "/v1/book": + "$ref": "./book.yaml" diff --git a/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4536.yaml b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4536.yaml new file mode 100644 index 0000000..26303cd --- /dev/null +++ b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4536.yaml @@ -0,0 +1,9 @@ +swagger: '2.0' +info: + version: 0.0.0 + title: test +paths: {} + +definitions: + ModelName: + title: TitleName diff --git a/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4587.yaml b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4587.yaml new file mode 100644 index 0000000..f8e0821 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4587.yaml @@ -0,0 +1,126 @@ +--- +swagger: '2.0' +info: + version: 0.0.1 + title: DDErl REST interface + description: RESTful access to IMEM DB and DDErl +schemes: +- http +- https +securityDefinitions: + basicAuth: + type: basic + description: HTTP Basic Authentication Username:Password +basePath: "/dderlrest/0.0.1" +paths: + "/sql/": + get: + tags: + - sql + security: + - basicAuth: [] + summary: execute sql + operationId: execSql + description: Prepare and execute SQL statements + parameters: + - name: x-irest-conn + in: header + required: false + description: ErlImem connection identifier + type: string + produces: + - application/json + responses: + '200': + description: OK + schema: + type: object + headers: + x-irest-conn: + description: ErlImem connection identifier + type: string + '403': + description: Malformed/Invalid + schema: + "$ref": "#/definitions/ErrorResponse" +definitions: + ErrorResponse: + readOnly: true + type: object + required: + - errorCode + - errorMessage + - errorDetails + properties: + errorCode: + description: Error Code + type: number + example: 1400 + errorMessage: + description: Error Message + type: string + example: malformed + errorDetails: + description: Error Details + type: string + example: mandatory properties missing or bad type + ViewParams: + readOnly: true + type: array + items: + type: object + required: + - typ + - value + - name + properties: + name: + description: Name + type: string + example: ":atom_user" + value: + description: Value + type: string + example: system + typ: + description: Datatype + type: string + enum: + - atom + - binary + - raw + - blob + - rowid + - binstr + - clob + - nclob + - varchar2 + - nvarchar2 + - char + - nchar + - boolean + - datetime + - decimal + - float + - fun + - integer + - ipaddr + - list + - map + - number + - pid + - ref + - string + - term + - binterm + - timestamp + - tuple + - userid + example: atom + dir: + description: Direction + type: string + enum: + - in + - out + default: in diff --git a/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4756.yaml b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4756.yaml new file mode 100644 index 0000000..b1c142e --- /dev/null +++ b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/4756.yaml @@ -0,0 +1,51 @@ +swagger: '2.0' +info: + title: test doc + description: 'test doc ' + license: + name: Copyright @2018 + version: 1.0.0 +host: www.example.com +basePath: /test/API +tags: + - name: devices + description: devices +schemes: + - http + - https +consumes: + - application/x-www-form-urlencoded +produces: + - application/json +paths: + /zero: + post: + parameters: + - in: query + name: one + type: string + required: true + enum: + - 0 + - 1 + responses: + 200: + description: 'response' + examples: + application/json: {"error":0} + /one: + post: + parameters: + - in: query + name: one + type: string + required: true + default: 1 + enum: + - 0 + - 1 + responses: + 200: + description: 'response' + examples: + application/json: {"error":0} \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/frozen-array-input.yaml b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/frozen-array-input.yaml new file mode 100644 index 0000000..ede8040 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/bugs/frozen-array-input.yaml @@ -0,0 +1,35 @@ +openapi: '3.0.0' +info: + description: >- + Repro API + title: Repro API + version: '1.0' +paths: + /test: + get: + summary: Test get + parameters: + - name: fields + in: query + required: false + explode: false + schema: + type: array + items: + type: string + style: form + example: + - friends + - family + responses: + 200: + description: Success! + content: + application/json: + schema: + type: object + properties: + id: + type: integer + name: + type: string \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-selenium/static/test-specs/callbacks.openapi.yaml b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/callbacks.openapi.yaml new file mode 100644 index 0000000..7950d41 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/callbacks.openapi.yaml @@ -0,0 +1,41 @@ +openapi: 3.0.0 +servers: +- url: http://petstore.swagger.io/v2 +info: + version: 1.0.0 + title: Swagger Petstore + termsOfService: http://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html +paths: + "/pet": + post: + tags: + - pet + summary: Add a new pet to the store + description: '' + operationId: addPet + parameters: [] + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + callbacks: + myWebhook: + '$request.body#/url': + post: + requestBody: + description: Callback payload + content: + 'application/json': + schema: + type: string + responses: + '200': + description: webhook successfully processed and no retries will be performed diff --git a/frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/allow-empty-value.openapi.yaml b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/allow-empty-value.openapi.yaml new file mode 100644 index 0000000..7190097 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/allow-empty-value.openapi.yaml @@ -0,0 +1,64 @@ +openapi: "3.0.0" + +paths: + /regularParams: + get: + parameters: + - name: int + in: query + schema: + type: integer + - name: str + in: query + schema: + type: string + - name: num + in: query + schema: + type: number + - name: bool + in: query + schema: + type: boolean + - name: arr + in: query + schema: + type: array + items: + type: string + responses: + 200: + description: ok + /emptyValueParams: + get: + parameters: + - name: int + in: query + schema: + type: integer + allowEmptyValue: true + - name: str + in: query + schema: + type: string + allowEmptyValue: true + - name: num + in: query + schema: + type: number + allowEmptyValue: true + - name: bool + in: query + schema: + type: boolean + allowEmptyValue: true + - name: arr + in: query + schema: + type: array + items: + type: string + allowEmptyValue: true + responses: + 200: + description: ok \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/allow-empty-value.swagger.yaml b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/allow-empty-value.swagger.yaml new file mode 100644 index 0000000..9075987 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/allow-empty-value.swagger.yaml @@ -0,0 +1,57 @@ +swagger: "2.0" + +consumes: +- application/json +- multipart/form-data +paths: + /regularParams: + get: + parameters: + - name: int + in: query + type: integer + - name: str + in: query + type: string + - name: num + in: query + type: number + - name: bool + in: query + type: boolean + - name: arr + in: query + type: array + items: + type: string + responses: + 200: + description: ok + /emptyValueParams: + get: + parameters: + - name: int + in: query + type: integer + allowEmptyValue: true + - name: str + in: query + type: string + allowEmptyValue: true + - name: num + in: query + type: number + allowEmptyValue: true + - name: bool + in: query + type: boolean + allowEmptyValue: true + - name: arr + in: query + type: array + items: + type: string + allowEmptyValue: true + responses: + 200: + description: ok \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/example.openapi.yaml b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/example.openapi.yaml new file mode 100644 index 0000000..c016aca --- /dev/null +++ b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/example.openapi.yaml @@ -0,0 +1,156 @@ +openapi: 3.0.0 +servers: + - url: http://example.com/v1 + description: Production server version 1 + - url: http://staging-api.example.com + description: Staging server +info: + description: | + This is an API documentation of example. + version: "0.1.0" + title: Example + termsOfService: 'http://www.example.com/terms/' + contact: + email: developer@example.com + license: + name: Proprietary license + url: 'http://www.example.com/license/' +tags: + - name: agent + description: Access to example +paths: + /agents/{agentId}: + put: + tags: + - agent + summary: Edit agent + operationId: editAgent + parameters: + - in: path + name: agentId + schema: + type: integer + example: 12345 + required: true + description: Numeric ID of the paper agent to edit + requestBody: + required: true + content: + application/json_media-type-level: + schema: + type: object + properties: + code: + type: string + name: + type: string + example: + code: AE1 + name: Andrew + application/json_schema-level: + schema: + type: object + properties: + code: + type: string + name: + type: string + example: + code: AE1 + name: Andrew + application/json_property-level: + schema: + type: object + properties: + code: + type: string + example: AE1 + name: + type: string + example: Andrew + responses: + '200': + description: media type-level example + content: + application/json: + schema: + type: object + properties: + code: + type: integer + format: int64 + payload: + type: array + items: + type: object + properties: + id: + type: integer + format: int64 + readOnly: true + code: + type: string + name: + type: string + example: + code: 200 + payload: + - id: 1 + code: AE2 + name: Yono + '201': + description: schema-level example + content: + application/json: + schema: + type: object + properties: + code: + type: integer + format: int64 + payload: + type: array + items: + type: object + properties: + id: + type: integer + format: int64 + readOnly: true + code: + type: string + name: + type: string + example: + code: 201 + payload: + - id: 1 + code: AE2 + name: Yono + '202': + description: property-level example + content: + application/json: + schema: + type: object + properties: + code: + type: integer + format: int64 + example: 202 + payload: + type: array + items: + type: object + properties: + id: + type: integer + format: int64 + readOnly: true + example: 1 + code: + type: string + example: AE2 + name: + type: string + example: Yono \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/example.swagger.yaml b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/example.swagger.yaml new file mode 100644 index 0000000..1237599 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/example.swagger.yaml @@ -0,0 +1,127 @@ +swagger: "2.0" +info: + description: | + This is an API documentation of example. + version: "0.1.0" + title: Example + termsOfService: 'http://www.example.com/terms/' + contact: + email: developer@example.com + license: + name: Proprietary license + url: 'http://www.example.com/license/' +paths: + /one: + put: + parameters: + - in: query + name: NotValidParam + type: integer + example: 12345 + required: true + description: This example **should not** have an effect + - in: query + name: ValidParam + type: integer + x-example: 12345 + description: This example **should** have an effect + - in: body + name: body + description: property-level examples + schema: + type: object + properties: + one: + type: string + example: hello! + two: + type: object + properties: + uno: + type: string + example: wow! + dos: + type: string + example: hey there! + - in: body + name: body2 + description: root schema-level example + schema: + type: object + properties: + foo: + type: string + bar: + type: integer + example: + foo: hey + bar: 123 + - in: body + name: body3 + description: nested schema-level example + schema: + type: object + properties: + one: + type: object + properties: + uno: + type: string + dos: + type: string + example: + uno: woohoo! + dos: amazing! + responses: + '201': + description: schema-level example + schema: + type: object + properties: + code: + type: integer + format: int64 + payload: + type: array + items: + type: object + properties: + id: + type: integer + format: int64 + readOnly: true + code: + type: string + name: + type: string + example: + code: 201 + payload: + - id: 1 + code: AE2 + name: Yono + '202': + description: property-level example + schema: + type: object + properties: + code: + type: integer + format: int64 + example: 202 + payload: + type: array + items: + type: object + properties: + id: + type: integer + format: int64 + readOnly: true + example: 1 + code: + type: string + example: AE2 + name: + type: string + example: Yono \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/parameter-enum-rendering.openapi.yaml b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/parameter-enum-rendering.openapi.yaml new file mode 100644 index 0000000..444cf19 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/parameter-enum-rendering.openapi.yaml @@ -0,0 +1,21 @@ +openapi: 3.0.0 +info: + title: test + version: 0.0.0 +paths: + /report: + get: + parameters: + - in: query + name: rel_date + required: true + schema: + type: string + default: today + enum: + - today + - yesterday + - lastweek + responses: + '200': + description: OK diff --git a/frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/parameter-enum-rendering.swagger.yaml b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/parameter-enum-rendering.swagger.yaml new file mode 100644 index 0000000..b81c88d --- /dev/null +++ b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/features/parameter-enum-rendering.swagger.yaml @@ -0,0 +1,20 @@ +swagger: '2.0' +info: + title: test + version: 0.0.0 +paths: + /report: + get: + parameters: + - in: query + name: rel_date + required: true + type: string + default: today + enum: + - today + - yesterday + - lastweek + responses: + 200: + description: OK diff --git a/frontend/web/api-doc/test/e2e-selenium/static/test-specs/petstore.json b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/petstore.json new file mode 100644 index 0000000..2d16188 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/petstore.json @@ -0,0 +1,1043 @@ +{ + "swagger":"2.0", + "info":{ + "description":"This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", + "version":"1.0.0", + "title":"Swagger Petstore", + "termsOfService":"http://swagger.io/terms/", + "contact":{ + "email":"apiteam@swagger.io" + }, + "license":{ + "name":"Apache 2.0", + "url":"http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "host":"localhost:3204", + "basePath":"/", + "tags":[ + { + "name":"pet", + "description":"Everything about your Pets", + "externalDocs":{ + "description":"Find out more", + "url":"http://swagger.io" + } + }, + { + "name":"store", + "description":"Access to Petstore orders" + }, + { + "name":"user", + "description":"Operations about user", + "externalDocs":{ + "description":"Find out more about our store", + "url":"http://swagger.io" + } + } + ], + "schemes":[ + "http" + ], + "paths":{ + "/pet":{ + "post":{ + "tags":[ + "pet" + ], + "summary":"Add a new pet to the store", + "description":"", + "operationId":"addPet", + "consumes":[ + "application/json", + "application/xml" + ], + "produces":[ + "application/xml", + "application/json" + ], + "parameters":[ + { + "in":"body", + "name":"body", + "description":"Pet object that needs to be added to the store", + "required":true, + "schema":{ + "$ref":"#/definitions/Pet" + } + } + ], + "responses":{ + "405":{ + "description":"Invalid input" + } + }, + "security":[ + { + "petstore_auth":[ + "write:pets", + "read:pets" + ] + } + ] + }, + "put":{ + "tags":[ + "pet" + ], + "summary":"Update an existing pet", + "description":"", + "operationId":"updatePet", + "consumes":[ + "application/json", + "application/xml" + ], + "produces":[ + "application/xml", + "application/json" + ], + "parameters":[ + { + "in":"body", + "name":"body", + "description":"Pet object that needs to be added to the store", + "required":true, + "schema":{ + "$ref":"#/definitions/Pet" + } + } + ], + "responses":{ + "400":{ + "description":"Invalid ID supplied" + }, + "404":{ + "description":"Pet not found" + }, + "405":{ + "description":"Validation exception" + } + }, + "security":[ + { + "petstore_auth":[ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByStatus":{ + "get":{ + "tags":[ + "pet" + ], + "summary":"Finds Pets by status", + "description":"Multiple status values can be provided with comma separated strings", + "operationId":"findPetsByStatus", + "produces":[ + "application/xml", + "application/json" + ], + "parameters":[ + { + "name":"status", + "in":"query", + "description":"Status values that need to be considered for filter", + "required":true, + "type":"array", + "items":{ + "type":"string", + "enum":[ + "available", + "pending", + "sold" + ], + "default":"available" + }, + "collectionFormat":"multi" + } + ], + "responses":{ + "200":{ + "description":"successful operation", + "schema":{ + "type":"array", + "items":{ + "$ref":"#/definitions/Pet" + } + } + }, + "400":{ + "description":"Invalid status value" + } + }, + "security":[ + { + "petstore_auth":[ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByTags":{ + "get":{ + "tags":[ + "pet" + ], + "summary":"Finds Pets by tags", + "description":"Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + "operationId":"findPetsByTags", + "produces":[ + "application/xml", + "application/json" + ], + "parameters":[ + { + "name":"tags", + "in":"query", + "description":"Tags to filter by", + "required":true, + "type":"array", + "items":{ + "type":"string" + }, + "collectionFormat":"multi" + } + ], + "responses":{ + "200":{ + "description":"successful operation", + "schema":{ + "type":"array", + "items":{ + "$ref":"#/definitions/Pet" + } + } + }, + "400":{ + "description":"Invalid tag value" + } + }, + "security":[ + { + "petstore_auth":[ + "write:pets", + "read:pets" + ] + } + ], + "deprecated":true + } + }, + "/pet/{petId}":{ + "get":{ + "tags":[ + "pet" + ], + "summary":"Find pet by ID", + "description":"Returns a single pet", + "operationId":"getPetById", + "produces":[ + "application/xml", + "application/json" + ], + "parameters":[ + { + "name":"petId", + "in":"path", + "description":"ID of pet to return", + "required":true, + "type":"integer", + "format":"int64" + } + ], + "responses":{ + "200":{ + "description":"successful operation", + "schema":{ + "$ref":"#/definitions/Pet" + } + }, + "400":{ + "description":"Invalid ID supplied" + }, + "404":{ + "description":"Pet not found" + } + }, + "security":[ + { + "api_key":[ + + ] + } + ] + }, + "post":{ + "tags":[ + "pet" + ], + "summary":"Updates a pet in the store with form data", + "description":"", + "operationId":"updatePetWithForm", + "consumes":[ + "application/x-www-form-urlencoded" + ], + "produces":[ + "application/xml", + "application/json" + ], + "parameters":[ + { + "name":"petId", + "in":"path", + "description":"ID of pet that needs to be updated", + "required":true, + "type":"integer", + "format":"int64" + }, + { + "name":"name", + "in":"formData", + "description":"Updated name of the pet", + "required":false, + "type":"string" + }, + { + "name":"status", + "in":"formData", + "description":"Updated status of the pet", + "required":false, + "type":"string" + } + ], + "responses":{ + "405":{ + "description":"Invalid input" + } + }, + "security":[ + { + "petstore_auth":[ + "write:pets", + "read:pets" + ] + } + ] + }, + "delete":{ + "tags":[ + "pet" + ], + "summary":"Deletes a pet", + "description":"", + "operationId":"deletePet", + "produces":[ + "application/xml", + "application/json" + ], + "parameters":[ + { + "name":"api_key", + "in":"header", + "required":false, + "type":"string" + }, + { + "name":"petId", + "in":"path", + "description":"Pet id to delete", + "required":true, + "type":"integer", + "format":"int64" + } + ], + "responses":{ + "400":{ + "description":"Invalid ID supplied" + }, + "404":{ + "description":"Pet not found" + } + }, + "security":[ + { + "petstore_auth":[ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/{petId}/uploadImage":{ + "post":{ + "tags":[ + "pet" + ], + "summary":"uploads an image", + "description":"", + "operationId":"uploadFile", + "consumes":[ + "multipart/form-data" + ], + "produces":[ + "application/json" + ], + "parameters":[ + { + "name":"petId", + "in":"path", + "description":"ID of pet to update", + "required":true, + "type":"integer", + "format":"int64" + }, + { + "name":"additionalMetadata", + "in":"formData", + "description":"Additional data to pass to server", + "required":false, + "type":"string" + }, + { + "name":"file", + "in":"formData", + "description":"file to upload", + "required":false, + "type":"file" + } + ], + "responses":{ + "200":{ + "description":"successful operation", + "schema":{ + "$ref":"#/definitions/ApiResponse" + } + } + }, + "security":[ + { + "petstore_auth":[ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/store/inventory":{ + "get":{ + "tags":[ + "store" + ], + "summary":"Returns pet inventories by status", + "description":"Returns a map of status codes to quantities", + "operationId":"getInventory", + "produces":[ + "application/json" + ], + "parameters":[ + + ], + "responses":{ + "200":{ + "description":"successful operation", + "schema":{ + "type":"object", + "additionalProperties":{ + "type":"integer", + "format":"int32" + } + } + } + }, + "security":[ + { + "api_key":[ + + ] + } + ] + } + }, + "/store/order":{ + "post":{ + "tags":[ + "store" + ], + "summary":"Place an order for a pet", + "description":"", + "operationId":"placeOrder", + "produces":[ + "application/xml", + "application/json" + ], + "parameters":[ + { + "in":"body", + "name":"body", + "description":"order placed for purchasing the pet", + "required":true, + "schema":{ + "$ref":"#/definitions/Order" + } + } + ], + "responses":{ + "200":{ + "description":"successful operation", + "schema":{ + "$ref":"#/definitions/Order" + } + }, + "400":{ + "description":"Invalid Order" + } + } + } + }, + "/store/order/{orderId}":{ + "get":{ + "tags":[ + "store" + ], + "summary":"Find purchase order by ID", + "description":"For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", + "operationId":"getOrderById", + "produces":[ + "application/xml", + "application/json" + ], + "parameters":[ + { + "name":"orderId", + "in":"path", + "description":"ID of pet that needs to be fetched", + "required":true, + "type":"integer", + "maximum":10.0, + "minimum":1.0, + "format":"int64" + } + ], + "responses":{ + "200":{ + "description":"successful operation", + "schema":{ + "$ref":"#/definitions/Order" + } + }, + "400":{ + "description":"Invalid ID supplied" + }, + "404":{ + "description":"Order not found" + } + } + }, + "delete":{ + "tags":[ + "store" + ], + "summary":"Delete purchase order by ID", + "description":"For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", + "operationId":"deleteOrder", + "produces":[ + "application/xml", + "application/json" + ], + "parameters":[ + { + "name":"orderId", + "in":"path", + "description":"ID of the order that needs to be deleted", + "required":true, + "type":"integer", + "minimum":1.0, + "format":"int64" + } + ], + "responses":{ + "400":{ + "description":"Invalid ID supplied" + }, + "404":{ + "description":"Order not found" + } + } + } + }, + "/user":{ + "post":{ + "tags":[ + "user" + ], + "summary":"Create user", + "description":"This can only be done by the logged in user.", + "operationId":"createUser", + "produces":[ + "application/xml", + "application/json" + ], + "parameters":[ + { + "in":"body", + "name":"body", + "description":"Created user object", + "required":true, + "schema":{ + "$ref":"#/definitions/User" + } + } + ], + "responses":{ + "default":{ + "description":"successful operation" + } + } + } + }, + "/user/createWithArray":{ + "post":{ + "tags":[ + "user" + ], + "summary":"Creates list of users with given input array", + "description":"", + "operationId":"createUsersWithArrayInput", + "produces":[ + "application/xml", + "application/json" + ], + "parameters":[ + { + "in":"body", + "name":"body", + "description":"List of user object", + "required":true, + "schema":{ + "type":"array", + "items":{ + "$ref":"#/definitions/User" + } + } + } + ], + "responses":{ + "default":{ + "description":"successful operation" + } + } + } + }, + "/user/createWithList":{ + "post":{ + "tags":[ + "user" + ], + "summary":"Creates list of users with given input array", + "description":"", + "operationId":"createUsersWithListInput", + "produces":[ + "application/xml", + "application/json" + ], + "parameters":[ + { + "in":"body", + "name":"body", + "description":"List of user object", + "required":true, + "schema":{ + "type":"array", + "items":{ + "$ref":"#/definitions/User" + } + } + } + ], + "responses":{ + "default":{ + "description":"successful operation" + } + } + } + }, + "/user/login":{ + "get":{ + "tags":[ + "user" + ], + "summary":"Logs user into the system", + "description":"", + "operationId":"loginUser", + "produces":[ + "application/xml", + "application/json" + ], + "parameters":[ + { + "name":"username", + "in":"query", + "description":"The user name for login", + "required":true, + "type":"string" + }, + { + "name":"password", + "in":"query", + "description":"The password for login in clear text", + "required":true, + "type":"string" + } + ], + "responses":{ + "200":{ + "description":"successful operation", + "schema":{ + "type":"string" + }, + "headers":{ + "X-Rate-Limit":{ + "type":"integer", + "format":"int32", + "description":"calls per hour allowed by the user" + }, + "X-Expires-After":{ + "type":"string", + "format":"date-time", + "description":"date in UTC when token expires" + } + } + }, + "400":{ + "description":"Invalid username/password supplied" + } + } + } + }, + "/user/logout":{ + "get":{ + "tags":[ + "user" + ], + "summary":"Logs out current logged in user session", + "description":"", + "operationId":"logoutUser", + "produces":[ + "application/xml", + "application/json" + ], + "parameters":[ + + ], + "responses":{ + "default":{ + "description":"successful operation" + } + } + } + }, + "/user/{username}":{ + "get":{ + "tags":[ + "user" + ], + "summary":"Get user by user name", + "description":"", + "operationId":"getUserByName", + "produces":[ + "application/xml", + "application/json" + ], + "parameters":[ + { + "name":"username", + "in":"path", + "description":"The name that needs to be fetched. Use user1 for testing. ", + "required":true, + "type":"string" + } + ], + "responses":{ + "200":{ + "description":"successful operation", + "schema":{ + "$ref":"#/definitions/User" + } + }, + "400":{ + "description":"Invalid username supplied" + }, + "404":{ + "description":"User not found" + } + } + }, + "put":{ + "tags":[ + "user" + ], + "summary":"Updated user", + "description":"This can only be done by the logged in user.", + "operationId":"updateUser", + "produces":[ + "application/xml", + "application/json" + ], + "parameters":[ + { + "name":"username", + "in":"path", + "description":"name that need to be updated", + "required":true, + "type":"string" + }, + { + "in":"body", + "name":"body", + "description":"Updated user object", + "required":true, + "schema":{ + "$ref":"#/definitions/User" + } + } + ], + "responses":{ + "400":{ + "description":"Invalid user supplied" + }, + "404":{ + "description":"User not found" + } + } + }, + "delete":{ + "tags":[ + "user" + ], + "summary":"Delete user", + "description":"This can only be done by the logged in user.", + "operationId":"deleteUser", + "produces":[ + "application/xml", + "application/json" + ], + "parameters":[ + { + "name":"username", + "in":"path", + "description":"The name that needs to be deleted", + "required":true, + "type":"string" + } + ], + "responses":{ + "400":{ + "description":"Invalid username supplied" + }, + "404":{ + "description":"User not found" + } + } + } + } + }, + "securityDefinitions":{ + "petstore_auth":{ + "type":"oauth2", + "authorizationUrl":"http://petstore.swagger.io/oauth/dialog", + "flow":"implicit", + "scopes":{ + "write:pets":"modify pets in your account", + "read:pets":"read your pets" + } + }, + "api_key":{ + "type":"apiKey", + "name":"api_key", + "in":"header" + } + }, + "definitions":{ + "Order":{ + "type":"object", + "properties":{ + "id":{ + "type":"integer", + "format":"int64" + }, + "petId":{ + "type":"integer", + "format":"int64" + }, + "quantity":{ + "type":"integer", + "format":"int32" + }, + "shipDate":{ + "type":"string", + "format":"date-time" + }, + "status":{ + "type":"string", + "description":"Order Status", + "enum":[ + "placed", + "approved", + "delivered" + ] + }, + "complete":{ + "type":"boolean", + "default":false + } + }, + "xml":{ + "name":"Order" + } + }, + "Category":{ + "type":"object", + "properties":{ + "id":{ + "type":"integer", + "format":"int64" + }, + "name":{ + "type":"string" + } + }, + "xml":{ + "name":"Category" + } + }, + "User":{ + "type":"object", + "properties":{ + "id":{ + "type":"integer", + "format":"int64" + }, + "username":{ + "type":"string" + }, + "firstName":{ + "type":"string" + }, + "lastName":{ + "type":"string" + }, + "email":{ + "type":"string" + }, + "password":{ + "type":"string" + }, + "phone":{ + "type":"string" + }, + "userStatus":{ + "type":"integer", + "format":"int32", + "description":"User Status" + } + }, + "xml":{ + "name":"User" + } + }, + "Tag":{ + "type":"object", + "properties":{ + "id":{ + "type":"integer", + "format":"int64" + }, + "name":{ + "type":"string" + } + }, + "xml":{ + "name":"Tag" + } + }, + "Pet":{ + "type":"object", + "required":[ + "name", + "photoUrls" + ], + "properties":{ + "id":{ + "type":"integer", + "format":"int64" + }, + "category":{ + "$ref":"#/definitions/Category" + }, + "name":{ + "type":"string", + "example":"doggie" + }, + "photoUrls":{ + "type":"array", + "xml":{ + "name":"photoUrl", + "wrapped":true + }, + "items":{ + "type":"string" + } + }, + "tags":{ + "type":"array", + "xml":{ + "name":"tag", + "wrapped":true + }, + "items":{ + "$ref":"#/definitions/Tag" + } + }, + "status":{ + "type":"string", + "description":"pet status in the store", + "enum":[ + "available", + "pending", + "sold" + ] + } + }, + "xml":{ + "name":"Pet" + } + }, + "ApiResponse":{ + "type":"object", + "properties":{ + "code":{ + "type":"integer", + "format":"int32" + }, + "type":{ + "type":"string" + }, + "message":{ + "type":"string" + } + } + } + }, + "externalDocs":{ + "description":"Find out more about Swagger", + "url":"http://swagger.io" + } +} \ No newline at end of file diff --git a/frontend/web/api-doc/test/e2e-selenium/static/test-specs/petstore.openapi.yaml b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/petstore.openapi.yaml new file mode 100644 index 0000000..cd4e44b --- /dev/null +++ b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/petstore.openapi.yaml @@ -0,0 +1,689 @@ +--- +openapi: 3.0.0 +servers: +- url: http://localhost:3204/ +info: + description: 'This is a sample server Petstore server. You can find out more about + Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For + this sample, you can use the api key `special-key` to test the authorization filters.' + version: 1.0.0 + title: Swagger Petstore + termsOfService: http://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html +tags: +- name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: http://swagger.io +- name: store + description: Access to Petstore orders +- name: user + description: Operations about user + externalDocs: + description: Find out more about our store + url: http://swagger.io +paths: + "/pet": + post: + tags: + - pet + summary: Add a new pet to the store + description: '' + operationId: addPet + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + requestBody: + "$ref": "#/components/requestBodies/Pet" + put: + tags: + - pet + summary: Update an existing pet + description: '' + operationId: updatePet + responses: + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '405': + description: Validation exception + security: + - petstore_auth: + - write:pets + - read:pets + requestBody: + "$ref": "#/components/requestBodies/Pet" + "/pet/findByStatus": + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + explode: true + schema: + type: array + items: + type: string + enum: + - available + - pending + - sold + default: available + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + "$ref": "#/components/schemas/Pet" + application/json: + schema: + type: array + items: + "$ref": "#/components/schemas/Pet" + '400': + description: Invalid status value + security: + - petstore_auth: + - write:pets + - read:pets + "/pet/findByTags": + get: + tags: + - pet + summary: Finds Pets by tags + description: Muliple tags can be provided with comma separated strings. Use + tag1, tag2, tag3 for testing. + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + explode: true + schema: + type: array + items: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + "$ref": "#/components/schemas/Pet" + application/json: + schema: + type: array + items: + "$ref": "#/components/schemas/Pet" + '400': + description: Invalid tag value + security: + - petstore_auth: + - write:pets + - read:pets + deprecated: true + "/pet/{petId}": + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + "$ref": "#/components/schemas/Pet" + application/json: + schema: + "$ref": "#/components/schemas/Pet" + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - api_key: [] + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: '' + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + name: + description: Updated name of the pet + type: string + status: + description: Updated status of the pet + type: string + delete: + tags: + - pet + summary: Deletes a pet + description: '' + operationId: deletePet + parameters: + - name: api_key + in: header + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - petstore_auth: + - write:pets + - read:pets + "/pet/{petId}/uploadImage": + post: + tags: + - pet + summary: uploads an image + description: '' + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + "$ref": "#/components/schemas/ApiResponse" + security: + - petstore_auth: + - write:pets + - read:pets + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + "/store/inventory": + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + "/store/order": + post: + tags: + - store + summary: Place an order for a pet + description: '' + operationId: placeOrder + responses: + '200': + description: successful operation + content: + application/xml: + schema: + "$ref": "#/components/schemas/Order" + application/json: + schema: + "$ref": "#/components/schemas/Order" + '400': + description: Invalid Order + requestBody: + content: + application/json: + schema: + "$ref": "#/components/schemas/Order" + description: order placed for purchasing the pet + required: true + "/store/order/{orderId}": + get: + tags: + - store + summary: Find purchase order by ID + description: For valid response try integer IDs with value >= 1 and <= 10. Other + values will generated exceptions + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of pet that needs to be fetched + required: true + schema: + type: integer + format: int64 + minimum: 1 + maximum: 10 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + "$ref": "#/components/schemas/Order" + application/json: + schema: + "$ref": "#/components/schemas/Order" + '400': + description: Invalid ID supplied + '404': + description: Order not found + delete: + tags: + - store + summary: Delete purchase order by ID + description: For valid response try integer IDs with positive integer value. + Negative or non-integer values will generate API errors + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: integer + format: int64 + minimum: 1 + responses: + '400': + description: Invalid ID supplied + '404': + description: Order not found + "/user": + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + responses: + default: + description: successful operation + requestBody: + content: + application/json: + schema: + "$ref": "#/components/schemas/User" + description: Created user object + required: true + "/user/createWithArray": + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithArrayInput + responses: + default: + description: successful operation + requestBody: + "$ref": "#/components/requestBodies/UserArray" + "/user/createWithList": + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithListInput + responses: + default: + description: successful operation + requestBody: + "$ref": "#/components/requestBodies/UserArray" + "/user/login": + get: + tags: + - user + summary: Logs user into the system + description: '' + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: true + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: true + schema: + type: string + responses: + '200': + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + '400': + description: Invalid username/password supplied + "/user/logout": + get: + tags: + - user + summary: Logs out current logged in user session + description: '' + operationId: logoutUser + responses: + default: + description: successful operation + "/user/{username}": + get: + tags: + - user + summary: Get user by user name + description: '' + operationId: getUserByName + parameters: + - name: username + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + "$ref": "#/components/schemas/User" + application/json: + schema: + "$ref": "#/components/schemas/User" + '400': + description: Invalid username supplied + '404': + description: User not found + put: + tags: + - user + summary: Updated user + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be updated + required: true + schema: + type: string + responses: + '400': + description: Invalid user supplied + '404': + description: User not found + requestBody: + content: + application/json: + schema: + "$ref": "#/components/schemas/User" + description: Updated user object + required: true + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid username supplied + '404': + description: User not found +externalDocs: + description: Find out more about Swagger + url: http://swagger.io +components: + schemas: + Order: + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + quantity: + type: integer + format: int32 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + type: boolean + default: false + xml: + name: Order + Category: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Category + User: + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + format: int32 + description: User Status + xml: + name: User + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Tag + Pet: + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + category: + "$ref": "#/components/schemas/Category" + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + "$ref": "#/components/schemas/Tag" + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: Pet + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + requestBodies: + Pet: + content: + application/json: + schema: + "$ref": "#/components/schemas/Pet" + application/xml: + schema: + "$ref": "#/components/schemas/Pet" + description: Pet object that needs to be added to the store + required: true + UserArray: + content: + application/json: + schema: + type: array + items: + "$ref": "#/components/schemas/User" + description: List of user object + required: true + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: http://petstore.swagger.io/oauth/dialog + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header diff --git a/frontend/web/api-doc/test/e2e-selenium/static/test-specs/refs/api1.yaml b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/refs/api1.yaml new file mode 100644 index 0000000..ae99421 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/refs/api1.yaml @@ -0,0 +1,32 @@ +openapi: 3.0.0 +info: + description: | + This is a test to show how model reference from another file fails. + This file references api2.yaml. If you load this file first in the browser it fails. + However, if you load api2.yaml first, then load this one it will work. + version: 1.0.0 + title: API1 Test +paths: + '/test-api-1': + get: + summary: Api 1 + responses: + '200': + description: 'api 2 response' + content: + application/json: + schema: + $ref: '#/components/schemas/TestResponse' +components: + schemas: + Api1Prop: + type: string + example: 'api1prop-value' + + TestResponse: + type: object + properties: + api1prop: + $ref: '#/components/schemas/Api1Prop' + api2prop: + $ref: 'api2.yaml#/components/schemas/Api2Prop' diff --git a/frontend/web/api-doc/test/e2e-selenium/static/test-specs/refs/api2.yaml b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/refs/api2.yaml new file mode 100644 index 0000000..b88e833 --- /dev/null +++ b/frontend/web/api-doc/test/e2e-selenium/static/test-specs/refs/api2.yaml @@ -0,0 +1,32 @@ +openapi: 3.0.0 +info: + description: | + This is a test to show how model reference from another file fails. + This file is referenced api1.yaml. If you load api1.yaml first in the browser it fails. + However, if you load this file first, then load api1.yaml it will work. + version: 1.0.0 + title: API2 Test +paths: + '/test-api-2': + get: + summary: Api 2 + responses: + '200': + description: api 2 response + content: + application/json: + schema: + $ref: '#/components/schemas/TestResponse2' +components: + schemas: + Api2Prop: + type: string + description: this is an api2prop + example: 'api1prop-value' + + TestResponse2: + type: object + description: This is a test prop + properties: + api2prop: + $ref: '#/components/schemas/Api2Prop' diff --git a/frontend/web/api-doc/test/mocha/.eslinrc b/frontend/web/api-doc/test/mocha/.eslinrc new file mode 100644 index 0000000..b08be1b --- /dev/null +++ b/frontend/web/api-doc/test/mocha/.eslinrc @@ -0,0 +1,7 @@ +env: + mocha: true +rules: + "react/prop-types": 1 # bah humbug + "react/require-render-return": 1 + "no-unused-vars": 1 # unused vars in tests can be useful for indicating a full signature + "no-global-assign": 1 diff --git a/frontend/web/api-doc/test/mocha/components/live-response.jsx b/frontend/web/api-doc/test/mocha/components/live-response.jsx new file mode 100644 index 0000000..bac8ff0 --- /dev/null +++ b/frontend/web/api-doc/test/mocha/components/live-response.jsx @@ -0,0 +1,104 @@ +/* eslint-env mocha */ +import React from "react" +import { fromJSOrdered } from "core/utils" +import sinon from "sinon" +import expect from "expect" +import { shallow } from "enzyme" +import LiveResponse from "components/live-response" +import ResponseBody from "components/response-body" +import RequestSnippets from "core/plugins/request-snippets/request-snippets" + +describe("", function () { + let request = fromJSOrdered({ + credentials: "same-origin", + headers: { + accept: "application/xml" + }, + url: "http://petstore.swagger.io/v2/pet/1" + }) + + let mutatedRequest = fromJSOrdered({ + credentials: "same-origin", + headers: { + accept: "application/xml", + mutated: "header" + }, + url: "http://mutated.petstore.swagger.io/v2/pet/1" + }) + + let requests = { + request: request, + mutatedRequest: mutatedRequest + } + + const tests = [ + { showMutatedRequest: true, expected: { request: "mutatedRequest", requestForCalls: 0, mutatedRequestForCalls: 1 } }, + { showMutatedRequest: false, expected: { request: "request", requestForCalls: 1, mutatedRequestForCalls: 0 } } + ] + + tests.forEach(function (test) { + it("passes " + test.expected.request + " to RequestSnippets when showMutatedRequest = " + test.showMutatedRequest, function () { + + // Given + + let response = fromJSOrdered({ + status: 200, + url: "http://petstore.swagger.io/v2/pet/1", + headers: { + "content-type": "application/xml" + }, + text: "", + duration: 50 + }) + + let mutatedRequestForSpy = sinon.stub().returns(mutatedRequest) + let requestForSpy = sinon.stub().returns(request) + + let components = { + RequestSnippets: RequestSnippets, + responseBody: ResponseBody + } + + let props = { + response: response, + specSelectors: { + mutatedRequestFor: mutatedRequestForSpy, + requestFor: requestForSpy, + }, + pathMethod: ["/one", "get"], + getComponent: (c) => { + return components[c] + }, + displayRequestDuration: true, + getConfigs: () => ({ showMutatedRequest: test.showMutatedRequest, requestSnippetsEnabled: true }) + } + + // When + let wrapper = shallow() + + // Then + expect(mutatedRequestForSpy.callCount).toEqual(test.expected.mutatedRequestForCalls) + expect(requestForSpy.callCount).toEqual(test.expected.requestForCalls) + + const snippets = wrapper.find("RequestSnippets") + expect(snippets.length).toEqual(1) + expect(snippets.props().request).toBe(requests[test.expected.request]) + + const expectedUrl = requests[test.expected.request].get("url") + expect(wrapper.find("div.request-url pre.microlight").text()).toEqual(expectedUrl) + + let duration = wrapper.find("Duration") + expect(duration.length).toEqual(1) + expect(duration.props().duration).toEqual(50) + expect(duration.html()) + .toEqual("
Request duration
50 ms
") + + let responseHeaders = wrapper.find("Headers") + expect(duration.length).toEqual(1) + expect(responseHeaders.props().headers.length).toEqual(1) + expect(responseHeaders.props().headers[0].key).toEqual("content-type") + expect(responseHeaders.html()) + .toEqual("
Response headers
 content-type: application/xml 
") + }) + }) +}) diff --git a/frontend/web/api-doc/test/mocha/components/online-validator-badge.jsx b/frontend/web/api-doc/test/mocha/components/online-validator-badge.jsx new file mode 100644 index 0000000..8b46482 --- /dev/null +++ b/frontend/web/api-doc/test/mocha/components/online-validator-badge.jsx @@ -0,0 +1,79 @@ +/* eslint-env mocha */ +import React from "react" +import expect from "expect" +import { mount } from "enzyme" +import { fromJS, Map } from "immutable" +import OnlineValidatorBadge from "components/online-validator-badge" + +describe("", function () { + it("should render a validator link and image correctly for the default validator", function () { + // When + const props = { + getConfigs: () => ({}), + getComponent: () => null, + specSelectors: { + url: () => "swagger.json" + } + } + const wrapper = mount( + + ) + + // Then + expect(wrapper.find("a").props().href).toEqual( + "https://validator.swagger.io/validator/debug?url=swagger.json" + ) + expect(wrapper.find("ValidatorImage").length).toEqual(1) + expect(wrapper.find("ValidatorImage").props().src).toEqual( + "https://validator.swagger.io/validator?url=swagger.json" + ) + }) + it("should encode a definition URL correctly", function () { + // When + const props = { + getConfigs: () => ({}), + getComponent: () => null, + specSelectors: { + url: () => "http://google.com/swagger.json" + } + } + const wrapper = mount( + + ) + + // Then + expect(wrapper.find("a").props().href).toEqual( + "https://validator.swagger.io/validator/debug?url=http%3A%2F%2Fgoogle.com%2Fswagger.json" + ) + expect(wrapper.find("ValidatorImage").length).toEqual(1) + expect(wrapper.find("ValidatorImage").props().src).toEqual( + "https://validator.swagger.io/validator?url=http%3A%2F%2Fgoogle.com%2Fswagger.json" + ) + }) + it.skip("should resolve a definition URL against the browser's location", function () { + // TODO: mock `window` + // When + + const props = { + getConfigs: () => ({}), + getComponent: () => null, + specSelectors: { + url: () => "http://google.com/swagger.json" + } + } + const wrapper = mount( + + ) + + // Then + expect(wrapper.find("a").props().href).toEqual( + "https://validator.swagger.io/validator/debug?url=http%3A%2F%2Fgoogle.com%2Fswagger.json" + ) + expect(wrapper.find("ValidatorImage").length).toEqual(1) + expect(wrapper.find("ValidatorImage").props().src).toEqual( + "https://validator.swagger.io/validator?url=http%3A%2F%2Fgoogle.com%2Fswagger.json" + ) + }) + // should resolve a definition URL against the browser's location + +}) diff --git a/frontend/web/api-doc/test/mocha/setup.js b/frontend/web/api-doc/test/mocha/setup.js new file mode 100644 index 0000000..06c650e --- /dev/null +++ b/frontend/web/api-doc/test/mocha/setup.js @@ -0,0 +1,28 @@ +const { JSDOM } = require("jsdom") +const Enzyme = require("enzyme") +const Adapter = require("@wojtekmaj/enzyme-adapter-react-17") + +const win = require("../../src/core/window") + +Enzyme.configure({ adapter: new Adapter() }) + +const jsdom = new JSDOM("") +const { window } = jsdom + +function copyProps(src, target) { + const props = Object.getOwnPropertyNames(src) + .filter(prop => typeof target[prop] === "undefined") + .reduce((result, prop) => ({ + ...result, + [prop]: Object.getOwnPropertyDescriptor(src, prop), + }), {}) + Object.defineProperties(target, props) +} + +global.window = window +global.document = window.document +global.navigator = { + userAgent: "node.js", +} +copyProps(win, window) // use UI's built-in window wrapper +copyProps(window, global) diff --git a/frontend/web/api-doc/test/mocha/xss/anchor-target-rel/online-validator-badge.jsx b/frontend/web/api-doc/test/mocha/xss/anchor-target-rel/online-validator-badge.jsx new file mode 100644 index 0000000..78e73a8 --- /dev/null +++ b/frontend/web/api-doc/test/mocha/xss/anchor-target-rel/online-validator-badge.jsx @@ -0,0 +1,32 @@ +/* eslint-env mocha */ +import React from "react" +import expect from "expect" +import { mount } from "enzyme" +import { fromJS, Map } from "immutable" +import OnlineValidatorBadge from "components/online-validator-badge" + +describe(" Anchor Target Safety", function () { + it("should render a validator link with safe `rel` attributes", function () { + // When + const props = { + getConfigs: () => ({}), + getComponent: () => null, + specSelectors: { + url: () => "swagger.json" + } + } + const wrapper = mount( + + ) + + const anchor = wrapper.find("a") + + // Then + expect(anchor.props().href).toEqual( + "https://validator.swagger.io/validator/debug?url=swagger.json" + ) + expect(anchor.props().target).toEqual("_blank") + expect(anchor.props().rel || "").toContain("noopener") + expect(anchor.props().rel || "").toContain("noreferrer") + }) +}) diff --git a/frontend/web/api-doc/test/unit/.eslintrc b/frontend/web/api-doc/test/unit/.eslintrc new file mode 100644 index 0000000..e69de29 diff --git a/frontend/web/api-doc/test/unit/bugs/3199-sanitization-escaping.jsx b/frontend/web/api-doc/test/unit/bugs/3199-sanitization-escaping.jsx new file mode 100644 index 0000000..fd39f5b --- /dev/null +++ b/frontend/web/api-doc/test/unit/bugs/3199-sanitization-escaping.jsx @@ -0,0 +1,21 @@ +import React from "react" +import { render } from "enzyme" +import Markdown from "components/providers/markdown" + +describe("UI-3199: Sanitized Markdown causing code examples to be double escaped", function(){ + it("should single-escape quotes", function(){ + + let str = "" + + "This is a test: \n\n" + + " {\"abc\": \"def\"}\n" + + let props = { + source: str + } + + let el = render() + + expect(el.find("code").first().text()).toEqual("{\"abc\": \"def\"}\n") + expect(el.find("code").first().html()).toEqual("{\"abc\": \"def\"}\n") + }) +}) diff --git a/frontend/web/api-doc/test/unit/bugs/3279-empty-markdown-source.jsx b/frontend/web/api-doc/test/unit/bugs/3279-empty-markdown-source.jsx new file mode 100644 index 0000000..33be60f --- /dev/null +++ b/frontend/web/api-doc/test/unit/bugs/3279-empty-markdown-source.jsx @@ -0,0 +1,35 @@ +import React from "react" +import { render } from "enzyme" +import Markdown from "components/providers/markdown" + +describe("UI-3279: Empty Markdown inputs causing bare `undefined` in output", function(){ + it("should return no text for `null` as source input", function(){ + let props = { + source: null + } + + let el = render() + + expect(el.text()).toEqual("") + }) + + it("should return no text for `undefined` as source input", function(){ + let props = { + source: undefined + } + + let el = render() + + expect(el.text()).toEqual("") + }) + + it("should return no text for empty string as source input", function(){ + let props = { + source: "" + } + + let el = render() + + expect(el.text()).toEqual("") + }) +}) diff --git a/frontend/web/api-doc/test/unit/bugs/4557-default-parameter-values.jsx b/frontend/web/api-doc/test/unit/bugs/4557-default-parameter-values.jsx new file mode 100644 index 0000000..05f96d4 --- /dev/null +++ b/frontend/web/api-doc/test/unit/bugs/4557-default-parameter-values.jsx @@ -0,0 +1,78 @@ + +import React from "react" +import { List, fromJS } from "immutable" + +import { render } from "enzyme" +import ParameterRow from "components/parameter-row" + +describe("bug #4557: default parameter values", function(){ + it("should apply a Swagger 2.0 default value", function(){ + + const paramValue = fromJS({ + description: "a pet", + type: "string", + default: "MyDefaultValue" + }) + + let props = { + getComponent: ()=> "div", + specSelectors: { + security(){}, + parameterWithMetaByIdentity(){ return paramValue }, + isOAS3(){ return false }, + isSwagger2(){ return true } + }, + fn: {}, + operation: {get: ()=>{}}, + onChange: jest.fn(), + param: paramValue, + rawParam: paramValue, + onChangeConsumes: () => {}, + pathMethod: [], + getConfigs: () => { return {} }, + specPath: List([]) + } + + render() + + expect(props.onChange).toHaveBeenCalled() + expect(props.onChange).toHaveBeenCalledWith(paramValue, "MyDefaultValue", false) + }) + it("should apply an OpenAPI 3.0 default value", function(){ + + const paramValue = fromJS({ + description: "a pet", + schema: { + type: "string", + default: "MyDefaultValue" + } + }) + + let props = { + getComponent: ()=> "div", + specSelectors: { + security(){}, + parameterWithMetaByIdentity(){ return paramValue }, + isOAS3(){ return true }, + isSwagger2() { return false } + }, + oas3Selectors: { + activeExamplesMember: () => null + }, + fn: {}, + operation: {get: ()=>{}}, + onChange: jest.fn(), + param: paramValue, + rawParam: paramValue, + onChangeConsumes: () => {}, + pathMethod: [], + getConfigs: () => { return {} }, + specPath: List([]) + } + + render() + + expect(props.onChange).toHaveBeenCalled() + expect(props.onChange).toHaveBeenCalledWith(paramValue, "MyDefaultValue", false) + }) +}) diff --git a/frontend/web/api-doc/test/unit/components/filter.jsx b/frontend/web/api-doc/test/unit/components/filter.jsx new file mode 100644 index 0000000..6be672d --- /dev/null +++ b/frontend/web/api-doc/test/unit/components/filter.jsx @@ -0,0 +1,62 @@ +import React from "react" +import { mount } from "enzyme" +import FilterContainer from "containers/filter" +import { Col } from "components/layout-utils" + +describe("", function(){ + + const mockedProps = { + specSelectors: { + loadingStatus() {} + }, + layoutSelectors: { + currentFilter() {} + }, + getComponent: () => {return Col} + } + + it("renders FilterContainer if filter is provided", function(){ + + // Given + let props = {...mockedProps} + props.layoutSelectors = {...mockedProps.specSelectors} + props.layoutSelectors.currentFilter = function() {return true} + + // When + let wrapper = mount() + + // Then + const renderedColInsideFilter = wrapper.find(Col) + expect(renderedColInsideFilter.length).toEqual(1) + }) + + it("does not render FilterContainer if filter is null", function(){ + + // Given + let props = {...mockedProps} + props.layoutSelectors = {...mockedProps.specSelectors} + props.layoutSelectors.currentFilter = function() {return null} + + // When + let wrapper = mount() + + // Then + const renderedColInsideFilter = wrapper.find(Col) + expect(renderedColInsideFilter.length).toEqual(0) + }) + + it("does not render FilterContainer if filter is false", function(){ + + // Given + let props = {...mockedProps} + props.layoutSelectors = {...mockedProps.specSelectors} + props.layoutSelectors.currentFilter = function() {return false} + + // When + let wrapper = mount() + + // Then + const renderedColInsideFilter = wrapper.find(Col) + expect(renderedColInsideFilter.length).toEqual(0) + }) +}) diff --git a/frontend/web/api-doc/test/unit/components/info-wrapper.jsx b/frontend/web/api-doc/test/unit/components/info-wrapper.jsx new file mode 100644 index 0000000..256ee1d --- /dev/null +++ b/frontend/web/api-doc/test/unit/components/info-wrapper.jsx @@ -0,0 +1,67 @@ +import React from "react" +import { mount } from "enzyme" +import { fromJS } from "immutable" +import InfoContainer from "containers/info" + +describe("", function () { + + const components = { + info: () => + } + const mockedProps = { + specSelectors: { + info () {}, + url () {}, + basePath () {}, + host () {}, + externalDocs () {}, + }, + oas3Selectors: { + selectedServer () {}, + }, + getComponent: c => components[c] + } + + it("renders Info inside InfoContainer if info is provided", function () { + + // Given + let props = {...mockedProps} + props.specSelectors = {...mockedProps.specSelectors} + props.specSelectors.info = function () {return fromJS(["info1", "info2"])} + + // When + let wrapper = mount() + + // Then + const renderedInfo = wrapper.find("span.mocked-info") + expect(renderedInfo.length).toEqual(1) + }) + + it("does not render Info inside InfoContainer if no info is provided", function () { + + // Given + let props = {...mockedProps} + props.specSelectors = {...mockedProps.specSelectors} + props.specSelectors.info = function () {return fromJS([])} + + // When + let wrapper = mount() + + // Then + const renderedInfo = wrapper.find("span.mocked-info") + expect(renderedInfo.length).toEqual(0) + }) + + it("does not render Info inside InfoContainer if info is undefined", function () { + + // Given + let props = {...mockedProps} + + // When + let wrapper = mount() + + // Then + const renderedInfo = wrapper.find("span.mocked-info") + expect(renderedInfo.length).toEqual(0) + }) +}) diff --git a/frontend/web/api-doc/test/unit/components/json-schema-form.jsx b/frontend/web/api-doc/test/unit/components/json-schema-form.jsx new file mode 100644 index 0000000..f8df3fb --- /dev/null +++ b/frontend/web/api-doc/test/unit/components/json-schema-form.jsx @@ -0,0 +1,257 @@ +import React from "react" +import Immutable, { List } from "immutable" +import { Select, Input, TextArea } from "components/layout-utils" +import { mount, render } from "enzyme" +import * as JsonSchemaComponents from "core/json-schema-components" +import { JsonSchemaForm } from "core/json-schema-components" + +const components = {...JsonSchemaComponents, Select, Input, TextArea} + +const getComponentStub = (name) => { + if(components[name]) return components[name] + + return null +} + +describe("", function(){ + describe("strings", function() { + it("should render the correct options for a string enum parameter", function(){ + + let props = { + getComponent: getComponentStub, + value: "", + onChange: () => {}, + keyName: "", + fn: {}, + schema: Immutable.fromJS({ + type: "string", + enum: ["one", "two"] + }) + } + + let wrapper = render() + + expect(wrapper.get(0).name).toEqual("select") + expect(wrapper.find("option").length).toEqual(3) + expect(wrapper.find("option").eq(0).text()).toEqual("--") + expect(wrapper.find("option").eq(1).text()).toEqual("one") + expect(wrapper.find("option").eq(2).text()).toEqual("two") + }) + + it("should render a string enum as disabled when JsonSchemaForm is disabled", function(){ + + let props = { + getComponent: getComponentStub, + value: "", + onChange: () => {}, + keyName: "", + fn: {}, + schema: Immutable.fromJS({ + type: "string", + enum: ["one", "two"] + }), + disabled: true + } + + let wrapper = render() + + expect(wrapper.attr("disabled")).toEqual("disabled") + }) + + + it("should render the correct options for a required string enum parameter", function(){ + + let props = { + getComponent: getComponentStub, + value: "", + onChange: () => {}, + keyName: "", + fn: {}, + required: true, + schema: Immutable.fromJS({ + type: "string", + enum: ["one", "two"] + }) + } + + let wrapper = render() + + expect(wrapper.get(0).name).toEqual("select") + expect(wrapper.find("select option").length).toEqual(2) + expect(wrapper.find("select option").eq(0).text()).toEqual("one") + expect(wrapper.find("select option").eq(1).text()).toEqual("two") + }) + }) + describe("booleans", function() { + it("should render the correct options for a boolean parameter", function(){ + + let props = { + getComponent: getComponentStub, + value: "", + onChange: () => {}, + keyName: "", + fn: {}, + schema: Immutable.fromJS({ + type: "boolean" + }) + } + + let wrapper = render() + + expect(wrapper.get(0).name).toEqual("select") + expect(wrapper.find("select option").length).toEqual(3) + expect(wrapper.find("select option").eq(0).text()).toEqual("--") + expect(wrapper.find("select option").eq(1).text()).toEqual("true") + expect(wrapper.find("select option").eq(2).text()).toEqual("false") + }) + + + it("should render the correct options for an enum boolean parameter", function(){ + + let props = { + getComponent: getComponentStub, + value: "", + onChange: () => {}, + keyName: "", + fn: {}, + schema: Immutable.fromJS({ + type: "boolean", + enum: ["true"] + }) + } + + let wrapper = render() + + expect(wrapper.get(0).name).toEqual("select") + expect(wrapper.find("select option").length).toEqual(2) + expect(wrapper.find("select option").eq(0).text()).toEqual("--") + expect(wrapper.find("select option").eq(1).text()).toEqual("true") + expect(wrapper.find("select option:checked").first().text()).toEqual("--") + }) + + it("should render the correct options for a required boolean parameter", function(){ + + let props = { + getComponent: getComponentStub, + value: "", + onChange: () => {}, + keyName: "", + fn: {}, + schema: Immutable.fromJS({ + type: "boolean", + required: true + }) + } + + let wrapper = render() + + expect(wrapper.get(0).name).toEqual("select") + expect(wrapper.find("select option").length).toEqual(3) + expect(wrapper.find("select option").eq(0).text()).toEqual("--") + expect(wrapper.find("select option").eq(1).text()).toEqual("true") + expect(wrapper.find("select option").eq(2).text()).toEqual("false") + expect(wrapper.find("select option:checked").first().text()).toEqual("--") + }) + + it("should render the correct options for a required enum boolean parameter", function(){ + + let props = { + getComponent: getComponentStub, + value: "", + onChange: () => {}, + keyName: "", + fn: {}, + required: true, + schema: Immutable.fromJS({ + type: "boolean", + enum: ["true"] + }) + } + + let wrapper = render() + + expect(wrapper.get(0).name).toEqual("select") + expect(wrapper.find("select option").length).toEqual(1) + expect(wrapper.find("select option").eq(0).text()).toEqual("true") + expect(wrapper.find("select option:checked").first().text()).toEqual("true") + }) + }) + describe("objects", function() { + it("should render the correct editor for an OAS3 object parameter", function(){ + let updateQueue = [] + + let props = { + getComponent: getComponentStub, + value: `{\n "id": "abc123"\n}`, + onChange: (value) => { + updateQueue.push({ value }) + }, + keyName: "", + fn: {}, + errors: List(), + schema: Immutable.fromJS({ + type: "object", + properties: { + id: { + type: "string", + example: "abc123" + } + } + }) + } + + let wrapper = mount() + + updateQueue.forEach(newProps => wrapper.setProps(newProps)) + + expect(wrapper.find("textarea").length).toEqual(1) + expect(wrapper.find("textarea").text()).toEqual(`{\n "id": "abc123"\n}`) + }) + }) + describe("unknown types", function() { + it("should render unknown types as strings", function(){ + + let props = { + getComponent: getComponentStub, + value: "yo", + onChange: () => {}, + keyName: "", + fn: {}, + schema: Immutable.fromJS({ + type: "NotARealType" + }) + } + + + let wrapper = render() + + expect(wrapper.length).toEqual(1) + expect(wrapper.get(0).name).toEqual("input") + // expect(wrapper.find("select input").length).toEqual(1) + // expect(wrapper.find("select option").first().text()).toEqual("true") + }) + + it("should render unknown types as strings when a format is passed", function(){ + + let props = { + getComponent: getComponentStub, + value: "yo", + onChange: () => {}, + keyName: "", + fn: {}, + schema: Immutable.fromJS({ + type: "NotARealType", + format: "NotARealFormat" + }) + } + + + let wrapper = render() + + expect(wrapper.length).toEqual(1) + expect(wrapper.get(0).name).toEqual("input") + // expect(wrapper.find("select input").length).toEqual(1) + // expect(wrapper.find("select option").first().text()).toEqual("true") + }) + }) +}) diff --git a/frontend/web/api-doc/test/unit/components/live-response.jsx b/frontend/web/api-doc/test/unit/components/live-response.jsx new file mode 100644 index 0000000..ce7702f --- /dev/null +++ b/frontend/web/api-doc/test/unit/components/live-response.jsx @@ -0,0 +1,101 @@ +import React from "react" +import { fromJSOrdered } from "core/utils" +import { shallow } from "enzyme" +import Curl from "components/curl" +import LiveResponse from "components/live-response" +import ResponseBody from "components/response-body" + +describe("", function(){ + let request = fromJSOrdered({ + credentials: "same-origin", + headers: { + accept: "application/xml" + }, + url: "http://petstore.swagger.io/v2/pet/1" + }) + + let mutatedRequest = fromJSOrdered({ + credentials: "same-origin", + headers: { + accept: "application/xml", + mutated: "header" + }, + url: "http://mutated.petstore.swagger.io/v2/pet/1" + }) + + let requests = { + request: request, + mutatedRequest: mutatedRequest + } + + const tests = [ + { showMutatedRequest: true, expected: { request: "mutatedRequest", requestForCalls: 0, mutatedRequestForCalls: 1 } }, + { showMutatedRequest: false, expected: { request: "request", requestForCalls: 1, mutatedRequestForCalls: 0 } } + ] + + tests.forEach(function(test) { + it("passes " + test.expected.request + " to Curl when showMutatedRequest = " + test.showMutatedRequest, function() { + + // Given + + let response = fromJSOrdered({ + status: 200, + url: "http://petstore.swagger.io/v2/pet/1", + headers: { + "content-type": "application/xml" + }, + text: "", + duration: 50 + }) + + let mutatedRequestForSpy = jest.fn().mockImplementation(function(mutatedRequest) { return mutatedRequest }) + let requestForSpy = jest.fn().mockImplementation(function(request) { return request }) + + let components = { + curl: Curl, + responseBody: ResponseBody + } + + let props = { + response: response, + specSelectors: { + mutatedRequestFor: mutatedRequestForSpy, + requestFor: requestForSpy, + }, + pathMethod: [ "/one", "get" ], + getComponent: (c) => { + return components[c] + }, + displayRequestDuration: true, + getConfigs: () => ({ showMutatedRequest: test.showMutatedRequest }) + } + + // When + let wrapper = shallow() + + // Then + expect(mutatedRequestForSpy.calls.length).toEqual(test.expected.mutatedRequestForCalls) + expect(requestForSpy.calls.length).toEqual(test.expected.requestForCalls) + + const curl = wrapper.find(Curl) + expect(curl.length).toEqual(1) + expect(curl.props().request).toBe(requests[test.expected.request]) + + const expectedUrl = requests[test.expected.request].get("url") + expect(wrapper.find("div.request-url pre.microlight").text()).toEqual(expectedUrl) + + let duration = wrapper.find("Duration") + expect(duration.length).toEqual(1) + expect(duration.props().duration).toEqual(50) + expect(duration.html()) + .toEqual("
Request duration
50 ms
") + + let responseHeaders = wrapper.find("Headers") + expect(duration.length).toEqual(1) + expect(responseHeaders.props().headers.length).toEqual(1) + expect(responseHeaders.props().headers[0].key).toEqual("content-type") + expect(responseHeaders.html()) + .toEqual("
Response headers
 content-type: application/xml 
") + }) + }) +}) diff --git a/frontend/web/api-doc/test/unit/components/markdown.jsx b/frontend/web/api-doc/test/unit/components/markdown.jsx new file mode 100644 index 0000000..536ce49 --- /dev/null +++ b/frontend/web/api-doc/test/unit/components/markdown.jsx @@ -0,0 +1,110 @@ +import React from "react" +import { render } from "enzyme" +import Markdown from "components/providers/markdown" +import { Markdown as OAS3Markdown } from "corePlugins/oas3/wrap-components/markdown.jsx" + +describe("Markdown component", function () { + describe("Swagger 2.0", function () { + it("allows elements with class, style and data-* attribs", function () { + const getConfigs = () => ({ useUnsafeMarkdown: true }) + const str = `ONE` + const el = render() + expect(el.prop("outerHTML")).toEqual(`

ONE

\n
`) + }) + + it("strips class, style and data-* attribs from elements", function () { + const getConfigs = () => ({ useUnsafeMarkdown: false }) + const str = `ONE` + const el = render() + expect(el.prop("outerHTML")).toEqual(`

ONE

\n
`) + }) + + it("allows td elements with colspan attrib", function () { + const str = `
ABC
` + const el = render() + expect(el.prop("outerHTML")).toEqual(`
ABC
`) + }) + + it("allows image elements", function () { + const str = `![Image alt text](http://image.source "Image title")` + const el = render() + expect(el.prop("outerHTML")).toEqual(`

Image alt text

\n
`) + }) + + it("allows image elements with https scheme", function () { + const str = `![Image alt text](https://image.source "Image title")` + const el = render() + expect(el.prop("outerHTML")).toEqual(`

Image alt text

\n
`) + }) + + it("allows image elements with data scheme", function () { + const str = `` + const el = render() + expect(el.prop("outerHTML")).toEqual(`

` + str + `

\n
`) + }) + + it("allows heading elements", function () { + const str = ` +# h1 +## h2 +### h3 +#### h4 +##### h5 +###### h6` + const el = render() + expect(el.prop("outerHTML")).toEqual(`

h1

\n

h2

\n

h3

\n

h4

\n
h5
\n
h6
\n
`) + }) + + it("allows links", function () { + const str = `[Link](https://example.com/)` + const el = render() + expect(el.prop("outerHTML")).toEqual(``) + }) + }) + + describe("OAS 3", function () { + it("allows elements with class, style and data-* attribs", function () { + const getConfigs = () => ({ useUnsafeMarkdown: true }) + const str = `ONE` + const el = render() + expect(el.prop("outerHTML")).toEqual(`

ONE

`) + }) + + it("strips class, style and data-* attribs from elements", function () { + const getConfigs = () => ({ useUnsafeMarkdown: false }) + const str = `ONE` + const el = render() + expect(el.prop("outerHTML")).toEqual(`

ONE

`) + }) + + it("allows image elements", function () { + const str = `![Image alt text](http://image.source "Image title")` + const el = render() + expect(el.prop("outerHTML")).toEqual(`

Image alt text

`) + }) + + it("allows image elements with https scheme", function () { + const str = `![Image alt text](https://image.source "Image title")` + const el = render() + expect(el.prop("outerHTML")).toEqual(`

Image alt text

`) + }) + + it("allows image elements with data scheme", function () { + const str = `` + const el = render() + expect(el.prop("outerHTML")).toEqual(`

` + str + `

`) + }) + + it("allows heading elements", function () { + const str = ` +# h1 +## h2 +### h3 +#### h4 +##### h5 +###### h6` + const el = render() + expect(el.prop("outerHTML")).toEqual(`

h1

\n

h2

\n

h3

\n

h4

\n
h5
\n
h6
`) + }) + }) +}) diff --git a/frontend/web/api-doc/test/unit/components/model-example.jsx b/frontend/web/api-doc/test/unit/components/model-example.jsx new file mode 100644 index 0000000..c45ce46 --- /dev/null +++ b/frontend/web/api-doc/test/unit/components/model-example.jsx @@ -0,0 +1,122 @@ +import React from "react" +import { shallow } from "enzyme" +import ModelExample from "components/model-example" +import ModelComponent from "components/model-wrapper" + +describe("", function(){ + let components, props + + let exampleSelectedTestInputs = [ + { defaultModelRendering: "model", isExecute: true }, + { defaultModelRendering: "example", isExecute: true }, + { defaultModelRendering: "example", isExecute: false }, + { defaultModelRendering: "othervalue", isExecute: true }, + { defaultModelRendering: "othervalue", isExecute: false } + ] + + let modelSelectedTestInputs = [ + { defaultModelRendering: "model", isExecute: false } + ] + + beforeEach(() => { + components = { + ModelWrapper: ModelComponent + } + + props = { + getComponent: (c) => { + return components[c] + }, + specSelectors: { + isOAS3: () => false + }, + schema: {}, + example: "{\"example\": \"value\"}", + isExecute: false, + getConfigs: () => ({ + defaultModelRendering: "model", + defaultModelExpandDepth: 1 + }) + } + }) + + + it("renders model and example tabs", function(){ + // When + let wrapper = shallow() + + // Then should render tabs + expect(wrapper.find("div > ul.tab").length).toEqual(1) + + let tabs = wrapper.find("div > ul.tab").children() + expect(tabs.length).toEqual(2) + tabs.forEach((node) => { + expect(node.length).toEqual(1) + expect(node.name()).toEqual("li") + expect(node.hasClass("tabitem")).toEqual(true) + }) + expect(tabs.at(0).text()).toEqual("Example Value") + expect(tabs.at(1).text()).toEqual("Model") + }) + + exampleSelectedTestInputs.forEach(function(testInputs) { + it("example tab is selected if isExecute = " + testInputs.isExecute + " and defaultModelRendering = " + testInputs.defaultModelRendering, function(){ + // When + props.isExecute = testInputs.isExecute + props.getConfigs = () => ({ + defaultModelRendering: testInputs.defaultModelRendering, + defaultModelExpandDepth: 1 + }) + let wrapper = shallow() + + // Then + let tabs = wrapper.find("div > ul.tab").children() + + let exampleTab = tabs.at(0) + expect(exampleTab.hasClass("active")).toEqual(true) + let modelTab = tabs.at(1) + expect(modelTab.hasClass("active")).toEqual(false) + + expect(wrapper.find("div > div").length).toEqual(1) + expect(wrapper.find("div > div").text()).toEqual(props.example) + }) + }) + + modelSelectedTestInputs.forEach(function(testInputs) { + it("model tab is selected if isExecute = " + testInputs.isExecute + " and defaultModelRendering = " + testInputs.defaultModelRendering, function(){ + // When + props.isExecute = testInputs.isExecute + props.getConfigs = () => ({ + defaultModelRendering: testInputs.defaultModelRendering, + defaultModelExpandDepth: 1 + }) + let wrapper = shallow() + + // Then + let tabs = wrapper.find("div > ul.tab").children() + + let exampleTab = tabs.at(0) + expect(exampleTab.hasClass("active")).toEqual(false) + let modelTab = tabs.at(1) + expect(modelTab.hasClass("active")).toEqual(true) + + expect(wrapper.find("div > div").length).toEqual(1) + expect(wrapper.find("div > div").find(ModelComponent).props().expandDepth).toBe(1) + }) + }) + + it("passes defaultModelExpandDepth to ModelComponent", function(){ + // When + let expandDepth = 0 + props.isExecute = false + props.getConfigs = () => ({ + defaultModelRendering: "model", + defaultModelExpandDepth: expandDepth + }) + let wrapper = shallow() + + // Then + expect(wrapper.find("div > div").find(ModelComponent).props().expandDepth).toBe(expandDepth) + }) + +}) diff --git a/frontend/web/api-doc/test/unit/components/models.jsx b/frontend/web/api-doc/test/unit/components/models.jsx new file mode 100644 index 0000000..dde61ff --- /dev/null +++ b/frontend/web/api-doc/test/unit/components/models.jsx @@ -0,0 +1,54 @@ +import React from "react" +import { shallow } from "enzyme" +import { fromJS, Map } from "immutable" +import Models from "components/models" +import ModelCollapse from "components/model-collapse" +import ModelComponent from "components/model-wrapper" + +describe("", function(){ + const dummyComponent = () => null + // Given + let components = { + Collapse: ModelCollapse, + ModelWrapper: ModelComponent, + JumpToPath: dummyComponent, + } + let props = { + getComponent: (c) => { + return components[c] + }, + specSelectors: { + isOAS3: () => false, + specJson: () => Map(), + definitions: function() { + return fromJS({ + def1: {}, + def2: {} + }) + }, + specResolvedSubtree: () => {} + }, + layoutSelectors: { + isShown: jest.fn() + }, + layoutActions: {}, + getConfigs: () => ({ + docExpansion: "list", + defaultModelsExpandDepth: 0 + }) + } + + + it("passes defaultModelsExpandDepth to ModelWrapper", function(){ + // When + let wrapper = shallow() + + // Then should render tabs + expect(wrapper.find("ModelCollapse").length).toEqual(1) + expect(wrapper.find("ModelWrapper").length).toBeGreaterThan(0) + wrapper.find("ModelComponent").forEach((modelWrapper) => { + expect(modelWrapper.props().expandDepth).toBe(0) + }) + }) + +}) diff --git a/frontend/web/api-doc/test/unit/components/object-model.jsx b/frontend/web/api-doc/test/unit/components/object-model.jsx new file mode 100644 index 0000000..3367bac --- /dev/null +++ b/frontend/web/api-doc/test/unit/components/object-model.jsx @@ -0,0 +1,109 @@ +import React from "react" +import { shallow } from "enzyme" +import { List } from "immutable" +import ObjectModel from "components/object-model" +// import ModelExample from "components/model-example" +import Immutable from "immutable" +import Model from "components/model" +import ModelCollapse from "components/model-collapse" +import Property from "components/property" +// import { inferSchema } from "corePlugins/samples/fn" + +describe("", function() { + const dummyComponent = () => null + const components = { + "JumpToPath" : dummyComponent, + "Markdown" : dummyComponent, + "Model" : Model, + "ModelCollapse" : ModelCollapse, + "Property" : Property + } + const props = { + getComponent: c => components[c], + getConfigs: () => { + return { + showExtensions: true + } + }, + isRef : false, + specPath: List(), + schema: Immutable.fromJS( + { + "properties": { + // Note reverse order: c, b, a + c: { + type: "integer", + name: "c" + }, + b: { + type: "boolean", + name: "b" + }, + a: { + type: "string", + name: "a" + } + } + } + ), + specSelectors: { + isOAS3(){ + return false + } + }, + className: "for-test" + } + const propsNullable = { + ...props, + schema: props.schema.set("nullable", true) + } + const propsMinMaxProperties = { + ...props, + schema: props.schema.set("minProperties", 1).set("maxProperties", 5) + } + + it("renders a collapsible header", function(){ + const wrapper = shallow() + const renderedModelCollapse = wrapper.find(ModelCollapse) + expect(renderedModelCollapse.length).toEqual(1) + }) + + it("renders the object properties in order", function() { + const wrapper = shallow() + const renderedModel = wrapper.find(Model) + expect(renderedModel.length).toEqual(3) + expect(renderedModel.get(0).props.schema.get("name")).toEqual("c") + expect(renderedModel.get(1).props.schema.get("name")).toEqual("b") + expect(renderedModel.get(2).props.schema.get("name")).toEqual("a") + }) + + it("doesn't render `nullable` for model when it absent", function() { + const wrapper = shallow() + const renderProperties = wrapper.find(Property) + expect(renderProperties.length).toEqual(0) + }) + + it("renders `nullable` for model", function() { + const wrapper = shallow() + const renderProperties = wrapper.find(Property) + expect(renderProperties.length).toEqual(1) + expect(renderProperties.get(0).props.propKey).toEqual("nullable") + expect(renderProperties.get(0).props.propVal).toEqual(true) + }) + + it("doesn't render `minProperties` and `maxProperties` if they are absent", function() { + const wrapper = shallow() + const renderProperties = wrapper.find(Property) + expect(renderProperties.length).toEqual(0) + }) + + it("renders `minProperties` and `maxProperties` if they are defined", function() { + const wrapper = shallow() + const renderProperties = wrapper.find(Property) + expect(renderProperties.length).toEqual(2) + expect(renderProperties.get(0).props.propKey).toEqual("minProperties") + expect(renderProperties.get(0).props.propVal).toEqual(1) + expect(renderProperties.get(1).props.propKey).toEqual("maxProperties") + expect(renderProperties.get(1).props.propVal).toEqual(5) + }) +}) diff --git a/frontend/web/api-doc/test/unit/components/online-validator-badge.jsx b/frontend/web/api-doc/test/unit/components/online-validator-badge.jsx new file mode 100644 index 0000000..f3a7755 --- /dev/null +++ b/frontend/web/api-doc/test/unit/components/online-validator-badge.jsx @@ -0,0 +1,76 @@ +import React from "react" +import { mount } from "enzyme" +import OnlineValidatorBadge from "components/online-validator-badge" + +describe("", function () { + it("should render a validator link and image correctly for the default validator", function () { + // When + const props = { + getConfigs: () => ({}), + getComponent: () => null, + specSelectors: { + url: () => "swagger.json" + } + } + const wrapper = mount( + + ) + + // Then + expect(wrapper.find("a").props().href).toEqual( + "https://validator.swagger.io/validator/debug?url=swagger.json" + ) + expect(wrapper.find("ValidatorImage").length).toEqual(1) + expect(wrapper.find("ValidatorImage").props().src).toEqual( + "https://validator.swagger.io/validator?url=swagger.json" + ) + }) + it("should encode a definition URL correctly", function () { + // When + const props = { + getConfigs: () => ({}), + getComponent: () => null, + specSelectors: { + url: () => "http://google.com/swagger.json" + } + } + const wrapper = mount( + + ) + + // Then + expect(wrapper.find("a").props().href).toEqual( + "https://validator.swagger.io/validator/debug?url=http%3A%2F%2Fgoogle.com%2Fswagger.json" + ) + expect(wrapper.find("ValidatorImage").length).toEqual(1) + expect(wrapper.find("ValidatorImage").props().src).toEqual( + "https://validator.swagger.io/validator?url=http%3A%2F%2Fgoogle.com%2Fswagger.json" + ) + }) + it.skip("should resolve a definition URL against the browser's location", function () { + // TODO: mock `window` + // When + + const props = { + getConfigs: () => ({}), + getComponent: () => null, + specSelectors: { + url: () => "http://google.com/swagger.json" + } + } + const wrapper = mount( + + ) + + // Then + expect(wrapper.find("a").props().href).toEqual( + "https://validator.swagger.io/validator/debug?url=http%3A%2F%2Fgoogle.com%2Fswagger.json" + ) + expect(wrapper.find("ValidatorImage").length).toEqual(1) + expect(wrapper.find("ValidatorImage").props().src).toEqual( + "https://validator.swagger.io/validator?url=http%3A%2F%2Fgoogle.com%2Fswagger.json" + ) + }) + // should resolve a definition URL against the browser's location + +}) diff --git a/frontend/web/api-doc/test/unit/components/operation-tag.jsx b/frontend/web/api-doc/test/unit/components/operation-tag.jsx new file mode 100644 index 0000000..af3850c --- /dev/null +++ b/frontend/web/api-doc/test/unit/components/operation-tag.jsx @@ -0,0 +1,53 @@ +import React from "react" +import { shallow } from "enzyme" +import OperationTag from "components/operation-tag" +import Im from "immutable" +import { Link } from "components/layout-utils" + +describe("", function(){ + it("render externalDocs URL for swagger v2", function(){ + + const dummyComponent = () => null + const components = { + Collapse: () => dummyComponent, + Markdown: () => dummyComponent, + DeepLink: () => dummyComponent, + Link + } + + let props = { + tagObj: Im.fromJS({ + tagDetails: { + externalDocs: { + description: "Find out more", + url: "http://swagger.io" + } + } + }), + tag: "testtag", + getConfigs: () => ({}), + getComponent: c => components[c], + layoutSelectors: { + currentFilter() { + return null + }, + isShown() { + return true + }, + show() { + return true + } + } + } + + let wrapper = shallow() + + const opblockTag = wrapper.find(".opblock-tag") + expect(opblockTag.length).toEqual(1) + expect(opblockTag.getElement().type).toEqual("h3") + + const renderedLink = wrapper.find("Link") + expect(renderedLink.length).toEqual(1) + expect(renderedLink.props().href).toEqual("http://swagger.io") + }) +}) diff --git a/frontend/web/api-doc/test/unit/components/operation.jsx b/frontend/web/api-doc/test/unit/components/operation.jsx new file mode 100644 index 0000000..161d567 --- /dev/null +++ b/frontend/web/api-doc/test/unit/components/operation.jsx @@ -0,0 +1,30 @@ +import React from "react" +import { shallow } from "enzyme" +import Operation from "components/operation" + +describe("", function(){ + it.skip("blanket tests", function(){ + + let props = { + operation: {get: ()=>{}}, + getComponent: ()=> "div", + specSelectors: { security(){} }, + path: "/one", + method: "get", + shown: true, + showOpId: "", + showOpIdPrefix: "", + toggleCollapse: jest.fn() + } + + let wrapper = shallow() + + expect(wrapper.find(".opblock").length).toEqual(1) + expect(wrapper.find(".opblock-summary-method").text()).toEqual("GET") + expect(wrapper.find(".opblock-summary-path").text().trim()).toEqual("/one") + expect(wrapper.find("[isOpened]").prop("isOpened")).toEqual(true) + + wrapper.find(".opblock-summary").simulate("click") + expect(props.toggleCollapse).toHaveBeenCalled() + }) +}) diff --git a/frontend/web/api-doc/test/unit/components/operations.jsx b/frontend/web/api-doc/test/unit/components/operations.jsx new file mode 100644 index 0000000..d8295aa --- /dev/null +++ b/frontend/web/api-doc/test/unit/components/operations.jsx @@ -0,0 +1,126 @@ +import React from "react" +import { render } from "enzyme" +import { fromJS } from "immutable" +import DeepLink from "components/deep-link" +import Operations from "components/operations" +import {Collapse} from "components/layout-utils" + +const components = { + Collapse, + DeepLink, + OperationContainer: ({ path, method }) => , + OperationTag: "div", +} + +describe("", function(){ + it("should render a Swagger2 `get` method, but not a `trace` or `foo` method", function(){ + + let props = { + fn: {}, + specActions: {}, + layoutActions: {}, + getComponent: (name)=> { + return components[name] || null + }, + getConfigs: () => { + return {} + }, + specSelectors: { + isOAS3() { return false }, + url() { return "https://petstore.swagger.io/v2/swagger.json" }, + taggedOperations() { + return fromJS({ + "default": { + "operations": [ + { + "path": "/pets/{id}", + "method": "get" + }, + { + "path": "/pets/{id}", + "method": "trace" + }, + { + "path": "/pets/{id}", + "method": "foo" + }, + ] + } + }) + }, + }, + layoutSelectors: { + currentFilter() { + return null + }, + isShown() { + return true + }, + show() { + return true + } + } + } + + let wrapper = render() + + expect(wrapper.find("span.mocked-op").length).toEqual(1) + expect(wrapper.find("span.mocked-op").eq(0).attr("id")).toEqual("/pets/{id}-get") + }) + + it("should render an OAS3 `get` and `trace` method, but not a `foo` method", function(){ + + let props = { + fn: {}, + specActions: {}, + layoutActions: {}, + getComponent: (name)=> { + return components[name] || null + }, + getConfigs: () => { + return {} + }, + specSelectors: { + isOAS3() { return true }, + url() { return "https://petstore.swagger.io/v2/swagger.json" }, + taggedOperations() { + return fromJS({ + "default": { + "operations": [ + { + "path": "/pets/{id}", + "method": "get" + }, + { + "path": "/pets/{id}", + "method": "trace" + }, + { + "path": "/pets/{id}", + "method": "foo" + }, + ] + } + }) + }, + }, + layoutSelectors: { + currentFilter() { + return null + }, + isShown() { + return true + }, + show() { + return true + } + } + } + + let wrapper = render() + + expect(wrapper.find("span.mocked-op").length).toEqual(2) + expect(wrapper.find("span.mocked-op").eq(0).attr("id")).toEqual("/pets/{id}-get") + expect(wrapper.find("span.mocked-op").eq(1).attr("id")).toEqual("/pets/{id}-trace") + }) +}) diff --git a/frontend/web/api-doc/test/unit/components/parameter-row.jsx b/frontend/web/api-doc/test/unit/components/parameter-row.jsx new file mode 100644 index 0000000..b452682 --- /dev/null +++ b/frontend/web/api-doc/test/unit/components/parameter-row.jsx @@ -0,0 +1,257 @@ +import React from "react" +import { List, fromJS } from "immutable" +import { render } from "enzyme" +import ParameterRow from "components/parameter-row" + +describe("", () => { + const createProps = ({ param, isOAS3 }) => ({ + getComponent: () => "div", + specSelectors: { + parameterWithMetaByIdentity: () => param, + isOAS3: () => isOAS3, + isSwagger2: () => !isOAS3 + }, + oas3Selectors: { activeExamplesMember: () => {} }, + param, + rawParam: param, + pathMethod: [], + getConfigs: () => ({}) + }) + + it("Can render Swagger 2 parameter type with format", () => { + const param = fromJS({ + name: "petUuid", + in: "path", + description: "UUID that identifies a pet", + type: "string", + format: "uuid" + }) + + const props = createProps({ param, isOAS3: false }) + const wrapper = render() + + expect(wrapper.find(".parameter__type").length).toEqual(1) + expect(wrapper.find(".parameter__type").text()).toEqual("string($uuid)") + }) + + it("Can render Swagger 2 parameter type without format", () => { + const param = fromJS({ + name: "petId", + in: "path", + description: "ID that identifies a pet", + type: "string" + }) + + const props = createProps({ param, isOAS3: false }) + const wrapper = render() + + expect(wrapper.find(".parameter__type").length).toEqual(1) + expect(wrapper.find(".parameter__type").text()).toEqual("string") + }) + + it("Can render Swagger 2 parameter type boolean without format", () => { + const param = fromJS({ + name: "hasId", + in: "path", + description: "boolean value to indicate if the pet has an id", + type: "boolean" + }) + + const props = createProps({ param, isOAS3: false }) + const wrapper = render() + + expect(wrapper.find(".parameter__type").length).toEqual(1) + expect(wrapper.find(".parameter__type").text()).toEqual("boolean") + }) + + it("Can render OAS3 parameter type with format", () => { + const param = fromJS({ + name: "petUuid", + in: "path", + description: "UUID that identifies a pet", + schema: { + type: "string", + format: "uuid" + } + }) + + const props = createProps({ param, isOAS3: true }) + const wrapper = render() + + expect(wrapper.find(".parameter__type").length).toEqual(1) + expect(wrapper.find(".parameter__type").text()).toEqual("string($uuid)") + }) + + it("Can render OAS3 parameter type without format", () => { + const param = fromJS({ + name: "petId", + in: "path", + description: "ID that identifies a pet", + schema: { + type: "string" + } + }) + + const props = createProps({ param, isOAS3: true }) + const wrapper = render() + + expect(wrapper.find(".parameter__type").length).toEqual(1) + expect(wrapper.find(".parameter__type").text()).toEqual("string") + }) + + it("Can render OAS3 parameter type boolean without format", () => { + const param = fromJS({ + name: "hasId", + in: "path", + description: "boolean value to indicate if the pet has an id", + schema: { + type: "boolean" + } + }) + + const props = createProps({ param, isOAS3: true }) + const wrapper = render() + + expect(wrapper.find(".parameter__type").length).toEqual(1) + expect(wrapper.find(".parameter__type").text()).toEqual("boolean") + }) +}) + +describe("bug #5573: zero default and example values", function () { + it("should apply a Swagger 2.0 default value of zero", function () { + const paramValue = fromJS({ + description: "a pet", + type: "integer", + default: 0 + }) + + let props = { + getComponent: () => "div", + specSelectors: { + security() { }, + parameterWithMetaByIdentity() { return paramValue }, + isOAS3() { return false }, + isSwagger2() { return true } + }, + fn: {}, + operation: { get: () => { } }, + onChange: jest.fn(), + param: paramValue, + rawParam: paramValue, + onChangeConsumes: () => { }, + pathMethod: [], + getConfigs: () => { return {} }, + specPath: List([]) + } + + render() + + expect(props.onChange).toHaveBeenCalled() + expect(props.onChange).toHaveBeenCalledWith(paramValue, "0", false) + }) + it("should apply a Swagger 2.0 example value of zero", function () { + const paramValue = fromJS({ + description: "a pet", + type: "integer", + schema: { + example: 0 + } + }) + + let props = { + getComponent: () => "div", + specSelectors: { + security() { }, + parameterWithMetaByIdentity() { return paramValue }, + isOAS3() { return false }, + isSwagger2() { return true } + }, + fn: {}, + operation: { get: () => { } }, + onChange: jest.fn(), + param: paramValue, + rawParam: paramValue, + onChangeConsumes: () => { }, + pathMethod: [], + getConfigs: () => { return {} }, + specPath: List([]) + } + + render() + + expect(props.onChange).toHaveBeenCalled() + expect(props.onChange).toHaveBeenCalledWith(paramValue, "0", false) + }) + it("should apply an OpenAPI 3.0 default value of zero", function () { + const paramValue = fromJS({ + description: "a pet", + schema: { + type: "integer", + default: 0 + } + }) + + let props = { + getComponent: () => "div", + specSelectors: { + security() { }, + parameterWithMetaByIdentity() { return paramValue }, + isOAS3() { return true }, + isSwagger2() { return false } + }, + oas3Selectors: { + activeExamplesMember: () => null + }, + fn: {}, + operation: { get: () => { } }, + onChange: jest.fn(), + param: paramValue, + rawParam: paramValue, + onChangeConsumes: () => { }, + pathMethod: [], + getConfigs: () => { return {} }, + specPath: List([]) + } + + render() + + expect(props.onChange).toHaveBeenCalled() + expect(props.onChange).toHaveBeenCalledWith(paramValue, "0", false) + }) + it("should apply an OpenAPI 3.0 example value of zero", function () { + const paramValue = fromJS({ + description: "a pet", + schema: { + type: "integer", + example: 0 + } + }) + + let props = { + getComponent: () => "div", + specSelectors: { + security() { }, + parameterWithMetaByIdentity() { return paramValue }, + isOAS3() { return true }, + isSwagger2() { return false } + }, + oas3Selectors: { + activeExamplesMember: () => null + }, + fn: {}, + operation: { get: () => { } }, + onChange: jest.fn(), + param: paramValue, + rawParam: paramValue, + onChangeConsumes: () => { }, + pathMethod: [], + getConfigs: () => { return {} }, + specPath: List([]) + } + + render() + + expect(props.onChange).toHaveBeenCalled() + expect(props.onChange).toHaveBeenCalledWith(paramValue, "0", false) + }) +}) diff --git a/frontend/web/api-doc/test/unit/components/primitive-model.jsx b/frontend/web/api-doc/test/unit/components/primitive-model.jsx new file mode 100644 index 0000000..f6ff3f4 --- /dev/null +++ b/frontend/web/api-doc/test/unit/components/primitive-model.jsx @@ -0,0 +1,55 @@ +import React from "react" +import { shallow } from "enzyme" +import { fromJS } from "immutable" +import PrimitiveModel from "components/primitive-model" +import ModelCollapse from "components/model-collapse" + +describe("", function () { + const dummyComponent = () => null + const components = { + Markdown: dummyComponent, + EnumModel: dummyComponent, + Property: dummyComponent, + "ModelCollapse" : ModelCollapse, + } + const props = { + getComponent: c => components[c], + getConfigs: () => ({ + showExtensions: false + }), + name: "Name from props", + depth: 1, + schema: fromJS({ + type: "string", + title: "Custom model title" + }), + expandDepth: 1 + } + + it("renders the schema's title", function () { + // When + const wrapper = shallow() + const modelTitleEl = wrapper.find("ModelCollapse").prop("title").props.children.props.children + + expect(modelTitleEl).toEqual("Custom model title") + }) + + it("falls back to the passed-in `name` prop for the title", function () { + // When + props.schema = fromJS({ + type: "string" + }) + const wrapper = shallow() + const modelTitleEl = wrapper.find("ModelCollapse").prop("title").props.children.props.children + + // Then + expect(modelTitleEl).toEqual("Name from props") + + }) + + it("renders a collapsible header", function(){ + const wrapper = shallow() + const renderedModelCollapse = wrapper.find(ModelCollapse) + expect(renderedModelCollapse.length).toEqual(1) + }) +}) diff --git a/frontend/web/api-doc/test/unit/components/response-body.jsx b/frontend/web/api-doc/test/unit/components/response-body.jsx new file mode 100644 index 0000000..654ef95 --- /dev/null +++ b/frontend/web/api-doc/test/unit/components/response-body.jsx @@ -0,0 +1,42 @@ +import React from "react" +import { shallow } from "enzyme" +import ResponseBody from "components/response-body" + +describe("", function () { + const highlightCodeComponent = () => null + const components = { + highlightCode: highlightCodeComponent + } + const props = { + getComponent: c => components[c], + } + + it("renders ResponseBody as 'application/json'", function () { + props.contentType = "application/json" + props.content = "{\"key\": \"a test value\"}" + const wrapper = shallow() + expect(wrapper.find("highlightCodeComponent").length).toEqual(1) + }) + + it("renders ResponseBody as 'text/html'", function () { + props.contentType = "application/json" + props.content = "Result" + const wrapper = shallow() + expect(wrapper.find("highlightCodeComponent").length).toEqual(1) + }) + + it("renders ResponseBody as 'image/svg'", function () { + props.contentType = "image/svg" + const wrapper = shallow() + console.warn(wrapper.debug()) + expect(wrapper.find("highlightCodeComponent").length).toEqual(0) + }) + + it("should render a copyable highlightCodeComponent for text types", function () { + props.contentType = "text/plain" + props.content = "test text" + const wrapper = shallow() + console.warn(wrapper.debug()) + expect(wrapper.find("highlightCodeComponent[canCopy]").length).toEqual(1) + }) +}) diff --git a/frontend/web/api-doc/test/unit/components/response.jsx b/frontend/web/api-doc/test/unit/components/response.jsx new file mode 100644 index 0000000..b54e332 --- /dev/null +++ b/frontend/web/api-doc/test/unit/components/response.jsx @@ -0,0 +1,63 @@ +import React from "react" +import { shallow } from "enzyme" +import { fromJS, List } from "immutable" +import Response from "components/response" +import ModelExample from "components/model-example" +import { inferSchema } from "corePlugins/samples/fn" + +describe("", function () { + const dummyComponent = () => null + const components = { + headers: dummyComponent, + highlightCode: dummyComponent, + modelExample: ModelExample, + Markdown: dummyComponent, + operationLink: dummyComponent, + contentType: dummyComponent + } + const props = { + getComponent: c => components[c], + getConfigs: () => { + return {} + }, + specSelectors: { + isOAS3() { + return false + } + }, + fn: { + inferSchema + }, + contentType: "application/json", + className: "for-test", + specPath: List(), + response: fromJS({ + schema: { + type: "object", + properties: { + // Note reverse order: c, b, a + "c": { + type: "integer" + }, + "b": { + type: "boolean" + }, + "a": { + type: "string" + } + } + } + }), + code: "200" + } + + it("renders the model-example schema properties in order", function () { + const wrapper = shallow() + const renderedModelExample = wrapper.find(ModelExample) + expect(renderedModelExample.length).toEqual(1) + + // Assert the schema's properties have maintained their order + const modelExampleSchemaProperties = renderedModelExample.props().schema.toJS().properties + expect(Object.keys(modelExampleSchemaProperties)).toEqual(["c", "b", "a"]) + }) +}) diff --git a/frontend/web/api-doc/test/unit/components/schemes-wrapper.jsx b/frontend/web/api-doc/test/unit/components/schemes-wrapper.jsx new file mode 100644 index 0000000..b23aa8c --- /dev/null +++ b/frontend/web/api-doc/test/unit/components/schemes-wrapper.jsx @@ -0,0 +1,88 @@ +import React from "react" +import { mount } from "enzyme" +import { fromJS } from "immutable" +import SchemesContainer from "containers/schemes" +import Schemes from "components/schemes" +import { Col } from "components/layout-utils" + +describe("", function(){ + + const components = { + schemes: Schemes, + Col, + authorizeBtn: () => + } + const mockedProps = { + specSelectors: { + securityDefinitions() {}, + operationScheme() {}, + schemes() {} + }, + specActions: { + setScheme() {} + }, + getComponent: c => components[c] + } + const twoSecurityDefinitions = { + "petstore_auth": { + "type": "oauth2", + "authorizationUrl": "http://petstore.swagger.io/oauth/dialog", + "flow": "implicit", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + } + + it("renders Schemes inside SchemesContainer if schemes are provided", function(){ + + // Given + let props = {...mockedProps} + props.specSelectors = {...mockedProps.specSelectors} + props.specSelectors.operationScheme = function() {return "http"} + props.specSelectors.schemes = function() {return fromJS(["http", "https"])} + + // When + let wrapper = mount() + + // Then + const renderedSchemes = wrapper.find(Schemes) + expect(renderedSchemes.length).toEqual(1) + }) + + it("does not render Schemes inside SchemeWrapper if empty array of schemes is provided", function(){ + + // Given + let props = {...mockedProps} + props.specSelectors = {...mockedProps.specSelectors} + props.specSelectors.schemes = function() {return fromJS([])} + + // When + let wrapper = mount() + + // Then + const renderedSchemes = wrapper.find(Schemes) + expect(renderedSchemes.length).toEqual(0) + }) + + it("does not render Schemes inside SchemeWrapper if provided schemes are undefined", function(){ + + // Given + let props = {...mockedProps} + props.specSelectors = {...mockedProps.specSelectors} + props.specSelectors.schemes = function() {return undefined} + + // When + let wrapper = mount() + + // Then + const renderedSchemes = wrapper.find(Schemes) + expect(renderedSchemes.length).toEqual(0) + }) +}) diff --git a/frontend/web/api-doc/test/unit/components/schemes.jsx b/frontend/web/api-doc/test/unit/components/schemes.jsx new file mode 100644 index 0000000..e34496d --- /dev/null +++ b/frontend/web/api-doc/test/unit/components/schemes.jsx @@ -0,0 +1,69 @@ +import React from "react" +import { shallow } from "enzyme" +import { fromJS } from "immutable" +import Schemes from "components/schemes" + +describe("", function(){ + it("calls props.specActions.setScheme() when no currentScheme is selected", function(){ + + let setSchemeSpy = jest.fn() + + // Given + let props = { + specActions: { + setScheme: setSchemeSpy + }, + schemes: fromJS([ + "http", + "https" + ]), + currentScheme: undefined, + path: "/test", + method: "get" + } + + // When + let wrapper = shallow() + + // Then currentScheme should default to first scheme in options list + expect(props.specActions.setScheme).toHaveBeenCalledWith("http", "/test" , "get") + + // When the currentScheme is no longer in the list of options + props.schemes = fromJS([ + "https" + ]) + wrapper.setProps(props) + + // Then currentScheme should default to first scheme in options list, again + expect(props.specActions.setScheme).toHaveBeenCalledWith("https", "/test", "get") + }) + + it("doesn't call props.specActions.setScheme() when schemes hasn't changed", function(){ + + let setSchemeSpy = jest.fn() + + // Given + let props = { + specActions: { + setScheme: setSchemeSpy + }, + schemes: fromJS([ + "http", + "https" + ]), + currentScheme: "https" + } + + // When + let wrapper = shallow() + + // Should be called initially, to set the global state + expect(setSchemeSpy.mock.calls.length).toEqual(1) + + // After an update + wrapper.instance().UNSAFE_componentWillReceiveProps(props) + + // Should not be called again, since `currentScheme` is in schemes + expect(setSchemeSpy.mock.calls.length).toEqual(1) + }) +}) diff --git a/frontend/web/api-doc/test/unit/components/version-pragma-filter.jsx b/frontend/web/api-doc/test/unit/components/version-pragma-filter.jsx new file mode 100644 index 0000000..8571916 --- /dev/null +++ b/frontend/web/api-doc/test/unit/components/version-pragma-filter.jsx @@ -0,0 +1,67 @@ +import React from "react" +import { shallow } from "enzyme" +import VersionPragmaFilter from "components/version-pragma-filter" + +describe("", function(){ + it("renders children for a Swagger 2 definition", function(){ + // When + let wrapper = shallow( + + hello! + + ) + + // Then + expect(wrapper.find("div").length).toEqual(1) + expect(wrapper.find("div").text()).toEqual("hello!") + }) + it("renders children for an OpenAPI 3 definition", function(){ + // When + let wrapper = shallow( + + hello! + + ) + + // Then + expect(wrapper.find("div").length).toEqual(1) + expect(wrapper.find("div").text()).toEqual("hello!") + }) + it("renders children when a bypass prop is set", function(){ + // When + let wrapper = shallow( + + hello! + + ) + + // Then + expect(wrapper.find("div").length).toEqual(1) + expect(wrapper.find("div").text()).toEqual("hello!") + }) + it("renders the correct message for an ambiguous-version definition", function(){ + // When + let wrapper = shallow( + + hello! + + ) + + // Then + expect(wrapper.find("div.version-pragma__message--ambiguous").length).toEqual(1) + expect(wrapper.find("div.version-pragma__message--missing").length).toEqual(0) + }) + it("renders the correct message for a missing-version definition", function(){ + // When + let wrapper = shallow( + + hello! + + ) + + // Then + expect(wrapper.find("div.version-pragma__message--missing").length).toEqual(1) + expect(wrapper.find("div.version-pragma__message--ambiguous").length).toEqual(0) + }) + +}) diff --git a/frontend/web/api-doc/test/unit/core/curlify.js b/frontend/web/api-doc/test/unit/core/curlify.js new file mode 100644 index 0000000..6a05d7b --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/curlify.js @@ -0,0 +1,389 @@ +import Im from "immutable" +import { requestSnippetGenerator_curl_bash as curl } from "core/plugins/request-snippets/fn.js" +import win from "core/window" + +describe("curlify", function () { + + it("prints a curl statement with custom content-type", function () { + const body = JSON.stringify({ + id: 0, + name: "doggie", + status: "available" + }, null, 2) + let req = { + url: "http://example.com", + method: "POST", + body, + headers: { + Accept: "application/json", + "content-type": "application/json" + } + } + + let curlified = curl(Im.fromJS(req)) + + expect(curlified).toEqual(`curl -X 'POST' \\\n 'http://example.com' \\\n -H 'Accept: application/json' \\\n -H 'content-type: application/json' \\\n -d '${body}'`) + }) + + it("does add a empty data param if no request body given", function () { + let req = { + url: "http://example.com", + method: "POST", + } + + let curlified = curl(Im.fromJS(req)) + + expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -d ''") + }) + + it("does not change the case of header in curl", function () { + let req = { + url: "http://example.com", + method: "POST", + headers: { + "conTenT Type": "application/Moar" + } + } + + let curlified = curl(Im.fromJS(req)) + + expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'conTenT Type: application/Moar' \\\n -d ''") + }) + + it("prints a curl statement with an array of query params", function () { + let req = { + url: "http://swaggerhub.com/v1/one?name=john|smith", + method: "GET" + } + + let curlified = curl(Im.fromJS(req)) + + expect(curlified).toEqual("curl -X 'GET' \\\n 'http://swaggerhub.com/v1/one?name=john|smith'") + }) + + it("prints a curl statement with an array of query params and auth", function () { + let req = { + url: "http://swaggerhub.com/v1/one?name=john|smith", + method: "GET", + headers: { + authorization: "Basic Zm9vOmJhcg==" + } + } + + let curlified = curl(Im.fromJS(req)) + + expect(curlified).toEqual("curl -X 'GET' \\\n 'http://swaggerhub.com/v1/one?name=john|smith' \\\n -H 'authorization: Basic Zm9vOmJhcg=='") + }) + + it("prints a curl statement with html", function () { + const body = { + description: "Test" + } + let req = { + url: "http://swaggerhub.com/v1/one?name=john|smith", + method: "GET", + headers: { + accept: "application/json" + }, + body + } + + let curlified = curl(Im.fromJS(req)) + + expect(curlified).toEqual(`curl -X 'GET' \\\n 'http://swaggerhub.com/v1/one?name=john|smith' \\\n -H 'accept: application/json' \\\n -d '${JSON.stringify(body, null, 2)}'`) + }) + + it("handles post body with html", function () { + let req = { + url: "http://swaggerhub.com/v1/one?name=john|smith", + method: "POST", + headers: { + accept: "application/json" + }, + body: { + description: "Test" + } + } + + let curlified = curl(Im.fromJS(req)) + + expect(curlified).toEqual(`curl -X 'POST' \\ + 'http://swaggerhub.com/v1/one?name=john|smith' \\ + -H 'accept: application/json' \\ + -d '{ + "description": "Test" +}'`) + }) + + it("handles post body with special chars", function () { + let req = { + url: "http://swaggerhub.com/v1/one?name=john|smith", + method: "POST", + body: { + description: "@prefix nif: .\n" + + "@prefix itsrdf: ." + } + } + + let curlified = curl(Im.fromJS(req)) + + expect(curlified).toEqual("curl -X 'POST' \\\n 'http://swaggerhub.com/v1/one?name=john|smith' \\\n -d '{\n \"description\": \"@prefix nif: .\\n@prefix itsrdf: .\"\n}'") + }) + + it("handles delete form with parameters", function () { + let req = { + url: "http://example.com", + method: "DELETE", + headers: { + accept: "application/x-www-form-urlencoded" + } + } + + let curlified = curl(Im.fromJS(req)) + + expect(curlified).toEqual("curl -X 'DELETE' \\\n 'http://example.com' \\\n -H 'accept: application/x-www-form-urlencoded'") + }) + + it("should print a curl with formData", function () { + let req = { + url: "http://example.com", + method: "POST", + headers: { "content-type": "multipart/form-data" }, + body: { + id: "123", + name: "Sahar" + } + } + + let curlified = curl(Im.fromJS(req)) + + expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'content-type: multipart/form-data' \\\n -F 'id=123' \\\n -F 'name=Sahar'") + }) + + it("should print a curl with formData that extracts array representation with hashIdx", function () { + // Note: hashIdx = `_**[]${counter}` + // Usage of hashIdx is an internal SwaggerUI method to convert formData array into something curlify can handle + const req = { + url: "http://example.com", + method: "POST", + headers: { "content-type": "multipart/form-data" }, + body: { + id: "123", + "fruits[]_**[]1": "apple", + "fruits[]_**[]2": "banana", + "fruits[]_**[]3": "grape" + } + } + + let curlified = curl(Im.fromJS(req)) + + expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'content-type: multipart/form-data' \\\n -F 'id=123' \\\n -F 'fruits[]=apple' \\\n -F 'fruits[]=banana' \\\n -F 'fruits[]=grape'") + }) + + it("should print a curl with formData and file", function () { + let file = new win.File([""], "file.txt", { type: "text/plain" }) + // file.name = "file.txt" + // file.type = "text/plain" + + let req = { + url: "http://example.com", + method: "POST", + headers: { "content-type": "multipart/form-data" }, + body: { + id: "123", + file + } + } + + let curlified = curl(Im.fromJS(req)) + + expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'content-type: multipart/form-data' \\\n -F 'id=123' \\\n -F 'file=@file.txt;type=text/plain'") + }) + + it("should print a curl without form data type if type is unknown", function () { + let file = new win.File([""], "file.txt", { type: "" }) + // file.name = "file.txt" + // file.type = "" + + let req = { + url: "http://example.com", + method: "POST", + headers: { "content-type": "multipart/form-data" }, + body: { + id: "123", + file + } + } + + let curlified = curl(Im.fromJS(req)) + + expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'content-type: multipart/form-data' \\\n -F 'id=123' \\\n -F 'file=@file.txt'") + }) + + it("should print a curl with data-binary if body is instance of File and it is not a multipart form data request", function () { + let file = new win.File([""], "file.txt", { type: "" }) + + let req = { + url: "http://example.com", + method: "POST", + headers: { "content-type": "application/octet-stream" }, + body: file + } + + let curlified = curl(Im.fromJS(req)) + + expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'content-type: application/octet-stream' \\\n --data-binary '@file.txt'") + }) + + it("prints a curl post statement from an object", function () { + let req = { + url: "http://example.com", + method: "POST", + headers: { + accept: "application/json" + }, + body: { + id: 10101 + } + } + + let curlified = curl(Im.fromJS(req)) + + expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'accept: application/json' \\\n -d '{\n \"id\": 10101\n}'") + }) + + it("prints a curl post statement from a string containing a single quote", function () { + let req = { + url: "http://example.com", + method: "POST", + headers: { + accept: "application/json" + }, + body: "{\"id\":\"foo'bar\"}" + } + + let curlified = curl(Im.fromJS(req)) + + expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'accept: application/json' \\\n -d '{\"id\":\"foo'\\''bar\"}'") + }) + + describe("given multiple entries with file", function () { + describe("and with leading custom header", function () { + it("should print a proper curl -F", function () { + let file = new win.File([""], "file.txt", { type: "text/plain" }) + // file.name = "file.txt" + // file.type = "text/plain" + + let req = { + url: "http://example.com", + method: "POST", + headers: { + "x-custom-name": "multipart/form-data", + "content-type": "multipart/form-data" + }, + body: { + id: "123", + file + } + } + + let curlified = curl(Im.fromJS(req)) + + expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'x-custom-name: multipart/form-data' \\\n -H 'content-type: multipart/form-data' \\\n -F 'id=123' \\\n -F 'file=@file.txt;type=text/plain'") + }) + }) + + describe("and with trailing custom header; e.g. from requestInterceptor appending req.headers", function () { + it("should print a proper curl -F", function () { + let file = new win.File([""], "file.txt", { type: "text/plain" }) + // file.name = "file.txt" + // file.type = "text/plain" + + let req = { + url: "http://example.com", + method: "POST", + headers: { + "content-type": "multipart/form-data", + "x-custom-name": "any-value" + }, + body: { + id: "123", + file + } + } + + let curlified = curl(Im.fromJS(req)) + + expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'content-type: multipart/form-data' \\\n -H 'x-custom-name: any-value' \\\n -F 'id=123' \\\n -F 'file=@file.txt;type=text/plain'") + }) + }) + }) + + describe("POST when header value is 'multipart/form-data' but header name is not 'content-type'", function () { + it("should print a proper curl as -d , when file type is provided", function () { + let file = new win.File([""], "file.txt", { type: "text/plain" }) + // file.name = "file.txt" + // file.type = "text/plain" + + let req = { + url: "http://example.com", + method: "POST", + headers: { "x-custom-name": "multipart/form-data" }, + body: { + id: "123", + file + } + } + + let curlified = curl(Im.fromJS(req)) + + expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'x-custom-name: multipart/form-data' \\\n -d '{\n \"id\": \"123\",\n \"file\": {\n \"name\": \"file.txt\",\n \"type\": \"text/plain\"\n }\n}'") + }) + + it("should print a proper curl as -d , no file type provided", function () { + let file = new win.File([""], "file.txt") + // file.name = "file.txt" + // file.type = "text/plain" + + let req = { + url: "http://example.com", + method: "POST", + headers: { "x-custom-name": "multipart/form-data" }, + body: { + id: "123", + file + } + } + + let curlified = curl(Im.fromJS(req)) + + expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'x-custom-name: multipart/form-data' \\\n -d '{\n \"id\": \"123\",\n \"file\": {\n \"name\": \"file.txt\"\n }\n}'") + }) + }) + + it("should include curlOptions from the request in the curl command", function () { + let req = { + url: "http://example.com", + method: "GET", + headers: { "X-DOLLAR": "token/123$" }, + curlOptions: ["-g"] + } + + let curlified = curl(Im.fromJS(req)) + + expect(curlified).toEqual("curl -g -X 'GET' \\\n 'http://example.com' \\\n -H 'X-DOLLAR: token/123$'") + }) + + it("should include multiple curlOptions from the request in the curl command", function () { + let req = { + url: "http://example.com", + method: "GET", + headers: { "X-DOLLAR": "token/123$" }, + curlOptions: ["-g", "--limit-rate 20k"] + } + + let curlified = curl(Im.fromJS(req)) + + expect(curlified).toEqual("curl -g --limit-rate 20k -X 'GET' \\\n 'http://example.com' \\\n -H 'X-DOLLAR: token/123$'") + }) +}) diff --git a/frontend/web/api-doc/test/unit/core/helpers/get-model-name.js b/frontend/web/api-doc/test/unit/core/helpers/get-model-name.js new file mode 100644 index 0000000..2dbdd99 --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/helpers/get-model-name.js @@ -0,0 +1,39 @@ +/** + * @prettier + */ + +import Model from "../../../../src/core/components/model" + +describe("getModelName", () => { + const model = new Model() + + it("should decode JSON Pointer and URI encoding for OpenAPI v3 refs", () => { + const actual = model.getModelName("#/components/schemas/a~1b%2Bc") + const expected = "a/b+c" + + expect(actual).toStrictEqual(expected) + }) + + it("should decode JSON Pointer and URI encoding for Swagger v2 refs", () => { + const actual = model.getModelName( + "#/definitions/custom%3A%3Anamespace%3A%3APerson" + ) + const expected = "custom::namespace::Person" + + expect(actual).toStrictEqual(expected) + }) + + it("should decode multiple json-pointer values", () => { + const actual = model.getModelName("#/components/schemas/~1~1~0~0") + const expected = "//~~" + + expect(actual).toStrictEqual(expected) + }) + + it("should support invalid URI encoding", () => { + const actual = model.getModelName("#/components/schemas/%25%d") + const expected = "%25%d" + + expect(actual).toStrictEqual(expected) + }) +}) diff --git a/frontend/web/api-doc/test/unit/core/helpers/get-parameter-schema.js b/frontend/web/api-doc/test/unit/core/helpers/get-parameter-schema.js new file mode 100644 index 0000000..47da1b3 --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/helpers/get-parameter-schema.js @@ -0,0 +1,147 @@ +/** + * @prettier + */ + +import { fromJS } from "immutable" +import getParameterSchema from "../../../../src/helpers/get-parameter-schema" + +describe("getParameterSchema", () => { + it("should return an empty Map when given no parameters", () => { + const result = getParameterSchema() + + expect(result.schema.toJS()).toEqual({}) + expect(result.parameterContentMediaType).toEqual(null) + }) + + it("should return an empty Map when given an empty Map", () => { + const result = getParameterSchema(fromJS({})) + + expect(result.schema.toJS()).toEqual({}) + expect(result.parameterContentMediaType).toEqual(null) + }) + + it("should return a schema for a Swagger 2.0 query parameter", () => { + const result = getParameterSchema( + fromJS({ + name: "id", + in: "query", + description: "ID of the object to fetch", + required: false, + type: "array", + items: { + type: "string", + }, + collectionFormat: "multi", + }) + ) + + expect(result.schema.toJS()).toEqual({ + type: "array", + items: { + type: "string", + }, + }) + expect(result.parameterContentMediaType).toEqual(null) + }) + + it("should return a schema for a Swagger 2.0 body parameter", () => { + const result = getParameterSchema( + fromJS({ + name: "user", + in: "body", + description: "user to add to the system", + required: true, + schema: { + type: "array", + items: { + type: "string", + }, + }, + }) + ) + + expect(result.schema.toJS()).toEqual({ + type: "array", + items: { + type: "string", + }, + }) + expect(result.parameterContentMediaType).toEqual(null) + }) + + it("should return a schema for an OpenAPI 3.0 query parameter", () => { + const result = getParameterSchema( + fromJS({ + name: "id", + in: "query", + description: "ID of the object to fetch", + required: false, + schema: { + type: "array", + items: { + type: "string", + }, + }, + style: "form", + explode: true, + }), + { + isOAS3: true, + } + ) + + expect(result.schema.toJS()).toEqual({ + type: "array", + items: { + type: "string", + }, + }) + expect(result.parameterContentMediaType).toEqual(null) + }) + + it("should return a schema for an OpenAPI 3.0 query parameter with `content`", () => { + const result = getParameterSchema( + fromJS({ + in: "query", + name: "coordinates", + content: { + "application/json": { + schema: { + type: "object", + required: ["lat", "long"], + properties: { + lat: { + type: "number", + }, + long: { + type: "number", + }, + }, + }, + "should-ignore/the-second-media-type": { + type: "string", + default: "this shouldn't be returned", + }, + }, + }, + }), + { + isOAS3: true, + } + ) + + expect(result.schema.toJS()).toEqual({ + type: "object", + required: ["lat", "long"], + properties: { + lat: { + type: "number", + }, + long: { + type: "number", + }, + }, + }) + expect(result.parameterContentMediaType).toEqual(`application/json`) + }) +}) diff --git a/frontend/web/api-doc/test/unit/core/oauth2-authorize.js b/frontend/web/api-doc/test/unit/core/oauth2-authorize.js new file mode 100644 index 0000000..5a3b11f --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/oauth2-authorize.js @@ -0,0 +1,201 @@ + +import Im from "immutable" +import oauth2Authorize from "core/oauth2-authorize" +import * as utils from "core/utils" + +describe("oauth2", () => { + + let mockSchema = { + flow: "accessCode", + authorizationUrl: "https://testAuthorizationUrl" + } + + let authConfig = { + auth: { schema: { get: (key)=> mockSchema[key] }, scopes: ["scope1", "scope2"] }, + authActions: { + authPopup: jest.fn() + }, + errActions: {}, + configs: { oauth2RedirectUrl: "" }, + authConfigs: {} + } + + let authConfig2 = { + auth: { schema: { get: (key)=> mockSchema[key] }, scopes: Im.List(["scope2","scope3"]) }, + authActions: { + authPopup: jest.fn() + }, + errActions: {}, + configs: { oauth2RedirectUrl: "" }, + authConfigs: {} + } + + let authConfig3 = { + auth: { schema: { get: (key)=> mockSchema[key] }, scopes: Im.List(["scope4","scope5"]) }, + authActions: { + authPopup: jest.fn() + }, + errActions: {}, + configs: { oauth2RedirectUrl: "" }, + authConfigs: {} + } + + describe("authorize redirect", () => { + it("should build authorize url", () => { + oauth2Authorize(authConfig) + expect(authConfig.authActions.authPopup.mock.calls.length).toEqual(1) + expect(authConfig.authActions.authPopup.mock.calls[0][0]).toMatch("https://testAuthorizationUrl?response_type=code&redirect_uri=&scope=scope1%20scope2&state=") + + authConfig.authActions.authPopup.mockReset() + }) + + it("should build authorize url relative", function () { + let relativeMockSchema = { + flow: "accessCode", + authorizationUrl: "/testAuthorizationUrl" + } + let relativeAuthConfig = { + auth: { schema: { get: (key) => relativeMockSchema[key] } }, + authActions: { + authPopup: jest.fn() + }, + errActions: {}, + configs: { oauth2RedirectUrl: "" }, + authConfigs: {}, + currentServer: "https://currentserver" + } + oauth2Authorize(relativeAuthConfig) + expect(relativeAuthConfig.authActions.authPopup.mock.calls.length).toEqual(1) + expect(relativeAuthConfig.authActions.authPopup.mock.calls[0][0]).toMatch("https://currentserver/testAuthorizationUrl?response_type=code&redirect_uri=&state=") + + relativeAuthConfig.authActions.authPopup.mockReset() + }) + + it("should append query parameters to authorizeUrl with query parameters", () => { + mockSchema.authorizationUrl = "https://testAuthorizationUrl?param=1" + oauth2Authorize(authConfig) + + expect(authConfig.authActions.authPopup.mock.calls.length).toEqual(1) + expect(authConfig.authActions.authPopup.mock.calls[0][0]).toMatch("https://testAuthorizationUrl?param=1&response_type=code&redirect_uri=&scope=scope1%20scope2&state=") + + authConfig.authActions.authPopup.mockReset() + }) + + it("should send code_challenge when using authorizationCode flow with usePkceWithAuthorizationCodeGrant enabled", () => { + mockSchema.flow = "authorizationCode" + + const expectedCodeVerifier = "mock_code_verifier" + const expectedCodeChallenge = "mock_code_challenge" + + const generateCodeVerifierSpy = jest.spyOn(utils, "generateCodeVerifier").mockImplementation(() => expectedCodeVerifier) + const createCodeChallengeSpy = jest.spyOn(utils, "createCodeChallenge").mockImplementation(() => expectedCodeChallenge) + + authConfig.authConfigs.usePkceWithAuthorizationCodeGrant = true + + oauth2Authorize(authConfig) + expect(authConfig.authActions.authPopup.mock.calls.length).toEqual(1) + + const actualUrl = new URLSearchParams(authConfig.authActions.authPopup.mock.calls[0][0]) + expect(actualUrl.get("code_challenge")).toBe(expectedCodeChallenge) + expect(actualUrl.get("code_challenge_method")).toBe("S256") + + expect(createCodeChallengeSpy.mock.calls.length).toEqual(1) + expect(createCodeChallengeSpy.mock.calls[0][0]).toBe(expectedCodeVerifier) + + // The code_verifier should be stored to be able to send in + // on the TokenUrl call + expect(authConfig.auth.codeVerifier).toBe(expectedCodeVerifier) + + // Restore spies + authConfig.authActions.authPopup.mockReset() + generateCodeVerifierSpy.mockReset() + createCodeChallengeSpy.mockReset() + }) + + + it("should send code_challenge when using accessCode flow with usePkceWithAuthorizationCodeGrant enabled", () => { + mockSchema.flow = "accessCode" + + const expectedCodeVerifier = "mock_code_verifier" + const expectedCodeChallenge = "mock_code_challenge" + + const generateCodeVerifierSpy = jest.spyOn(utils, "generateCodeVerifier").mockImplementation(() => expectedCodeVerifier) + const createCodeChallengeSpy = jest.spyOn(utils, "createCodeChallenge").mockImplementation(() => expectedCodeChallenge) + + authConfig.authConfigs.useBasicAuthenticationWithAccessCodeGrant = true + authConfig.authConfigs.usePkceWithAuthorizationCodeGrant = true + + oauth2Authorize(authConfig) + + expect(authConfig.authActions.authPopup.mock.calls.length).toEqual(1) + + const actualUrl = new URLSearchParams(authConfig.authActions.authPopup.mock.calls[0][0]) + expect(actualUrl.get("code_challenge")).toBe(expectedCodeChallenge) + expect(actualUrl.get("code_challenge_method")).toBe("S256") + + expect(createCodeChallengeSpy.mock.calls.length).toEqual(1) + expect(createCodeChallengeSpy.mock.calls[0][0]).toBe(expectedCodeVerifier) + + // The code_verifier should be stored to be able to send in + // on the TokenUrl call + expect(authConfig.auth.codeVerifier).toBe(expectedCodeVerifier) + + // Restore spies + authConfig.authActions.authPopup.mockReset() + generateCodeVerifierSpy.mockReset() + createCodeChallengeSpy.mockReset() + }) + + it("should send code_challenge when using authorization_code flow with usePkceWithAuthorizationCodeGrant enabled", () => { + mockSchema.flow = "authorization_code" + + const expectedCodeVerifier = "mock_code_verifier" + const expectedCodeChallenge = "mock_code_challenge" + + const generateCodeVerifierSpy = jest.spyOn(utils, "generateCodeVerifier").mockImplementation(() => expectedCodeVerifier) + const createCodeChallengeSpy = jest.spyOn(utils, "createCodeChallenge").mockImplementation(() => expectedCodeChallenge) + + authConfig.authConfigs.usePkceWithAuthorizationCodeGrant = true + + oauth2Authorize(authConfig) + expect(authConfig.authActions.authPopup.mock.calls.length).toEqual(1) + + const actualUrl = new URLSearchParams(authConfig.authActions.authPopup.mock.calls[0][0]) + expect(actualUrl.get("code_challenge")).toBe(expectedCodeChallenge) + expect(actualUrl.get("code_challenge_method")).toBe("S256") + + expect(createCodeChallengeSpy.mock.calls.length).toEqual(1) + expect(createCodeChallengeSpy.mock.calls[0][0]).toBe(expectedCodeVerifier) + + // The code_verifier should be stored to be able to send in + // on the TokenUrl call + expect(authConfig.auth.codeVerifier).toBe(expectedCodeVerifier) + + // Restore spies + authConfig.authActions.authPopup.mockReset() + generateCodeVerifierSpy.mockReset() + createCodeChallengeSpy.mockReset() + }) + + it("should add list of scopes to authorizeUrl", () => { + mockSchema.authorizationUrl = "https://testAuthorizationUrl?param=1" + + oauth2Authorize(authConfig2) + expect(authConfig2.authActions.authPopup.mock.calls.length).toEqual(1) + expect(authConfig2.authActions.authPopup.mock.calls[0][0]).toMatch("https://testAuthorizationUrl?param=1&response_type=code&redirect_uri=&scope=scope2%20scope3&state=") + + authConfig2.authActions.authPopup.mockReset() + }) + + it("should build authorize url for authorization_code flow", () => { + mockSchema.authorizationUrl = "https://testAuthorizationUrl" + mockSchema.flow = "authorization_code" + + oauth2Authorize(authConfig3) + expect(authConfig3.authActions.authPopup.mock.calls.length).toEqual(1) + expect(authConfig3.authActions.authPopup.mock.calls[0][0]).toMatch("https://testAuthorizationUrl?response_type=code&redirect_uri=&scope=scope4%20scope5&state=") + + authConfig3.authActions.authPopup.mockReset() + }) + }) +}) diff --git a/frontend/web/api-doc/test/unit/core/plugins/auth/actions.js b/frontend/web/api-doc/test/unit/core/plugins/auth/actions.js new file mode 100644 index 0000000..f73ee1e --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/plugins/auth/actions.js @@ -0,0 +1,346 @@ +import { Map } from "immutable" +import win from "core/window" +import { + authorizeRequest, + authorizeAccessCodeWithFormParams, + authorizeWithPersistOption, + authorizeOauth2WithPersistOption, + logoutWithPersistOption, + persistAuthorizationIfNeeded +} from "corePlugins/auth/actions" +import {authorizeAccessCodeWithBasicAuthentication, authPopup} from "../../../../../src/core/plugins/auth/actions" + +describe("auth plugin - actions", () => { + + describe("authorizeRequest", () => { + + [ + [ + { + oas3: true, + server: "https://host/resource", + effectiveServer: "https://host/resource", + scheme: "http", + host: null, + url: "http://specs/file", + }, + "https://host/authorize" + ], + [ + { + oas3: true, + server: "https://{selected_host}/resource", + effectiveServer: "https://host/resource", + scheme: "http", + host: null, + url: "http://specs/file", + }, + "https://host/authorize" + ], + [ + { + oas3: false, + server: null, + effectiveServer: null, + scheme: "https", + host: undefined, + url: "https://specs/file", + }, + "https://specs/authorize" + ], + [ + { + oas3: false, + server: null, + effectiveServer: null, + scheme: "https", + host: "host", + url: "http://specs/file", + }, + "http://specs/authorize" + ], + ].forEach(([{ oas3, server, effectiveServer, scheme, host, url }, expectedFetchUrl]) => { + it("should resolve authorization endpoint against the server URL", () => { + + // Given + const data = { + url: "/authorize" + } + const system = { + fn: { + fetch: jest.fn().mockImplementation(() => Promise.resolve()) + }, + getConfigs: () => ({}), + authSelectors: { + getConfigs: () => ({}) + }, + errActions: { + newAuthErr: () => ({}) + }, + oas3Selectors: { + selectedServer: () => server, + serverEffectiveValue: () => effectiveServer || server + }, + specSelectors: { + isOAS3: () => oas3, + operationScheme: () => scheme, + host: () => host, + url: () => url + } + } + + // When + authorizeRequest(data)(system) + + // Then + expect(system.fn.fetch.mock.calls.length).toEqual(1) + expect(system.fn.fetch.mock.calls[0][0]).toEqual(expect.objectContaining({ url: expectedFetchUrl })) + }) + }) + + it("should add additionalQueryStringParams to Swagger 2.0 authorization and token URLs", () => { + + // Given + const data = { + url: "/authorize?q=1" + } + const system = { + fn: { + fetch: jest.fn().mockImplementation(() => Promise.resolve()) + }, + getConfigs: () => ({}), + authSelectors: { + getConfigs: () => ({ + additionalQueryStringParams: { + myCustomParam: "abc123" + } + }) + }, + errActions: { + newAuthErr: () => ({}) + }, + specSelectors: { + isOAS3: () => false, + operationScheme: () => "https", + host: () => "http://google.com", + url: () => "http://google.com/swagger.json" + } + } + + // When + authorizeRequest(data)(system) + + // Then + expect(system.fn.fetch.mock.calls.length).toEqual(1) + + expect(system.fn.fetch.mock.calls[0][0].url) + .toEqual("http://google.com/authorize?q=1&myCustomParam=abc123") + }) + + it("should add additionalQueryStringParams to OpenAPI 3.0 authorization and token URLs", () => { + + // Given + const data = { + url: "/authorize?q=1" + } + const system = { + fn: { + fetch: jest.fn().mockImplementation(() => Promise.resolve()) + }, + errActions: { + newAuthErr: () => ({}) + }, + getConfigs: () => ({}), + authSelectors: { + getConfigs: () => ({ + additionalQueryStringParams: { + myCustomParam: "abc123" + } + }) + }, + oas3Selectors: { + selectedServer: () => "http://google.com", + serverEffectiveValue: () => "http://google.com" + }, + specSelectors: { + isOAS3: () => true, + } + } + + // When + authorizeRequest(data)(system) + + // Then + expect(system.fn.fetch.mock.calls.length).toEqual(1) + + expect(system.fn.fetch.mock.calls[0][0].url) + .toEqual("http://google.com/authorize?q=1&myCustomParam=abc123") + }) + }) + + describe("tokenRequest", function () { + it("should send the code verifier when set", () => { + const testCodeVerifierForAuthorizationCodeFlows = (flowAction) => { + const data = { + auth: { + schema: { + get: () => "http://tokenUrl", + }, + codeVerifier: "mock_code_verifier", + }, + redirectUrl: "http://google.com", + } + + const authActions = { + authorizeRequest: jest.fn(), + } + + flowAction(data)({ authActions }) + + expect(authActions.authorizeRequest.mock.calls.length).toEqual(1) + const actualArgument = authActions.authorizeRequest.mock.calls[0][0] + expect(actualArgument.body).toContain("code_verifier=" + data.auth.codeVerifier) + expect(actualArgument.body).toContain("grant_type=authorization_code") + } + + testCodeVerifierForAuthorizationCodeFlows(authorizeAccessCodeWithFormParams) + testCodeVerifierForAuthorizationCodeFlows(authorizeAccessCodeWithBasicAuthentication) + }) + }) + + describe("persistAuthorization", () => { + describe("wrapped functions with persist option", () => { + it("should wrap `authorize` action and persist data if needed", () => { + + // Given + const data = { + "api_key": {} + } + const system = { + getConfigs: () => ({}), + authActions: { + authorize: jest.fn(() => { }), + persistAuthorizationIfNeeded: jest.fn(() => { }) + } + } + + // When + authorizeWithPersistOption(data)(system) + + // Then + expect(system.authActions.authorize).toHaveBeenCalled() + expect(system.authActions.authorize).toHaveBeenCalledWith(data) + expect(system.authActions.persistAuthorizationIfNeeded).toHaveBeenCalled() + }) + + it("should wrap `oauth2Authorize` action and persist data if needed", () => { + + // Given + const data = { + "api_key": {} + } + const system = { + getConfigs: () => ({}), + authActions: { + authorizeOauth2: jest.fn(() => { }), + persistAuthorizationIfNeeded: jest.fn(() => { }) + } + } + + // When + authorizeOauth2WithPersistOption(data)(system) + + // Then + expect(system.authActions.authorizeOauth2).toHaveBeenCalled() + expect(system.authActions.authorizeOauth2).toHaveBeenCalledWith(data) + expect(system.authActions.persistAuthorizationIfNeeded).toHaveBeenCalled() + }) + + it("should wrap `logout` action and persist data if needed", () => { + + // Given + const data = { + "api_key": {} + } + const system = { + getConfigs: () => ({}), + authActions: { + logout: jest.fn(() => { }), + persistAuthorizationIfNeeded: jest.fn(() => { }) + } + } + + // When + logoutWithPersistOption(data)(system) + + // Then + expect(system.authActions.logout).toHaveBeenCalled() + expect(system.authActions.logout).toHaveBeenCalledWith(data) + expect(system.authActions.persistAuthorizationIfNeeded).toHaveBeenCalled() + }) + }) + + describe("persistAuthorizationIfNeeded", () => { + beforeEach(() => { + localStorage.clear() + }) + it("should skip if `persistAuthorization` is turned off", () => { + // Given + const system = { + getConfigs: () => ({ + persistAuthorization: false + }), + authSelectors: { + authorized: jest.fn(() => { }) + } + } + + // When + persistAuthorizationIfNeeded()(system) + + // Then + expect(system.authSelectors.authorized).not.toHaveBeenCalled() + }) + it("should persist authorization data to localStorage", () => { + // Given + const data = { + "api_key": {} + } + const system = { + getConfigs: () => ({ + persistAuthorization: true + }), + errActions: { + newAuthErr: () => ({}) + }, + authSelectors: { + authorized: jest.fn(() => Map(data)) + } + } + jest.spyOn(Object.getPrototypeOf(window.localStorage), "setItem") + + // When + persistAuthorizationIfNeeded()(system) + + expect(localStorage.setItem).toHaveBeenCalled() + expect(localStorage.setItem).toHaveBeenCalledWith("authorized", JSON.stringify(data)) + + }) + }) + + describe("authPopup", () => { + beforeEach(() => { + win.open = jest.fn() + }) + it("should call win.open with url", () => { + const windowOpenSpy = jest.spyOn(win, "open") + + authPopup("http://swagger.ui", {})() + + expect(windowOpenSpy.mock.calls.length).toEqual(1) + windowOpenSpy.mockReset() + }) + }) + + }) +}) diff --git a/frontend/web/api-doc/test/unit/core/plugins/auth/preauthorize.js b/frontend/web/api-doc/test/unit/core/plugins/auth/preauthorize.js new file mode 100644 index 0000000..a42f1c4 --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/plugins/auth/preauthorize.js @@ -0,0 +1,153 @@ + +import { fromJS } from "immutable" +import { preauthorizeBasic, preauthorizeApiKey } from "corePlugins/auth" +import { authorize } from "corePlugins/auth/actions" + +const S2_SYSTEM = { + authActions: { + authorize + }, + specSelectors: { + isOAS3: () => false, + specJson: () => { + return fromJS({ + swagger: "2.0", + securityDefinitions: { + "APIKeyHeader": { + "type": "apiKey", + "in": "header", + "name": "X-API-Key" + }, + "basicAuth": { + "type": "basic" + } + } + }) + } + } +} + +const OAI3_SYSTEM = { + authActions: { + authorize + }, + specSelectors: { + isOAS3: () => true, + specJson: () => { + return fromJS({ + openapi: "3.0.0", + components: { + securitySchemes: { + basicAuth: { + type: "http", + scheme: "basic" + }, + APIKeyHeader: { + type: "apiKey", + in: "header", + name: "X-API-Key" + } + } + } + }) + } + } +} + +describe("auth plugin - preauthorizers", () => { + describe("preauthorizeBasic", () => { + it("should return a valid authorize action in Swagger 2", () => { + const res = preauthorizeBasic(S2_SYSTEM, "basicAuth", "user", "pass") + + expect(res).toEqual({ + type: "authorize", + payload: { + basicAuth: { + schema: { + type: "basic" + }, + value: { + username: "user", + password: "pass" + } + } + } + }) + }) + it("should return a valid authorize action in OpenAPI 3", () => { + const res = preauthorizeBasic(OAI3_SYSTEM, "basicAuth", "user", "pass") + + expect(res).toEqual({ + type: "authorize", + payload: { + basicAuth: { + schema: { + type: "http", + scheme: "basic" + }, + value: { + username: "user", + password: "pass" + } + } + } + }) + }) + it("should return null when the authorization name is invalid in Swagger 2", () => { + const res = preauthorizeBasic(S2_SYSTEM, "fakeBasicAuth", "user", "pass") + + expect(res).toEqual(null) + }) + it("should return null when the authorization name is invalid in OpenAPI 3", () => { + const res = preauthorizeBasic(OAI3_SYSTEM, "fakeBasicAuth", "user", "pass") + + expect(res).toEqual(null) + }) + }) + describe("preauthorizeApiKey", () => { + it("should return a valid authorize action in Swagger 2", () => { + const res = preauthorizeApiKey(S2_SYSTEM, "APIKeyHeader", "Asdf1234") + + expect(res).toEqual({ + type: "authorize", + payload: { + APIKeyHeader: { + schema: { + type: "apiKey", + name: "X-API-Key", + "in": "header" + }, + value: "Asdf1234" + } + } + }) + }) + it("should return a valid authorize action in OpenAPI 3", () => { + const res = preauthorizeApiKey(OAI3_SYSTEM, "APIKeyHeader", "Asdf1234") + + expect(res).toEqual({ + type: "authorize", + payload: { + APIKeyHeader: { + schema: { + type: "apiKey", + "in": "header", + name: "X-API-Key" + }, + value: "Asdf1234" + } + } + }) + }) + it("should return null when the authorization name is invalid in Swagger 2", () => { + const res = preauthorizeApiKey(S2_SYSTEM, "FakeAPIKeyHeader", "Asdf1234") + + expect(res).toEqual(null) + }) + it("should return null when the authorization name is invalid in OpenAPI 3", () => { + const res = preauthorizeApiKey(OAI3_SYSTEM, "FakeAPIKeyHeader", "Asdf1234") + + expect(res).toEqual(null) + }) + }) +}) diff --git a/frontend/web/api-doc/test/unit/core/plugins/auth/selectors.js b/frontend/web/api-doc/test/unit/core/plugins/auth/selectors.js new file mode 100644 index 0000000..2b6c150 --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/plugins/auth/selectors.js @@ -0,0 +1,228 @@ + +import { fromJS } from "immutable" +import { definitionsToAuthorize, definitionsForRequirements } from "corePlugins/auth/selectors" + +describe("auth plugin - selectors", () => { + describe("definitionsToAuthorize", () => { + it("should return securityDefinitions as a List", () => { + const securityDefinitions = { + "petstore_auth": { + "type": "oauth2", + "authorizationUrl": "http://petstore.swagger.io/oauth/dialog", + "flow": "implicit", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + } + + const system = { + specSelectors: { + securityDefinitions() { + return fromJS(securityDefinitions) + } + } + } + + const res = definitionsToAuthorize({})(system) + + expect(res.toJS()).toEqual([ + { + "petstore_auth": securityDefinitions["petstore_auth"] + }, + { + "api_key": securityDefinitions["api_key"] + }, + ]) + }) + + it("should fail gracefully with bad data", () => { + const securityDefinitions = null + + const system = { + specSelectors: { + securityDefinitions() { + return fromJS(securityDefinitions) + } + } + } + + const res = definitionsToAuthorize({})(system) + + expect(res.toJS()).toEqual([]) + }) + }) + + describe("definitionsForRequirements", () => { + it("should return applicable securityDefinitions as a List", () => { + const securityDefinitions = { + "petstore_auth": { + "type": "oauth2", + "authorizationUrl": "http://petstore.swagger.io/oauth/dialog", + "flow": "implicit", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + } + + const system = { + authSelectors: { + definitionsToAuthorize() { + return fromJS([ + { + "petstore_auth": securityDefinitions["petstore_auth"] + }, + { + "api_key": securityDefinitions["api_key"] + }, + ]) + } + } + } + + const securities = fromJS([ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ]) + + const res = definitionsForRequirements({}, securities)(system) + + expect(res.toJS()).toEqual([ + { + "petstore_auth": securityDefinitions["petstore_auth"] + } + ]) + }) + + it("should fail gracefully with bad data", () => { + const securityDefinitions = null + + const system = { + authSelectors: { + definitionsToAuthorize() { + return null + } + } + } + + const securities = null + + const res = definitionsForRequirements({}, securities)(system) + + expect(res.toJS()).toEqual([]) + }) + }) + + it("should return only security definitions used by the endpoint", () => { + const securityDefinitions = { + "used": { + "type": "http", + "scheme": "basic", + }, + "unused": { + "type": "http", + "scheme": "basic", + } + } + + const system = { + authSelectors: { + definitionsToAuthorize() { + return fromJS([ + { + "used": securityDefinitions["used"] + }, + { + "unused": securityDefinitions["unused"] + }, + ]) + } + } + } + + const securities = fromJS([ + { + "used": [], + "undefined": [], + } + ]) + + const res = definitionsForRequirements({}, securities)(system) + + expect(res.toJS()).toEqual([ + { + "used": securityDefinitions["used"] + } + ]) + }) + + it("should return only oauth scopes used by the endpoint", () => { + const securityDefinitions = { + "oauth2": { + "type": "oauth2", + "flow": "clientCredentials", + "tokenUrl": "https://api.testserver.com/oauth2/token/", + "scopes": { + "used": "foo", + "unused": "bar" + } + }, + "other": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + + } + + const system = { + authSelectors: { + definitionsToAuthorize() { + return fromJS([ + { + "oauth2": securityDefinitions["oauth2"], + "other": securityDefinitions["other"], + }, + ]) + } + } + } + + const securities = fromJS([ + { + "oauth2": ["used", "undefined"], + "other": [], + } + ]) + + let expectedOauth2Definitions = {...securityDefinitions["oauth2"]} + expectedOauth2Definitions["scopes"] = {"used": "foo"} + + const res = definitionsForRequirements({}, securities)(system) + + expect(res.toJS()).toEqual([ + { + "oauth2": expectedOauth2Definitions, + "other": securityDefinitions["other"] + } + ]) + }) + +}) diff --git a/frontend/web/api-doc/test/unit/core/plugins/auth/wrap-spec-actions.js b/frontend/web/api-doc/test/unit/core/plugins/auth/wrap-spec-actions.js new file mode 100644 index 0000000..889423d --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/plugins/auth/wrap-spec-actions.js @@ -0,0 +1,40 @@ + +import { execute } from "corePlugins/auth/spec-wrap-actions" + +describe("spec plugin - actions", function(){ + + describe("execute", function(){ + + xit("should add `securities` to the oriAction call", function(){ + // Given + const system = { + authSelectors: { + authorized: jest.fn().mockImplementation(() => ({ + some: "security" + })) + } + } + const oriExecute = jest.fn() + + // When + let executeFn = execute(oriExecute, system) + executeFn({}) + + // Then + expect(oriExecute.mock.calls.length).toEqual(1) + expect(oriExecute.mock.calls[0][0]).toEqual({ + extras: { + security: { + some: "security" + } + }, + method: undefined, + path: undefined, + operation: undefined + }) + + }) + + }) + +}) diff --git a/frontend/web/api-doc/test/unit/core/plugins/configs/actions.js b/frontend/web/api-doc/test/unit/core/plugins/configs/actions.js new file mode 100644 index 0000000..757c410 --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/plugins/configs/actions.js @@ -0,0 +1,65 @@ +import { downloadConfig } from "corePlugins/configs/spec-actions" +import { loaded } from "corePlugins/configs/actions" + +describe("configs plugin - actions", () => { + + describe("downloadConfig", () => { + it("should call the system fetch helper with a provided request", () => { + const fetchSpy = jest.fn(async () => {}) + const system = { + fn: { + fetch: fetchSpy + } + } + + const req = { + url: "http://swagger.io/one", + requestInterceptor: a => a, + responseInterceptor: a => a, + } + + downloadConfig(req)(system) + + expect(fetchSpy).toHaveBeenCalledWith(req) + }) + }) + + describe("loaded hook", () => { + describe("authorization data restoration", () => { + beforeEach(() => { + localStorage.clear() + }) + it("retrieve `authorized` value from `localStorage`", () => { + const system = { + getConfigs: () => ({ + persistAuthorization: true + }), + authActions: { + + } + } + jest.spyOn(Object.getPrototypeOf(window.localStorage), "getItem") + loaded()(system) + expect(localStorage.getItem).toHaveBeenCalled() + expect(localStorage.getItem).toHaveBeenCalledWith("authorized") + }) + it("restore authorization data when a value exists", () => { + const system = { + getConfigs: () => ({ + persistAuthorization: true + }), + authActions: { + restoreAuthorization: jest.fn(() => {}) + } + } + const mockData = {"api_key": {}} + localStorage.setItem("authorized", JSON.stringify(mockData)) + loaded()(system) + expect(system.authActions.restoreAuthorization).toHaveBeenCalled() + expect(system.authActions.restoreAuthorization).toHaveBeenCalledWith({ + authorized: mockData + }) + }) + }) + }) +}) diff --git a/frontend/web/api-doc/test/unit/core/plugins/err/transformers/not-of-type.js b/frontend/web/api-doc/test/unit/core/plugins/err/transformers/not-of-type.js new file mode 100644 index 0000000..682c641 --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/plugins/err/transformers/not-of-type.js @@ -0,0 +1,54 @@ +import { Map, List } from "immutable" +import { transform } from "corePlugins/err/error-transformers/transformers/not-of-type" + +describe("err plugin - tranformers - not of type", () => { + + it("should transform a singular not of type(s) error without an inline path", () => { + let ori = List([ + Map({ + path: "info.version", + message: "is not of a type(s) string" + }) + ]) + + let res = transform(ori).toJS() + + expect(res).toEqual([{ + path: "info.version", + message: "should be a string" + }]) + }) + + it("should transform a plural (2) not of type(s) error without an inline path", () => { + let ori = List([ + Map({ + path: "info.version", + message: "is not of a type(s) string,array" + }) + ]) + + let res = transform(ori).toJS() + + expect(res).toEqual([{ + path: "info.version", + message: "should be a string or array" + }]) + }) + + it("should transform a plural (3+) not of type(s) error without an inline path", () => { + let ori = List([ + Map({ + path: "info.version", + message: "is not of a type(s) string,array,number" + }) + ]) + + let res = transform(ori).toJS() + + expect(res).toEqual([{ + path: "info.version", + message: "should be a string, array, or number" + }]) + }) + +}) diff --git a/frontend/web/api-doc/test/unit/core/plugins/err/transformers/parameter-oneof.js b/frontend/web/api-doc/test/unit/core/plugins/err/transformers/parameter-oneof.js new file mode 100644 index 0000000..1ff4101 --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/plugins/err/transformers/parameter-oneof.js @@ -0,0 +1,131 @@ +/* eslint-disable no-useless-escape */ +import { fromJS } from "immutable" +import { transform } from "corePlugins/err/error-transformers/transformers/parameter-oneof" + +describe.skip("err plugin - tranformers - parameter oneof", () => { + + describe("parameter.in misuse transformation to fixed value error", () => { + + it("should return a helpful error for invalid 'in' values", () => { + const jsSpec = { + paths: { + "/CoolPath/": { + get: { + parameters: [ + { + name: "id", + in: "heder" + } + ] + } + } + } + } + + const jsonSchemaError = { + "level": "error", + "path": "paths.\/CoolPath\/.get.parameters[0]", + "message": "is not exactly one from <#\/definitions\/parameter>,<#\/definitions\/jsonReference>", + "source": "schema", + "type": "spec" + } + + let res = transform(fromJS([jsonSchemaError]), { jsSpec }) + + expect(res.toJS()).toEqual([{ + path: "paths./CoolPath/.get.parameters[0].in", + message: "Wrong value for the \"in\" keyword. Expected one of: path, query, header, body, formData.", + level: "error", + source: "schema", + type: "spec" + }]) + }) + + }) + + describe("parameter.collectionFormat misuse transformation to fixed value error", () => { + it("should return a helpful error for invalid 'collectionFormat' values", () => { + const jsSpec = { + paths: { + "/CoolPath/": { + get: { + parameters: [ + { + name: "id", + in: "query", + collectionFormat: "asdf" + } + ] + } + } + } + } + + const jsonSchemaError = { + "level": "error", + "path": "paths.\/CoolPath\/.get.parameters[0]", + "message": "is not exactly one from <#\/definitions\/parameter>,<#\/definitions\/jsonReference>", + "source": "schema", + "type": "spec" + } + + let res = transform(fromJS([jsonSchemaError]), { jsSpec }) + + expect(res.toJS()).toEqual([{ + path: "paths./CoolPath/.get.parameters[0].collectionFormat", + message: "Wrong value for the \"collectionFormat\" keyword. Expected one of: csv, ssv, tsv, pipes, multi.", + level: "error", + source: "schema", + type: "spec" + }]) + }) + }) + + describe("Integrations", () => { + it("should return the correct errors when both 'in' and 'collectionFormat' are incorrect", () => { + const jsSpec = { + paths: { + "/CoolPath/": { + get: { + parameters: [ + { + name: "id", + in: "blah", + collectionFormat: "asdf" + } + ] + } + } + } + } + + const jsonSchemaError = { + "level": "error", + "path": "paths.\/CoolPath\/.get.parameters[0]", + "message": "is not exactly one from <#\/definitions\/parameter>,<#\/definitions\/jsonReference>", + "source": "schema", + "type": "spec" + } + + let res = transform(fromJS([jsonSchemaError]), { jsSpec }) + + expect(res.toJS()).toEqual([ + { + path: "paths./CoolPath/.get.parameters[0].in", + message: "Wrong value for the \"in\" keyword. Expected one of: path, query, header, body, formData.", + level: "error", + source: "schema", + type: "spec" + }, + { + path: "paths./CoolPath/.get.parameters[0].collectionFormat", + message: "Wrong value for the \"collectionFormat\" keyword. Expected one of: csv, ssv, tsv, pipes, multi.", + level: "error", + source: "schema", + type: "spec" + } + ]) + }) + }) + +}) diff --git a/frontend/web/api-doc/test/unit/core/plugins/filter/opsFilter.js b/frontend/web/api-doc/test/unit/core/plugins/filter/opsFilter.js new file mode 100644 index 0000000..d64ce60 --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/plugins/filter/opsFilter.js @@ -0,0 +1,24 @@ +import { Map } from "immutable" +import opsFilter from "corePlugins/filter/opsFilter" + +describe("opsFilter", function() { + const taggedOps = Map([["pet"], ["store"], ["user"]]) + + it("should filter taggedOps by tag name", function () { + const filtered = opsFilter(taggedOps, "sto") + + expect(filtered.size).toEqual(1) + }) + + it("should return all taggedOps when search phrase is empty", function () { + const filtered = opsFilter(taggedOps, "") + + expect(filtered.size).toEqual(taggedOps.size) + }) + + it("should return empty result when there is no match", function () { + const filtered = opsFilter(taggedOps, "NoMatch") + + expect(filtered.size).toEqual(0) + }) +}) diff --git a/frontend/web/api-doc/test/unit/core/plugins/oas3/helpers.js b/frontend/web/api-doc/test/unit/core/plugins/oas3/helpers.js new file mode 100644 index 0000000..14e31c9 --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/plugins/oas3/helpers.js @@ -0,0 +1,67 @@ +import { fromJS } from "immutable" +import { isOAS3, isSwagger2 } from "corePlugins/oas3/helpers" + +const isOAS3Shorthand = (version) => isOAS3(fromJS({ + openapi: version +})) + +const isSwagger2Shorthand = (version) => isSwagger2(fromJS({ + swagger: version +})) + +describe("isOAS3", function () { + it("should recognize valid OAS3 version values", function () { + expect(isOAS3Shorthand("3.0.0")).toEqual(true) + expect(isOAS3Shorthand("3.0.1")).toEqual(true) + expect(isOAS3Shorthand("3.0.11111")).toEqual(true) + expect(isOAS3Shorthand("3.0.0-rc0")).toEqual(true) + }) + + it("should fail for invalid OAS3 version values", function () { + expect(isOAS3Shorthand("3.0")).toEqual(false) + expect(isOAS3Shorthand("3.0.")).toEqual(false) + expect(isOAS3Shorthand("2.0")).toEqual(false) + }) + + it("should gracefully fail for non-string values", function () { + expect(isOAS3Shorthand(3.0)).toEqual(false) + expect(isOAS3Shorthand(3)).toEqual(false) + expect(isOAS3Shorthand({})).toEqual(false) + expect(isOAS3Shorthand(null)).toEqual(false) + }) + + it("should gracefully fail when `openapi` field is missing", function () { + expect(isOAS3(fromJS({ + openApi: "3.0.0" + }))).toEqual(false) + expect(isOAS3Shorthand(null)).toEqual(false) + }) +}) + +describe("isSwagger2", function () { + it("should recognize valid Swagger 2.0 version values", function () { + expect(isSwagger2Shorthand("2.0")).toEqual(true) + expect(isSwagger2Shorthand("2.0-rc0")).toEqual(true) + }) + + it("should fail for invalid Swagger 2.0 version values", function () { + expect(isSwagger2Shorthand("3.0")).toEqual(false) + expect(isSwagger2Shorthand("3.0.")).toEqual(false) + expect(isSwagger2Shorthand("2.1")).toEqual(false) + expect(isSwagger2Shorthand("1.2")).toEqual(false) + expect(isSwagger2Shorthand("2")).toEqual(false) + }) + + it("should gracefully fail for non-string values", function () { + expect(isSwagger2Shorthand(2.0)).toEqual(false) + expect(isSwagger2Shorthand(2)).toEqual(false) + expect(isSwagger2Shorthand({})).toEqual(false) + expect(isSwagger2Shorthand(null)).toEqual(false) + }) + + it("should gracefully fail when `swagger` field is missing", function () { + expect(isSwagger2(fromJS({ + Swagger: "2.0" + }))).toEqual(false) + }) +}) diff --git a/frontend/web/api-doc/test/unit/core/plugins/oas3/reducers.js b/frontend/web/api-doc/test/unit/core/plugins/oas3/reducers.js new file mode 100644 index 0000000..c99eaba --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/plugins/oas3/reducers.js @@ -0,0 +1,582 @@ + +import { fromJS } from "immutable" +import reducer from "corePlugins/oas3/reducers" + +describe("oas3 plugin - reducer", function () { + describe("SET_REQUEST_BODY_VALIDATE_ERROR", () => { + const setRequestBodyValidateError = reducer["oas3_set_request_body_validate_error"] + + describe("missingBodyValue exists, e.g. application/json", () => { + it("should set errors", () => { + const state = fromJS({ + requestData: { + "/pet": { + post: { + bodyValue: "", + requestContentType: "application/json" + } + } + } + }) + + const result = setRequestBodyValidateError(state, { + payload: { + path: "/pet", + method: "post", + validationErrors: { + missingBodyValue: true, + missingRequiredKeys: [] + }, + } + }) + + const expectedResult = { + requestData: { + "/pet": { + post: { + bodyValue: "", + requestContentType: "application/json", + errors: ["Required field is not provided"] + } + } + } + } + + expect(result.toJS()).toEqual(expectedResult) + }) + }) + + describe("missingRequiredKeys exists with length, e.g. application/x-www-form-urleconded", () => { + it("should set nested errors", () => { + const state = fromJS({ + requestData: { + "/pet": { + post: { + bodyValue: { + id: { + value: "10", + }, + name: { + value: "", + }, + }, + requestContentType: "application/x-www-form-urlencoded" + } + } + } + }) + + const result = setRequestBodyValidateError(state, { + payload: { + path: "/pet", + method: "post", + validationErrors: { + missingBodyValue: null, + missingRequiredKeys: ["name"] + }, + } + }) + + const expectedResult = { + requestData: { + "/pet": { + post: { + bodyValue: { + id: { + value: "10", + }, + name: { + value: "", + errors: ["Required field is not provided"] + }, + }, + requestContentType: "application/x-www-form-urlencoded", + } + } + } + } + + expect(result.toJS()).toEqual(expectedResult) + }) + + it("should overwrite nested errors, for keys listed in missingRequiredKeys", () => { + const state = fromJS({ + requestData: { + "/pet": { + post: { + bodyValue: { + id: { + value: "10", + }, + name: { + value: "", + errors: ["some fake error"] + }, + }, + requestContentType: "application/x-www-form-urlencoded" + } + } + } + }) + + const result = setRequestBodyValidateError(state, { + payload: { + path: "/pet", + method: "post", + validationErrors: { + missingBodyValue: null, + missingRequiredKeys: ["name"] + }, + } + }) + + const expectedResult = { + requestData: { + "/pet": { + post: { + bodyValue: { + id: { + value: "10", + }, + name: { + value: "", + errors: ["Required field is not provided"] + }, + }, + requestContentType: "application/x-www-form-urlencoded", + } + } + } + } + + expect(result.toJS()).toEqual(expectedResult) + }) + + it("should not overwrite nested errors, for keys not listed in missingRequiredKeys", () => { + const state = fromJS({ + requestData: { + "/pet": { + post: { + bodyValue: { + id: { + value: "10", + errors: ["random error should not be overwritten"] + }, + name: { + value: "", + errors: ["some fake error"] + }, + }, + requestContentType: "application/x-www-form-urlencoded" + } + } + } + }) + + const result = setRequestBodyValidateError(state, { + payload: { + path: "/pet", + method: "post", + validationErrors: { + missingBodyValue: null, + missingRequiredKeys: ["name"] + }, + } + }) + + const expectedResult = { + requestData: { + "/pet": { + post: { + bodyValue: { + id: { + value: "10", + errors: ["random error should not be overwritten"] + }, + name: { + value: "", + errors: ["Required field is not provided"] + }, + }, + requestContentType: "application/x-www-form-urlencoded", + } + } + } + } + + expect(result.toJS()).toEqual(expectedResult) + }) + + it("should set multiple nested errors", () => { + const state = fromJS({ + requestData: { + "/pet": { + post: { + bodyValue: { + id: { + value: "", + }, + name: { + value: "", + }, + }, + requestContentType: "application/x-www-form-urlencoded" + } + } + } + }) + + const result = setRequestBodyValidateError(state, { + payload: { + path: "/pet", + method: "post", + validationErrors: { + missingBodyValue: null, + missingRequiredKeys: ["id", "name"] + }, + } + }) + + const expectedResult = { + requestData: { + "/pet": { + post: { + bodyValue: { + id: { + value: "", + errors: ["Required field is not provided"] + }, + name: { + value: "", + errors: ["Required field is not provided"] + }, + }, + requestContentType: "application/x-www-form-urlencoded", + } + } + } + } + + expect(result.toJS()).toEqual(expectedResult) + }) + }) + + describe("missingRequiredKeys is empty list", () => { + it("should not set any errors, and return state unchanged", () => { + const state = fromJS({ + requestData: { + "/pet": { + post: { + bodyValue: { + id: { + value: "10", + }, + name: { + value: "", + }, + }, + requestContentType: "application/x-www-form-urlencoded" + } + } + } + }) + + const result = setRequestBodyValidateError(state, { + payload: { + path: "/pet", + method: "post", + validationErrors: { + missingBodyValue: null, + missingRequiredKeys: [] + }, + } + }) + + const expectedResult = { + requestData: { + "/pet": { + post: { + bodyValue: { + id: { + value: "10", + }, + name: { + value: "", + }, + }, + requestContentType: "application/x-www-form-urlencoded", + } + } + } + } + + expect(result.toJS()).toEqual(expectedResult) + }) + }) + + describe("other unexpected payload, e.g. no missingBodyValue or missingRequiredKeys", () => { + it("should not throw error if receiving unexpected validationError format. return state unchanged", () => { + const state = fromJS({ + requestData: { + "/pet": { + post: { + bodyValue: { + id: { + value: "10", + }, + name: { + value: "", + }, + }, + requestContentType: "application/x-www-form-urlencoded" + } + } + } + }) + + const result = setRequestBodyValidateError(state, { + payload: { + path: "/pet", + method: "post", + validationErrors: { + missingBodyValue: null, + // missingRequiredKeys: ["none provided"] + }, + } + }) + + const expectedResult = { + requestData: { + "/pet": { + post: { + bodyValue: { + id: { + value: "10", + }, + name: { + value: "", + }, + }, + requestContentType: "application/x-www-form-urlencoded", + } + } + } + } + + expect(result.toJS()).toEqual(expectedResult) + }) + }) + }) + + describe("CLEAR_REQUEST_BODY_VALIDATE_ERROR", function() { + const clearRequestBodyValidateError = reducer["oas3_clear_request_body_validate_error"] + + describe("bodyValue is String, e.g. application/json", () => { + it("should clear errors", () => { + const state = fromJS({ + requestData: { + "/pet": { + post: { + bodyValue: "{}", + requestContentType: "application/json" + } + } + } + }) + + const result = clearRequestBodyValidateError(state, { + payload: { + path: "/pet", + method: "post", + } + }) + + const expectedResult = { + requestData: { + "/pet": { + post: { + bodyValue: "{}", + requestContentType: "application/json", + errors: [] + } + } + } + } + + expect(result.toJS()).toEqual(expectedResult) + }) + }) + + describe("bodyValue is Map with entries, e.g. application/x-www-form-urleconded", () => { + it("should clear nested errors, and apply empty error list to all entries", () => { + const state = fromJS({ + requestData: { + "/pet": { + post: { + bodyValue: { + id: { + value: "10", + errors: ["some random error"] + }, + name: { + value: "doggie", + errors: ["Required field is not provided"] + }, + status: { + value: "available" + } + }, + requestContentType: "application/x-www-form-urlencoded" + } + } + } + }) + + const result = clearRequestBodyValidateError(state, { + payload: { + path: "/pet", + method: "post", + } + }) + + const expectedResult = { + requestData: { + "/pet": { + post: { + bodyValue: { + id: { + value: "10", + errors: [], + }, + name: { + value: "doggie", + errors: [], + }, + status: { + value: "available", + errors: [], + }, + }, + requestContentType: "application/x-www-form-urlencoded", + } + } + } + } + + expect(result.toJS()).toEqual(expectedResult) + }) + }) + + describe("bodyValue is empty Map", () => { + it("should return state unchanged", () => { + const state = fromJS({ + requestData: { + "/pet": { + post: { + bodyValue: {}, + requestContentType: "application/x-www-form-urlencoded" + } + } + } + }) + + const result = clearRequestBodyValidateError(state, { + payload: { + path: "/pet", + method: "post", + } + }) + + const expectedResult = { + requestData: { + "/pet": { + post: { + bodyValue: { + }, + requestContentType: "application/x-www-form-urlencoded", + } + } + } + } + + expect(result.toJS()).toEqual(expectedResult) + }) + }) + }) + + describe("CLEAR_REQUEST_BODY_VALUE", function () { + const clearRequestBodyValue = reducer["oas3_clear_request_body_value"] + describe("when requestBodyValue is a String", () => { + it("should clear requestBodyValue with empty String", () => { + const state = fromJS({ + requestData: { + "/pet": { + post: { + bodyValue: "some random string", + requestContentType: "application/json" + } + } + } + }) + + const result = clearRequestBodyValue(state, { + payload: { + pathMethod: ["/pet", "post"], + } + }) + + const expectedResult = { + requestData: { + "/pet": { + post: { + bodyValue: "", + requestContentType: "application/json", + } + } + } + } + + expect(result.toJS()).toEqual(expectedResult) + }) + }) + + describe("when requestBodyValue is a Map", () => { + it("should clear requestBodyValue with empty Map", () => { + const state = fromJS({ + requestData: { + "/pet": { + post: { + bodyValue: { + id: { + value: "10", + }, + }, + requestContentType: "application/x-www-form-urlencoded" + } + } + } + }) + + const result = clearRequestBodyValue(state, { + payload: { + pathMethod: ["/pet", "post"], + } + }) + + const expectedResult = { + requestData: { + "/pet": { + post: { + bodyValue: {}, + requestContentType: "application/x-www-form-urlencoded", + } + } + } + } + + expect(result.toJS()).toEqual(expectedResult) + }) + }) + }) + +}) diff --git a/frontend/web/api-doc/test/unit/core/plugins/oas3/servers-wrapper.jsx b/frontend/web/api-doc/test/unit/core/plugins/oas3/servers-wrapper.jsx new file mode 100644 index 0000000..31bdff8 --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/plugins/oas3/servers-wrapper.jsx @@ -0,0 +1,75 @@ + +import React from "react" +import { mount } from "enzyme" +import { fromJS } from "immutable" +import ServersContainer from "core/plugins/oas3/components/servers-container" +import Servers from "core/plugins/oas3/components/servers" +import { Col } from "components/layout-utils" + +describe("", function(){ + + const components = { + Servers, + Col + } + const mockedProps = { + specSelectors: { + servers() {} + }, + oas3Selectors: { + selectedServer() {}, + serverVariableValue() {}, + serverEffectiveValue() {} + }, + oas3Actions: { + setSelectedServer() {}, + setServerVariableValue() {} + }, + getComponent: c => components[c] + } + + it("renders Servers inside ServersContainer if servers are provided", function(){ + + // Given + let props = {...mockedProps} + props.specSelectors = {...mockedProps.specSelectors} + props.specSelectors.servers = function() {return fromJS([{url: "http://server1.com"}])} + props.oas3Selectors = {...mockedProps.oas3Selectors} + props.oas3Selectors.selectedServer = function() {return "http://server1.com"} + + // When + let wrapper = mount() + + // Then + const renderedServers = wrapper.find(Servers) + expect(renderedServers.length).toEqual(1) + }) + + it("does not render Servers inside ServersContainer if servers are empty", function(){ + + // Given + let props = {...mockedProps} + props.specSelectors = {...mockedProps.specSelectors} + props.specSelectors.servers = function() {return fromJS([])} + + // When + let wrapper = mount() + + // Then + const renderedServers = wrapper.find(Servers) + expect(renderedServers.length).toEqual(0) + }) + + it("does not render Servers inside ServersContainer if servers are undefined", function(){ + + // Given + let props = {...mockedProps} + + // When + let wrapper = mount() + + // Then + const renderedServers = wrapper.find(Servers) + expect(renderedServers.length).toEqual(0) + }) +}) diff --git a/frontend/web/api-doc/test/unit/core/plugins/oas3/state-integration.js b/frontend/web/api-doc/test/unit/core/plugins/oas3/state-integration.js new file mode 100644 index 0000000..e80918d --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/plugins/oas3/state-integration.js @@ -0,0 +1,358 @@ +import { fromJS, OrderedMap } from "immutable" + +import { + selectedServer, + serverVariableValue, + serverVariables, + serverEffectiveValue +} from "corePlugins/oas3/selectors" + +import reducers from "corePlugins/oas3/reducers" + +import { + setSelectedServer, + setServerVariableValue, +} from "corePlugins/oas3/actions" + +describe("OAS3 plugin - state", function() { + describe("action + reducer + selector integration", function() { + describe("selectedServer", function() { + it("should set and get a global selectedServer", function() { + const state = new OrderedMap() + const system = { + // needed to handle `onlyOAS3` wrapper + getSystem() { + return { + specSelectors: { + specJson: () => { + return fromJS({ openapi: "3.0.0" }) + } + } + } + } + } + + // Create the action + const action = setSelectedServer("http://google.com") + + // Collect the new state + const newState = reducers["oas3_set_servers"](state, action) + + // Get the value with the selector + const res = selectedServer(newState)(system) + + expect(res).toEqual("http://google.com") + }) + + it("should set and get a namespaced selectedServer", function() { + const state = fromJS({ + selectedServer: "http://yahoo.com" + }) + const system = { + // needed to handle `onlyOAS3` wrapper + getSystem() { + return { + specSelectors: { + specJson: () => { + return fromJS({ openapi: "3.0.0" }) + } + } + } + } + } + + // Create the action + const action = setSelectedServer("http://google.com", "myOperation") + + // Collect the new state + const newState = reducers["oas3_set_servers"](state, action) + + // Get the value with the selector + const res = selectedServer(newState, "myOperation")(system) + + // Get the global selected server + const globalRes = selectedServer(newState)(system) + + expect(res).toEqual("http://google.com") + expect(globalRes).toEqual("http://yahoo.com") + }) + }) + + describe("serverVariableValue", function() { + it("should set and get a global serverVariableValue", function() { + const state = new OrderedMap() + const system = { + // needed to handle `onlyOAS3` wrapper + getSystem() { + return { + specSelectors: { + specJson: () => { + return fromJS({ openapi: "3.0.0" }) + } + } + } + } + } + + // Create the action + const action = setServerVariableValue({ + server: "google.com", + key: "foo", + val: "bar" + }) + + // Collect the new state + const newState = reducers["oas3_set_server_variable_value"](state, action) + + // Get the value with the selector + const res = serverVariableValue(newState, "google.com", "foo")(system) + + expect(res).toEqual("bar") + }) + it("should set and get a namespaced serverVariableValue", function() { + const state = fromJS({ + serverVariableValues: { + "google.com": { + foo: "123" + } + } + }) + const system = { + // needed to handle `onlyOAS3` wrapper + getSystem() { + return { + specSelectors: { + specJson: () => { + return fromJS({ openapi: "3.0.0" }) + } + } + } + } + } + + // Create the action + const action = setServerVariableValue({ + namespace: "myOperation", + server: "google.com", + key: "foo", + val: "bar" + }) + + // Collect the new state + const newState = reducers["oas3_set_server_variable_value"](state, action) + + // Get the value with the selector + const res = serverVariableValue(newState, { + namespace: "myOperation", + server: "google.com" + }, "foo")(system) + + // Get the global value, to cross-check + const globalRes = serverVariableValue(newState, { + server: "google.com" + }, "foo")(system) + + expect(res).toEqual("bar") + expect(globalRes).toEqual("123") + }) + }) + + describe("serverVariables", function() { + it("should set and get global serverVariables", function() { + const state = new OrderedMap() + const system = { + // needed to handle `onlyOAS3` wrapper + getSystem() { + return { + specSelectors: { + specJson: () => { + return fromJS({ openapi: "3.0.0" }) + } + } + } + } + } + + // Create the action + const action = setServerVariableValue({ + server: "google.com", + key: "foo", + val: "bar" + }) + + // Collect the new state + const newState = reducers["oas3_set_server_variable_value"](state, action) + + // Get the value with the selector + const res = serverVariables(newState, "google.com", "foo")(system) + + expect(res.toJS()).toEqual({ + foo: "bar" + }) + }) + + it("should set and get namespaced serverVariables", function() { + const state = fromJS({ + serverVariableValues: { + "google.com": { + foo: "123" + } + } + }) + + const system = { + // needed to handle `onlyOAS3` wrapper + getSystem() { + return { + specSelectors: { + specJson: () => { + return fromJS({ openapi: "3.0.0" }) + } + } + } + } + } + + // Create the action + const action = setServerVariableValue({ + namespace: "myOperation", + server: "google.com", + key: "foo", + val: "bar" + }) + + // Collect the new state + const newState = reducers["oas3_set_server_variable_value"](state, action) + + // Get the value with the selector + const res = serverVariables(newState, { + namespace: "myOperation", + server: "google.com" + }, "foo")(system) + + // Get the global value, to cross-check + const globalRes = serverVariables(newState, { + server: "google.com" + }, "foo")(system) + + expect(res.toJS()).toEqual({ + foo: "bar" + }) + + expect(globalRes.toJS()).toEqual({ + foo: "123" + }) + }) + }) + describe("serverEffectiveValue", function() { + it("should set variable values and compute a URL for a namespaced server", function() { + const state = fromJS({ + serverVariableValues: { + "google.com/{foo}": { + foo: "123" + } + } + }) + + const system = { + // needed to handle `onlyOAS3` wrapper + getSystem() { + return { + specSelectors: { + specJson: () => { + return fromJS({ openapi: "3.0.0" }) + } + } + } + } + } + + // Create the action + const action = setServerVariableValue({ + namespace: "myOperation", + server: "google.com/{foo}", + key: "foo", + val: "bar" + }) + + // Collect the new state + const newState = reducers["oas3_set_server_variable_value"](state, action) + + // Get the value with the selector + const res = serverEffectiveValue(newState, { + namespace: "myOperation", + server: "google.com/{foo}" + })(system) + + // Get the global value, to cross-check + const globalRes = serverEffectiveValue(newState, { + server: "google.com/{foo}" + })(system) + + expect(res).toEqual("google.com/bar") + + expect(globalRes).toEqual("google.com/123") + }) + }) + + }) + describe("selectors", function() { + describe("serverEffectiveValue", function() { + it("should compute global serverEffectiveValues", function() { + const state = fromJS({ + serverVariableValues: { + "google.com/{foo}/{bar}": { + foo: "123", + bar: "456" + } + } + }) + const system = { + // needed to handle `onlyOAS3` wrapper + getSystem() { + return { + specSelectors: { + specJson: () => { + return fromJS({ openapi: "3.0.0" }) + } + } + } + } + } + + // Get the value with the selector + const res = serverEffectiveValue(state, "google.com/{foo}/{bar}")(system) + + expect(res).toEqual("google.com/123/456") + }) + + it("should handle multiple variable instances", function() { + const state = fromJS({ + serverVariableValues: { + "google.com/{foo}/{foo}/{bar}": { + foo: "123", + bar: "456" + } + } + }) + const system = { + // needed to handle `onlyOAS3` wrapper + getSystem() { + return { + specSelectors: { + specJson: () => { + return fromJS({ openapi: "3.0.0" }) + } + } + } + } + } + + // Get the value with the selector + const res = serverEffectiveValue(state, "google.com/{foo}/{foo}/{bar}")(system) + + expect(res).toEqual("google.com/123/123/456") + }) + }) + }) +}) diff --git a/frontend/web/api-doc/test/unit/core/plugins/oas3/wrap-auth-selectors.js b/frontend/web/api-doc/test/unit/core/plugins/oas3/wrap-auth-selectors.js new file mode 100644 index 0000000..59fd8a9 --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/plugins/oas3/wrap-auth-selectors.js @@ -0,0 +1,240 @@ + +import { fromJS, Map } from "immutable" +import { + definitionsToAuthorize +} from "corePlugins/oas3/auth-extensions/wrap-selectors" + +describe("oas3 plugin - auth extensions - wrapSelectors", function(){ + + describe("execute", function(){ + + it("should add `securities` to the oriAction call", function(){ + // Given + const system = { + getSystem: () => system, + getState: () => new Map(), + specSelectors: { + specJson: () => fromJS({ + openapi: "3.0.0" + }), + securityDefinitions: () => { + return fromJS({ + "oauth2AuthorizationCode": { + "type": "oauth2", + "description": "Some Oauth2 endpoint", + "flows": { + "authorizationCode": { + "authorizationUrl": "http://google.com/", + "tokenUrl": "http://google.com/", + "scopes": { + "myScope": "our only scope" + } + } + } + }, + "oauth2Multiflow": { + "type": "oauth2", + "flows": { + "clientCredentials": { + "tokenUrl": "http://google.com/", + "scopes": { + "myScope": "our only scope" + } + }, + "password": { + "tokenUrl": "http://google.com/", + "scopes": { + "myScope": "our only scope" + } + }, + "authorizationCode": { + "authorizationUrl": "http://google.com/", + "tokenUrl": "http://google.com/", + "scopes": { + "myScope": "our only scope" + } + } + } + }, + "oidc": { + "type": "openIdConnect", + "openIdConnectUrl": "https://accounts.google.com/.well-known/openid-configuration", + "openIdConnectData": { + "authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth", + "token_endpoint": "https://oauth2.googleapis.com/token", + "scopes_supported": [ + "openid", + "email", + "profile" + ], + "grant_types_supported": [ + "authorization_code", + "refresh_token", + "urn:ietf:params:oauth:grant-type:device_code", + "urn:ietf:params:oauth:grant-type:jwt-bearer" + ] + } + }, + "oidcNoGrant": { + "type": "openIdConnect", + "openIdConnectUrl": "https://accounts.google.com/.well-known/openid-configuration", + "openIdConnectData": { + "authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth", + "token_endpoint": "https://oauth2.googleapis.com/token", + "scopes_supported": [ + "openid", + "email", + "profile" + ] + }, + }, + }) + } + } + } + + // When + let res = definitionsToAuthorize(() => null, system)() + + // Then + expect(res.toJS()).toEqual([ + { + oauth2AuthorizationCode: { + flow: "authorizationCode", + authorizationUrl: "http://google.com/", + tokenUrl: "http://google.com/", + scopes: { + "myScope": "our only scope" + }, + type: "oauth2", + description: "Some Oauth2 endpoint" + } + }, + { + oauth2Multiflow: { + flow: "clientCredentials", + tokenUrl: "http://google.com/", + scopes: { + "myScope": "our only scope" + }, + type: "oauth2" + } + }, + { + oauth2Multiflow: { + flow: "password", + tokenUrl: "http://google.com/", + scopes: { + "myScope": "our only scope" + }, + type: "oauth2" + } + }, + { + oauth2Multiflow: { + flow: "authorizationCode", + authorizationUrl: "http://google.com/", + tokenUrl: "http://google.com/", + scopes: { + "myScope": "our only scope" + }, + type: "oauth2" + } + }, + { + oidc: { + flow: "authorization_code", + authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth", + tokenUrl: "https://oauth2.googleapis.com/token", + openIdConnectUrl: "https://accounts.google.com/.well-known/openid-configuration", + scopes: { + "openid": "", + "email": "", + "profile": "", + }, + type: "oauth2" + } + }, + { + oidc: { + flow: "refresh_token", + authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth", + tokenUrl: "https://oauth2.googleapis.com/token", + openIdConnectUrl: "https://accounts.google.com/.well-known/openid-configuration", + scopes: { + "openid": "", + "email": "", + "profile": "", + }, + type: "oauth2" + } + }, + { + oidc: { + flow: "urn:ietf:params:oauth:grant-type:device_code", + authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth", + tokenUrl: "https://oauth2.googleapis.com/token", + openIdConnectUrl: "https://accounts.google.com/.well-known/openid-configuration", + scopes: { + "openid": "", + "email": "", + "profile": "", + }, + type: "oauth2" + } + }, + { + oidc: { + flow: "urn:ietf:params:oauth:grant-type:jwt-bearer", + authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth", + tokenUrl: "https://oauth2.googleapis.com/token", + openIdConnectUrl: "https://accounts.google.com/.well-known/openid-configuration", + scopes: { + "openid": "", + "email": "", + "profile": "", + }, + type: "oauth2" + } + }, + { + // See https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata + // grant_types_supported + // OPTIONAL. JSON array containing a list of the OAuth 2.0 Grant Type values that + // this OP supports. Dynamic OpenID Providers MUST support the authorization_code + // and implicit Grant Type values and MAY support other Grant Types. If omitted, + // the default value is ["authorization_code", "implicit"]. + oidcNoGrant: { + flow: "authorization_code", + authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth", + tokenUrl: "https://oauth2.googleapis.com/token", + openIdConnectUrl: "https://accounts.google.com/.well-known/openid-configuration", + scopes: { + "openid": "", + "email": "", + "profile": "", + }, + type: "oauth2" + } + }, + { + oidcNoGrant: { + flow: "implicit", + authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth", + tokenUrl: "https://oauth2.googleapis.com/token", + openIdConnectUrl: "https://accounts.google.com/.well-known/openid-configuration", + scopes: { + "openid": "", + "email": "", + "profile": "", + }, + type: "oauth2" + } + }, + ]) + + }) + + }) + +}) diff --git a/frontend/web/api-doc/test/unit/core/plugins/oas3/wrap-spec-selectors.js b/frontend/web/api-doc/test/unit/core/plugins/oas3/wrap-spec-selectors.js new file mode 100644 index 0000000..55b196e --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/plugins/oas3/wrap-spec-selectors.js @@ -0,0 +1,98 @@ + +import { fromJS } from "immutable" +import { + definitions +} from "corePlugins/oas3/spec-extensions/wrap-selectors" + +describe("oas3 plugin - spec extensions - wrapSelectors", function(){ + + describe("definitions", function(){ + it("should return definitions by default", function () { + + // Given + const spec = fromJS({ + openapi: "3.0.0", + components: { + schemas: { + a: { + type: "string" + }, + b: { + type: "string" + } + } + } + }) + + const system = { + getSystem: () => system, + specSelectors: { + specJson: () => spec, + } + } + + // When + let res = definitions(() => null, system)(fromJS({ + json: spec + })) + + // Then + expect(res.toJS()).toEqual({ + a: { + type: "string" + }, + b: { + type: "string" + } + }) + }) + it("should return an empty Map when missing definitions", function () { + + // Given + const spec = fromJS({ + openapi: "3.0.0" + }) + + const system = { + getSystem: () => system, + specSelectors: { + specJson: () => spec, + } + } + + // When + let res = definitions(() => null, system)(fromJS({ + json: spec + })) + + // Then + expect(res.toJS()).toEqual({}) + }) + it("should return an empty Map when given non-object definitions", function () { + + // Given + const spec = fromJS({ + openapi: "3.0.0", + components: { + schemas: "..." + } + }) + + const system = { + getSystem: () => system, + specSelectors: { + specJson: () => spec, + } + } + + // When + let res = definitions(() => null, system)(fromJS({ + json: spec + })) + + // Then + expect(res.toJS()).toEqual({}) + }) + }) + +}) diff --git a/frontend/web/api-doc/test/unit/core/plugins/safe-render/index.jsx b/frontend/web/api-doc/test/unit/core/plugins/safe-render/index.jsx new file mode 100644 index 0000000..34d2c69 --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/plugins/safe-render/index.jsx @@ -0,0 +1,253 @@ +import React from "react" +import { mount } from "enzyme" +import sinon from "sinon" +import { Provider } from "react-redux" +import noop from "lodash/noop" + +import System from "core/system" +import ViewPlugin from "core/plugins/view" +import SafeRenderPlugin from "core/plugins/safe-render" + +describe("safe-render", function() { + const DisableComponentDidCatchPlugin = () => ({ + fn: { + componentDidCatch: noop, + } + }) + + it("should catch errors thrown inside of React Component class render method", function() { + class BrokenComponent extends React.Component { + render() { + return null + } + } + const BrokenComponentPlugin = () => { + return { + components: { + BrokenComponent, + } + } + } + + const system = new System({ + plugins: [ + ViewPlugin, + BrokenComponentPlugin, + SafeRenderPlugin({ + fullOverride: true, + componentList: ["BrokenComponent"], + }), + DisableComponentDidCatchPlugin, + ] + }) + + const SafeBrokenComponent = system.getSystem().getComponent("BrokenComponent") + const wrapper = mount() + wrapper.find(BrokenComponent).simulateError(new Error("error")) + + expect(wrapper.text()).toEqual("😱 Could not render BrokenComponent, see the console.") + }) + + it("should catch errors thrown inside of PureComponent class render method", function() { + class BrokenComponent extends React.PureComponent { + render() { + return null + } + } + const BrokenComponentPlugin = () => { + return { + components: { + BrokenComponent, + } + } + } + + const system = new System({ + plugins: [ + ViewPlugin, + BrokenComponentPlugin, + SafeRenderPlugin({ + fullOverride: true, + componentList: ["BrokenComponent"], + }), + DisableComponentDidCatchPlugin, + ] + }) + + const SafeBrokenComponent = system.getSystem().getComponent("BrokenComponent") + const wrapper = mount() + wrapper.find(BrokenComponent).simulateError(new Error("error")) + + expect(wrapper.text()).toEqual("😱 Could not render BrokenComponent, see the console.") + }) + + it("should catch errors thrown inside of function component", function() { + const BrokenComponent = () => null + const BrokenComponentPlugin = () => { + return { + components: { + BrokenComponent, + } + } + } + + const system = new System({ + plugins: [ + ViewPlugin, + BrokenComponentPlugin, + SafeRenderPlugin({ + fullOverride: true, + componentList: ["BrokenComponent"], + }), + DisableComponentDidCatchPlugin, + ] + }) + + const SafeBrokenComponent = system.getSystem().getComponent("BrokenComponent") + const wrapper = mount() + wrapper.find(BrokenComponent).simulateError(new Error("error")) + + expect(wrapper.text()).toEqual("😱 Could not render BrokenComponent, see the console.") + }) + + it("should catch errors thrown inside of container created from React Component class", function() { + class BrokenComponent extends React.Component { + render() { + return null + } + } + const BrokenComponentPlugin = () => { + return { + components: { + BrokenComponent, + } + } + } + + const system = new System({ + plugins: [ + ViewPlugin, + BrokenComponentPlugin, + SafeRenderPlugin({ + fullOverride: true, + componentList: ["BrokenComponent"], + }), + DisableComponentDidCatchPlugin, + ] + }) + + const SafeBrokenComponent = system.getSystem().getComponent("BrokenComponent", true) + const wrapper = mount( + + + + ) + wrapper.find(BrokenComponent).simulateError(new Error("error")) + + expect(wrapper.text()).toEqual("😱 Could not render BrokenComponent, see the console.") + }) + + it("should catch errors thrown inside of container created from function component", function() { + const BrokenComponent = () => null + const BrokenComponentPlugin = () => { + return { + components: { + BrokenComponent, + } + } + } + + const system = new System({ + plugins: [ + ViewPlugin, + BrokenComponentPlugin, + SafeRenderPlugin({ + fullOverride: true, + componentList: ["BrokenComponent"], + }), + DisableComponentDidCatchPlugin, + ] + }) + + const SafeBrokenComponent = system.getSystem().getComponent("BrokenComponent", true) + const wrapper = mount( + + + + ) + wrapper.find(BrokenComponent).simulateError(new Error("error")) + + expect(wrapper.text()).toEqual("😱 Could not render BrokenComponent, see the console.") + }) + + it("should render custom Fallback component", function() { + const BrokenComponent = () => null + const BrokenComponentPlugin = () => { + return { + components: { + BrokenComponent, + } + } + } + const FallbackPlugin = () => ({ + components: { + Fallback: () => "fallback component", + }, + }) + + const system = new System({ + plugins: [ + ViewPlugin, + BrokenComponentPlugin, + SafeRenderPlugin({ + fullOverride: true, + componentList: ["BrokenComponent"], + }), + FallbackPlugin, + DisableComponentDidCatchPlugin, + ] + }) + + const SafeBrokenComponent = system.getSystem().getComponent("BrokenComponent") + const wrapper = mount() + wrapper.find(BrokenComponent).simulateError(new Error("error")) + + expect(wrapper.text()).toEqual("fallback component") + }) + + it("should call custom componentDidCatch hook", function() { + const BrokenComponent = () => null + const componentDidCatch = sinon.spy() + + const BrokenComponentPlugin = () => { + return { + components: { + BrokenComponent, + } + } + } + const ComponentDidCatchPlugin = () => ({ + fn: { + componentDidCatch, + }, + }) + + const system = new System({ + plugins: [ + ViewPlugin, + BrokenComponentPlugin, + SafeRenderPlugin({ + fullOverride: true, + componentList: ["BrokenComponent"], + }), + ComponentDidCatchPlugin, + ] + }) + + const SafeBrokenComponent = system.getSystem().getComponent("BrokenComponent") + const wrapper = mount() + wrapper.find(BrokenComponent).simulateError(new Error("error")) + + expect(componentDidCatch.calledOnce).toBe(true) + }) +}) diff --git a/frontend/web/api-doc/test/unit/core/plugins/samples/fn.js b/frontend/web/api-doc/test/unit/core/plugins/samples/fn.js new file mode 100644 index 0000000..b87d59c --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/plugins/samples/fn.js @@ -0,0 +1,2360 @@ +import { fromJS } from "immutable" +import { createXMLExample, sampleFromSchema, memoizedCreateXMLExample, memoizedSampleFromSchema } from "corePlugins/samples/fn" + +describe("sampleFromSchema", () => { + it("handles Immutable.js objects for nested schemas", function () { + let definition = fromJS({ + "type": "object", + "properties": { + "json": { + "type": "object", + "example": { + "a": "string" + }, + "properties": { + "a": { + "type": "string" + } + } + } + } + }) + + let expected = { + json: { + a: "string" + } + } + + expect(sampleFromSchema(definition, { includeReadOnly: false })).toEqual(expected) + }) + + it("should return first enum value if only enum is provided", function () { + let definition = fromJS({ + enum: ["probe"] + }) + + let expected = "probe" + expect(sampleFromSchema(definition, { includeReadOnly: false })).toEqual(expected) + }) + + it("combine first oneOf or anyOf with schema's definitions", function () { + let definition = { + type: "object", + anyOf: [ + { + type: "object", + properties: { + test2: { + type: "string", + example: "anyOf" + }, + test: { + type: "string", + example: "anyOf" + } + } + } + ], + properties: { + test: { + type: "string", + example: "schema" + } + } + } + + let expected = { + test: "schema", + test2: "anyOf" + } + + expect(sampleFromSchema(definition, { includeReadOnly: false })).toEqual(expected) + + definition = { + type: "object", + oneOf: [ + { + type: "object", + properties: { + test2: { + type: "string", + example: "oneOf" + }, + test: { + type: "string", + example: "oneOf" + } + } + } + ], + properties: { + test: { + type: "string", + example: "schema" + } + } + } + + expected = { + test: "schema", + test2: "oneOf" + } + + expect(sampleFromSchema(definition, { includeReadOnly: false })).toEqual(expected) + }) + + it("returns object with no readonly fields for parameter", function () { + let definition = { + type: "object", + properties: { + id: { + type: "integer" + }, + readOnlyDog: { + readOnly: true, + type: "string" + } + }, + xml: { + name: "animals" + } + } + + let expected = { + id: 0 + } + + expect(sampleFromSchema(definition, { includeReadOnly: false })).toEqual(expected) + }) + + it("returns object with readonly fields for parameter, with includeReadOnly", function () { + let definition = { + type: "object", + properties: { + id: { + type: "integer" + }, + readOnlyDog: { + readOnly: true, + type: "string" + } + }, + xml: { + name: "animals" + } + } + + let expected = { + id: 0, + readOnlyDog: "string" + } + + expect(sampleFromSchema(definition, { includeReadOnly: true })).toEqual(expected) + }) + + + + it("regex pattern test", function () { + let definition = { + type: "object", + properties: { + macAddress: { + type: "string", + pattern: "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$" + } + } + } + const resp = sampleFromSchema(definition) + + expect(new RegExp("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$", "g").test(resp.macAddress)).toBe(true) + }) + + it("returns object without deprecated fields for parameter", function () { + let definition = { + type: "object", + properties: { + id: { + type: "integer" + }, + deprecatedProperty: { + deprecated: true, + type: "string" + } + }, + xml: { + name: "animals" + } + } + + let expected = { + id: 0 + } + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("returns object without writeonly fields for parameter", function () { + let definition = { + type: "object", + properties: { + id: { + type: "integer" + }, + writeOnlyDog: { + writeOnly: true, + type: "string" + } + }, + xml: { + name: "animals" + } + } + + let expected = { + id: 0 + } + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("returns object with writeonly fields for parameter, with includeWriteOnly", function () { + let definition = { + type: "object", + properties: { + id: { + type: "integer" + }, + writeOnlyDog: { + writeOnly: true, + type: "string" + } + }, + xml: { + name: "animals" + } + } + + let expected = { + id: 0, + writeOnlyDog: "string" + } + + expect(sampleFromSchema(definition, { includeWriteOnly: true })).toEqual(expected) + }) + + it("returns object without any $$ref fields at the root schema level", function () { + let definition = { + type: "object", + properties: { + message: { + type: "string" + } + }, + example: { + value: { + message: "Hello, World!" + }, + $$ref: "#/components/examples/WelcomeExample" + }, + $$ref: "#/components/schemas/Welcome" + } + + let expected = { + "value": { + "message": "Hello, World!" + } + } + + expect(sampleFromSchema(definition, { includeWriteOnly: true })).toEqual(expected) + }) + + it("returns object without any $$ref fields at nested schema levels", function () { + let definition = { + type: "object", + properties: { + message: { + type: "string" + } + }, + example: { + a: { + value: { + message: "Hello, World!" + }, + $$ref: "#/components/examples/WelcomeExample" + } + }, + $$ref: "#/components/schemas/Welcome" + } + + let expected = { + a: { + "value": { + "message": "Hello, World!" + } + } + } + + expect(sampleFromSchema(definition, { includeWriteOnly: true })).toEqual(expected) + }) + + it("returns object with any $$ref fields that appear to be user-created", function () { + let definition = { + type: "object", + properties: { + message: { + type: "string" + } + }, + example: { + $$ref: { + value: { + message: "Hello, World!" + }, + $$ref: "#/components/examples/WelcomeExample" + } + }, + $$ref: "#/components/schemas/Welcome" + } + + let expected = { + $$ref: { + "value": { + "message": "Hello, World!" + } + } + } + + expect(sampleFromSchema(definition, { includeWriteOnly: true })).toEqual(expected) + }) + + it("returns example value for date-time property", () => { + let definition = { + type: "string", + format: "date-time" + } + + // 0-20 chops off milliseconds + // necessary because test latency can cause failures + // it would be better to mock Date globally and expect a string - KS 11/18 + let expected = new Date().toISOString().substring(0, 20) + + expect(sampleFromSchema(definition)).toContain(expected) + }) + + it("returns example value for date property", () => { + let definition = { + type: "string", + format: "date" + } + + let expected = new Date().toISOString().substring(0, 10) + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("returns a UUID for a string with format=uuid", () => { + let definition = { + type: "string", + format: "uuid" + } + + let expected = "3fa85f64-5717-4562-b3fc-2c963f66afa6" + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("returns a hostname for a string with format=hostname", () => { + let definition = { + type: "string", + format: "hostname" + } + + let expected = "example.com" + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("returns an IPv4 address for a string with format=ipv4", () => { + let definition = { + type: "string", + format: "ipv4" + } + + let expected = "198.51.100.42" + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("returns an IPv6 address for a string with format=ipv6", () => { + let definition = { + type: "string", + format: "ipv6" + } + + let expected = "2001:0db8:5b96:0000:0000:426f:8e17:642a" + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + describe("for array type", () => { + it("returns array with sample of array type", () => { + let definition = { + type: "array", + items: { + type: "integer" + } + } + + let expected = [ 0 ] + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("returns string for example for array that has example of type string", () => { + let definition = { + type: "array", + items: { + type: "string" + }, + example: "dog" + } + + let expected = "dog" + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("returns array of examples for array that has examples", () => { + let definition = { + type: "array", + items: { + type: "string", + }, + example: [ "dog", "cat" ] + } + + let expected = [ "dog", "cat" ] + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("returns array of samples for oneOf type", () => { + let definition = { + type: "array", + items: { + type: "string", + oneOf: [ + { + type: "integer" + } + ] + } + } + + let expected = [ 0 ] + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("returns array of samples for oneOf types", () => { + let definition = { + type: "array", + items: { + type: "string", + oneOf: [ + { + type: "string" + }, + { + type: "integer" + } + ] + } + } + + let expected = [ "string", 0 ] + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("returns array of samples for oneOf examples", () => { + let definition = { + type: "array", + items: { + type: "string", + oneOf: [ + { + type: "string", + example: "dog" + }, + { + type: "integer", + example: 1 + } + ] + } + } + + let expected = [ "dog", 1 ] + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("returns array of samples for anyOf type", () => { + let definition = { + type: "array", + items: { + type: "string", + anyOf: [ + { + type: "integer" + } + ] + } + } + + let expected = [ 0 ] + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("returns array of samples for anyOf types", () => { + let definition = { + type: "array", + items: { + type: "string", + anyOf: [ + { + type: "string" + }, + { + type: "integer" + } + ] + } + } + + let expected = [ "string", 0 ] + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("returns array of samples for anyOf examples", () => { + let definition = { + type: "array", + items: { + type: "string", + anyOf: [ + { + type: "string", + example: "dog" + }, + { + type: "integer", + example: 1 + } + ] + } + } + + let expected = [ "dog", 1 ] + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("returns null for a null example", () => { + let definition = { + "type": "object", + "properties": { + "foo": { + "type": "string", + "nullable": true, + "example": null + } + } + } + + let expected = { + foo: null + } + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("returns null for a null object-level example", () => { + let definition = { + "type": "object", + "properties": { + "foo": { + "type": "string", + "nullable": true + } + }, + "example": { + "foo": null + } + } + + let expected = { + foo: null + } + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + }) + + describe("discriminator mapping example", () => { + it("returns an example where discriminated field is equal to mapping value", () => { + let definition = { + "type": "array", + "items": { + "oneOf": [ + { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "TYPE1", + "TYPE2" + ] + } + }, + "discriminator": { + "propertyName": "type", + "mapping": { + "TYPE1": "#/components/schemas/FirstDto", + "TYPE2": "#/components/schemas/SecondDto" + } + }, + "$$ref": "examples/swagger-config.yaml#/components/schemas/FirstDto" + }, + { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "TYPE1", + "TYPE2" + ] + } + }, + "discriminator": { + "propertyName": "type", + "mapping": { + "TYPE1": "#/components/schemas/FirstDto", + "TYPE2": "#/components/schemas/SecondDto" + } + }, + "$$ref": "examples/swagger-config.yaml#/components/schemas/SecondDto" + } + ] + } + } + + let expected = [ + { + "type": "TYPE1" + }, { + "type": "TYPE2" + } + ] + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should not throw if expected $$ref is missing, and should fallback to default behavior", () => { + let definition = { + "type": "array", + "items": { + "oneOf": [ + { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "TYPE1", + "TYPE2" + ] + } + }, + "discriminator": { + "propertyName": "type", + "mapping": { + "TYPE1": "#/components/schemas/FirstDto", + "TYPE2": "#/components/schemas/SecondDto" + } + }, + }, + { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "TYPE1", + "TYPE2" + ] + } + }, + "discriminator": { + "propertyName": "type", + "mapping": { + "TYPE1": "#/components/schemas/FirstDto", + "TYPE2": "#/components/schemas/SecondDto" + } + }, + } + ] + } + } + + expect(() => { + sampleFromSchema(definition) + }).not.toThrow() + }) + + }) + + it("should use overrideExample when defined", () => { + const definition = { + type: "object", + properties: { + foo: { + type: "string" + } + }, + example: { + foo: null + } + } + + const expected = { + foo: "override" + } + + expect(sampleFromSchema(definition, {}, expected)).toEqual(expected) + }) + + it("should merge properties with anyOf", () => { + const definition = { + type: "object", + properties: { + foo: { + type: "string" + } + }, + anyOf: [ + { + type: "object", + properties: { + bar: { + type: "boolean" + } + } + } + ] + } + + const expected = { + foo: "string", + bar: true + } + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should merge array item properties with anyOf", () => { + const definition = { + type: "array", + items: { + type: "object", + properties: { + foo: { + type: "string" + } + }, + anyOf: [ + { + type: "object", + properties: { + bar: { + type: "boolean" + } + } + } + ] + } + } + + const expected = [ + { + foo: "string", + bar: true + } + ] + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should merge properties with oneOf", () => { + const definition = { + type: "object", + properties: { + foo: { + type: "string" + } + }, + oneOf: [ + { + type: "object", + properties: { + bar: { + type: "boolean" + } + } + } + ] + } + + const expected = { + foo: "string", + bar: true + } + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should merge array item properties with oneOf", () => { + const definition = { + type: "array", + items: { + type: "object", + properties: { + foo: { + type: "string" + } + }, + oneOf: [ + { + type: "object", + properties: { + bar: { + type: "boolean" + } + } + } + ] + } + } + + const expected = [ + { + foo: "string", + bar: true + } + ] + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should lift items with anyOf", () => { + const definition = { + type: "array", + anyOf: [ + { + type: "array", + items: { + type: "boolean" + } + } + ] + } + + const expected = [ + true + ] + + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should lift items with oneOf", () => { + const definition = { + type: "array", + oneOf: [ + { + type: "array", + items: { + type: "boolean" + } + } + ] + } + + const expected = [ + true + ] + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + it("should ignore minProperties if cannot extend object", () => { + const definition = { + type: "object", + minProperties: 2, + properties: { + foo: { + type: "string" + } + } + } + + const expected = { + foo: "string" + } + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle minProperties in conjunction with additionalProperties", () => { + const definition = { + type: "object", + minProperties: 2, + additionalProperties: { + type: "string" + } + } + + const expected = { + additionalProp1: "string", + additionalProp2: "string" + } + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle minProperties in conjunction with properties and additionalProperties", () => { + const definition = { + type: "object", + minProperties: 2, + additionalProperties: true, + properties: { + foo: { + type: "string" + } + } + } + + const expected = { + foo: "string", + additionalProp1: {} + } + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle minProperties in conjunction with additionalProperties and anyOf", () => { + const definition = { + type: "object", + minProperties: 2, + additionalProperties: true, + anyOf: [ + { + type: "object", + properties: { + foo: { + type: "string" + } + } + } + ] + } + + const expected = { + foo: "string", + additionalProp1: {} + } + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle maxProperties", () => { + const definition = { + type: "object", + maxProperties: 1, + properties: { + foo: { + type: "string" + }, + swaggerUi: { + type: "string" + } + } + } + + const expected = { + foo: "string" + } + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle maxProperties in conjunction with additionalProperties", () => { + const definition = { + type: "object", + maxProperties: 1, + additionalProperties: true + } + + const expected = { + additionalProp1: {} + } + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle maxProperties in conjunction with anyOf", () => { + const definition = { + type: "object", + maxProperties: 1, + anyOf: [ + { + type: "object", + properties: { + foo: { + type: "string" + }, + swaggerUi: { + type: "string" + } + } + } + ] + } + + const expected = { + foo: "string" + } + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle handle maxProperties in conjunction with required", () => { + const definition = { + type: "object", + maxProperties: 1, + required: ["swaggerUi"], + properties: { + foo: { + type: "string" + }, + swaggerUi: { + type: "string", + example: "<3" + } + } + } + + const expected = { + swaggerUi: "<3", + } + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle handle maxProperties in conjunction with anyOf required", () => { + const definition = { + type: "object", + maxProperties: 1, + required: ["swaggerUi"], + anyOf: [ + { + type: "object", + properties: { + foo: { + type: "string" + }, + swaggerUi: { + type: "string", + example: "<3" + } + } + } + ], + } + + const expected = { + swaggerUi: "<3", + } + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle minItems", () => { + const definition = { + type: "array", + minItems: 2, + items: { + type: "string" + } + } + + const expected = ["string", "string"] + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle minItems with example", () => { + const definition = { + type: "array", + minItems: 2, + items: { + type: "string", + example: "some" + }, + } + + const expected = ["some", "some"] + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle minItems in conjunction with oneOf", () => { + const definition = { + type: "array", + minItems: 4, + items: { + oneOf: [ + { + type: "string" + }, + { + type: "number" + } + ] + } + } + + const expected = ["string", 0, "string", 0] + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle maxItems in conjunction with multiple oneOf", () => { + const definition = { + type: "array", + maxItems: 1, + items: { + oneOf: [ + { + type: "string" + }, + { + type: "number" + } + ] + } + } + + const expected = ["string"] + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle minimum", () => { + const definition = { + type: "number", + minimum: 5, + } + + const expected = 5 + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle minimum with exclusive", () => { + const definition = { + type: "number", + minimum: 5, + exclusiveMinimum: true, + } + + const expected = 6 + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + + it("should handle maximum", () => { + const definition = { + type: "number", + maximum: -1, + } + + const expected = -1 + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle maximum with exclusive", () => { + const definition = { + type: "number", + maximum: -1, + exclusiveMaximum: true, + } + + const expected = -2 + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle minLength", () => { + const definition = { + type: "string", + minLength: 7 + } + + const expected = "strings" + + expect(sampleFromSchema(definition)).toEqual(expected) + }) + + it("should handle maxLength", () => { + const definition = { + type: "string", + maxLength: 3 + } + + const expected = "str" + + expect(sampleFromSchema(definition)).toEqual(expected) + }) +}) + +describe("createXMLExample", function () { + let sut = createXMLExample + describe("simple types with xml property", function () { + it("returns tag string when passing type string and xml:{name: \"newtagname\"}", function () { + let definition = { + type: "string", + xml: { + name: "newtagname" + } + } + + expect(sut(definition)).toEqual("\nstring") + }) + + it("returns tag string when passing type string and xml:{name: \"newtagname\", prefix:\"test\"}", function () { + let definition = { + type: "string", + xml: { + name: "newtagname", + prefix: "test" + } + } + + expect(sut(definition)).toEqual("\nstring") + }) + + it("returns tag string when passing type string and xml:{\"namespace\": \"http://swagger.io/schema/sample\", \"prefix\": \"sample\"}", function () { + let definition = { + type: "string", + xml: { + namespace: "http://swagger.io/schema/sample", + prefix: "sample", + name: "name" + } + } + + expect(sut(definition)).toEqual("\nstring") + }) + + it("returns tag string when passing type string and xml:{\"namespace\": \"http://swagger.io/schema/sample\"}", function () { + let definition = { + type: "string", + xml: { + namespace: "http://swagger.io/schema/sample", + name: "name" + } + } + + expect(sut(definition)).toEqual("\nstring") + }) + + it("returns tag test when passing default value", function () { + let expected = "\ntest" + let definition = { + type: "string", + "default": "test", + xml: { + name: "newtagname" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns default value when enum provided", function () { + let expected = "\none" + let definition = { + type: "string", + "default": "one", + "enum": ["two", "one"], + xml: { + name: "newtagname" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns example value when provided", function () { + let expected = "\ntwo" + let definition = { + type: "string", + "default": "one", + "example": "two", + "enum": ["two", "one"], + xml: { + name: "newtagname" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("sets first enum if provided", function () { + let expected = "\none" + let definition = { + type: "string", + "enum": ["one", "two"], + xml: { + name: "newtagname" + } + } + + expect(sut(definition)).toEqual(expected) + }) + }) + + describe("array", function () { + it("returns tag string when passing string items", function () { + let expected = "\nstring" + let definition = { + type: "array", + items: { + type: "string" + }, + xml: { + name: "tagname" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns tag string when passing string items with name", function () { + let expected = "\nstring" + let definition = { + type: "array", + items: { + type: "string", + xml: { + name: "animal" + } + }, + xml: { + name: "animals" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns tag string when passing string items with name", function () { + let expected = "\n\n\tstring\n" + let definition = { + type: "array", + items: { + type: "string", + xml: { + name: "animal" + } + }, + xml: { + wrapped: true, + name: "animals" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("return correct nested wrapped array", function () { + let expected = "\n\n\tstring\n" + let definition = { + type: "array", + items: { + type: "array", + items: { + type: "string" + }, + xml: { + name: "dog" + } + }, + xml: { + wrapped: true, + name: "aliens" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("return correct nested wrapped array with xml", function () { + let expected = "\n\n\t\n\t\tstring\n\t\n" + let definition = { + type: "array", + items: { + type: "array", + items: { + type: "string", + xml: { + name: "dog" + } + }, + xml: { + name: "dogs", + wrapped: true + } + }, + xml: { + wrapped: true, + name: "aliens" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("adds namespace to array", function () { + let expected = "\nstring" + let definition = { + type: "array", + items: { + type: "string", + xml: { + name: "dog", + namespace: "test" + } + }, + xml: { + name: "aliens", + namespace: "test_new" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("adds prefix to array", function () { + let expected = "\nstring" + let definition = { + type: "array", + items: { + type: "string", + xml: { + name: "dog", + prefix: "test" + } + }, + xml: { + name: "aliens", + prefix: "test_new" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("adds prefix to array with no xml in items", function () { + let expected = "\nstring" + let definition = { + type: "array", + items: { + type: "string" + }, + xml: { + name: "dog", + prefix: "test" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("adds namespace to array with no xml in items", function () { + let expected = "\nstring" + let definition = { + type: "array", + items: { + type: "string" + }, + xml: { + name: "dog", + namespace: "test" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("adds namespace to array with wrapped", function () { + let expected = "\n\n\tstring\n" + let definition = { + type: "array", + items: { + type: "string", + xml: { + name: "dog" + } + }, + xml: { + wrapped: true, + name: "aliens", + namespace: "test" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("adds prefix to array with wrapped", function () { + let expected = "\n\n\tstring\n" + let definition = { + type: "array", + items: { + type: "string", + xml: { + name: "dog" + } + }, + xml: { + wrapped: true, + name: "aliens", + prefix: "test" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns wrapped array when type is not passed", function () { + let expected = "\n\n\tstring\n" + let definition = { + items: { + type: "string", + xml: { + name: "animal" + } + }, + xml: { + wrapped: true, + name: "animals" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns array with default values", function () { + let expected = "\none\ntwo" + let definition = { + items: { + type: "string", + xml: { + name: "animal" + } + }, + "default": ["one", "two"], + xml: { + name: "animals" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns array with default values with wrapped=true", function () { + let expected = "\n\n\tone\n\ttwo\n" + let definition = { + items: { + type: "string", + xml: { + name: "animal" + } + }, + "default": ["one", "two"], + xml: { + wrapped: true, + name: "animals" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns array with default values", function () { + let expected = "\none" + let definition = { + items: { + type: "string", + "enum": ["one", "two"], + xml: { + name: "animal" + } + }, + xml: { + name: "animals" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns array with default values with wrapped=true", function () { + let expected = "\n\n\t1\n\t2\n" + let definition = { + items: { + "enum": ["one", "two"], + type: "string", + xml: { + name: "animal" + } + }, + "default": ["1", "2"], + xml: { + wrapped: true, + name: "animals" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns array with example values with ", function () { + let expected = "\n\n\t1\n\t2\n" + let definition = { + type: "object", + properties: { + "animal": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "1", + "2" + ] + } + }, + xml: { + name: "animals" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns array with example values with wrapped=true", function () { + let expected = "\n\n\t1\n\t2\n" + let definition = { + type: "array", + items: { + type: "string", + xml: { + name: "animal" + } + }, + "example": [ "1", "2" ], + xml: { + wrapped: true, + name: "animals" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns array of objects with example values with wrapped=true", function () { + let expected = `\n\n\t\n\t\t1\n\t\tArthur Dent\n\t\n\t\n\t\t2\n\t\tFord Prefect\n\t\n` + let definition = { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "user" + } + }, + "xml": { + "name": "users", + "wrapped": true + }, + "example": [ + { + "id": 1, + "name": "Arthur Dent" + }, + { + "id": 2, + "name": "Ford Prefect" + } + ] + } + + expect(sut(definition)).toEqual(expected) + }) + it("should return additionalProperty example", () => { + let expected = "\n\n\ttest\n" + let definition = { + type: "array", + items: { + type: "object", + properties: { + alien: { + type: "string" + }, + dog: { + type: "integer" + } + } + }, + xml: { + name: "aliens" + } + } + + expect(sut(definition, {}, [{ notalien: "test" }])).toEqual(expected) + }) + it("should return literal example", () => { + let expected = "\n\t\n\t0\n" + let definition = { + type: "array", + items: { + properties: { + alien: { + type: "string" + }, + dog: { + type: "integer" + } + } + }, + xml: { + name: "aliens" + } + } + + expect(sut(definition, {}, expected)).toEqual(expected) + }) +}) + + describe("object", function () { + it("returns object with 2 properties", function () { + let expected = "\n\n\tstring\n\t0\n" + let definition = { + type: "object", + properties: { + alien: { + type: "string" + }, + dog: { + type: "integer" + } + }, + xml: { + name: "aliens" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns object with integer property and array property", function () { + let expected = "\n\n\tstring\n\t0\n" + let definition = { + type: "object", + properties: { + aliens: { + type: "array", + items: { + type: "string" + } + }, + dog: { + type: "integer" + } + }, + xml: { + name: "animals" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns nested objects", function () { + let expected = "\n\n\t\n\t\tstring\n\t\n\tstring\n" + let definition = { + type: "object", + properties: { + aliens: { + type: "object", + properties: { + alien: { + type: "string", + xml: { + name: "alien" + } + } + } + }, + dog: { + type: "string" + } + }, + xml: { + name: "animals" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns object with no readonly fields for parameter", function () { + let expected = "\n\n\t0\n" + let definition = { + type: "object", + properties: { + id: { + type: "integer" + }, + dog: { + readOnly: true, + type: "string" + } + }, + xml: { + name: "animals" + } + } + + expect(sut(definition, { includeReadOnly: false })).toEqual(expected) + }) + + it("returns object with readonly fields for parameter, with includeReadOnly", function () { + let expected = "\n\n\t0\n\tstring\n" + let definition = { + type: "object", + properties: { + id: { + type: "integer" + }, + dog: { + readOnly: true, + type: "string" + } + }, + xml: { + name: "animals" + } + } + + expect(sut(definition, { includeReadOnly: true })).toEqual(expected) + }) + + it("returns object without writeonly fields for parameter", function () { + let expected = "\n\n\t0\n" + let definition = { + type: "object", + properties: { + id: { + type: "integer" + }, + dog: { + writeOnly: true, + type: "string" + } + }, + xml: { + name: "animals" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns object with writeonly fields for parameter, with includeWriteOnly", function () { + let expected = "\n\n\t0\n\tstring\n" + let definition = { + type: "object", + properties: { + id: { + type: "integer" + }, + dog: { + writeOnly: true, + type: "string" + } + }, + xml: { + name: "animals" + } + } + + expect(sut(definition, { includeWriteOnly: true })).toEqual(expected) + }) + + it("returns object with passed property as attribute", function () { + let expected = "\n\n\tstring\n" + let definition = { + type: "object", + properties: { + id: { + type: "integer", + xml: { + attribute: true + } + }, + dog: { + type: "string" + } + }, + xml: { + name: "animals" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns object with passed property as attribute with custom name", function () { + let expected = "\n\n\tstring\n" + let definition = { + type: "object", + properties: { + id: { + type: "integer", + xml: { + attribute: true, + name: "test" + } + }, + dog: { + type: "string" + } + }, + xml: { + name: "animals" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns object with example values in attribute", function () { + let expected = "\n\n\tadmin\n" + let definition = { + type: "object", + properties: { + id: { + type: "integer", + xml: { + attribute: true + } + }, + role:{ + type: "string" + } + }, + xml: { + name: "user" + }, + example: { + id: 42, + role: "admin" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns object with enum values in attribute", function () { + let expected = "\n\n\tstring\n" + let definition = { + type: "object", + properties: { + id: { + type: "string", + "enum": ["one", "two"], + xml: { + attribute: true + } + }, + role:{ + type: "string" + } + }, + xml: { + name: "user" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns object with default values in attribute", function () { + let expected = "\n\n\tstring\n" + let definition = { + type: "object", + properties: { + id: { + type: "string", + "default": "one", + xml: { + attribute: true + } + }, + role:{ + type: "string" + } + }, + xml: { + name: "user" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns object with default values in attribute", function () { + let expected = "\n\n\tstring\n" + let definition = { + type: "object", + properties: { + id: { + type: "string", + "example": "one", + xml: { + attribute: true + } + }, + role:{ + type: "string" + } + }, + xml: { + name: "user" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns object with example value", function () { + let expected = "\n\n\t42\n\tadmin\n" + let definition = { + type: "object", + properties: { + id: { + type: "integer" + }, + role:{ + type: "string" + } + }, + xml: { + name: "user" + }, + example: { + id: 42, + role: "admin" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns object with additional props", function () { + let expected = "\n\n\tstring\n\tstring\n\tstring\n\tstring\n" + let definition = { + type: "object", + properties: { + dog: { + type: "string" + } + }, + additionalProperties: { + type: "string" + }, + xml: { + name: "animals" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns object with additional props =true", function () { + let expected = "\n\n\tstring\n\tAnything can be here\n" + let definition = { + type: "object", + properties: { + dog: { + type: "string" + } + }, + additionalProperties: true, + xml: { + name: "animals" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns object with 2 properties with no type passed but properties", function () { + let expected = "\n\n\tstring\n\t0\n" + let definition = { + properties: { + alien: { + type: "string" + }, + dog: { + type: "integer" + } + }, + xml: { + name: "aliens" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + it("returns object with additional props with no type passed", function () { + let expected = "\n\n\tstring\n\tstring\n\tstring\n" + let definition = { + additionalProperties: { + type: "string" + }, + xml: { + name: "animals" + } + } + + expect(sut(definition)).toEqual(expected) + }) + + + it("should use overrideExample when defined", () => { + const expected = "\n\n\toverride\n" + + const definition = { + type: "object", + properties: { + foo: { + type: "string", + xml: { + name: "foo" + } + } + }, + example: { + foo: null + }, + xml: { + name: "bar" + } + } + + const overrideExample = { + foo: "override" + } + + expect(sut(definition, {}, overrideExample)).toEqual(expected) + }) + + it("should return additionalProperty example", () => { + let expected = "\n\n\ttest\n\t1\n" + let definition = { + type: "object", + properties: { + alien: { + type: "string" + }, + dog: { + type: "integer" + } + }, + xml: { + name: "aliens" + } + } + + expect(sut(definition, {}, { alien: "test", dog: 1 })).toEqual(expected) + }) + it("should return literal example", () => { + let expected = "\n\t\n\t0\n" + let definition = { + type: "object", + properties: { + alien: { + type: "string" + }, + dog: { + type: "integer" + } + }, + xml: { + name: "aliens" + } + } + + expect(sut(definition, {}, expected)).toEqual(expected) + }) + it("should use exampleOverride for attr too", () => { + let expected = "\n\n" + let definition = { + type: "object", + properties: { + test: { + type: "string", + xml: { + attribute: true + } + } + }, + xml: { + name: "aliens" + } + } + + expect(sut(definition, {}, { test: "probe" })).toEqual(expected) + }) + }) + + it("should handle handle maxProperties in conjunction with required", function () { + const definition = { + type: "object", + maxProperties: 1, + required: ["swaggerUi"], + xml: { + name: "probe" + }, + properties: { + foo: { + type: "string" + }, + swaggerUi: { + type: "string", + example: "cool" + } + } + } + + const expected = ` + +\tcool +` + + expect(sut(definition)).toEqual(expected) + }) +}) + +describe("memoizedSampleFromSchema", () => { + it("should sequentially update memoized overrideExample", () => { + const definition = { + type: "object", + properties: { + foo: { + type: "string" + } + }, + example: { + foo: null + } + } + + const expected = { + foo: "override" + } + expect(memoizedSampleFromSchema(definition, {}, expected)).toEqual(expected) + + const updatedExpected = { + foo: "cat" + } + expect(memoizedSampleFromSchema(definition, {}, updatedExpected)).toEqual(updatedExpected) + }) +}) + +describe("memoizedCreateXMLExample", () => { + it("should sequentially update memoized overrideExample", () => { + const expected = "\n\n\toverride\n" + + const definition = { + type: "object", + properties: { + foo: { + type: "string", + xml: { + name: "foo" + } + } + }, + example: { + foo: null + }, + xml: { + name: "bar" + } + } + + const overrideExample = { + foo: "override" + } + expect(memoizedCreateXMLExample(definition, {}, overrideExample)).toEqual(expected) + + const updatedOverrideExample = { + foo: "cat" + } + const updatedExpected = "\n\n\tcat\n" + expect(memoizedCreateXMLExample(definition, {}, updatedOverrideExample)).toEqual(updatedExpected) + }) +}) diff --git a/frontend/web/api-doc/test/unit/core/plugins/spec/actions.js b/frontend/web/api-doc/test/unit/core/plugins/spec/actions.js new file mode 100644 index 0000000..d2a6803 --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/plugins/spec/actions.js @@ -0,0 +1,229 @@ + +import { fromJS } from "immutable" +import { execute, executeRequest, changeParamByIdentity, updateEmptyParamInclusion } from "corePlugins/spec/actions" + +describe("spec plugin - actions", function(){ + + describe("execute", function(){ + + xit("should collect a full request and call fn.executeRequest", function(){ + // Given + const system = { + fn: { + fetch: 1 + }, + specActions: { + executeRequest: jest.fn() + }, + specSelectors: { + spec: () => fromJS({spec: 1}), + parameterValues: () => fromJS({values: 2}), + contentTypeValues: () => fromJS({requestContentType: "one", responseContentType: "two"}) + } + } + + // When + let executeFn = execute({ path: "/one", method: "get"}) + executeFn(system) + + // Then + expect(system.specActions.executeRequest.calls[0][0]).toEqual({ + fetch: 1, + method: "get", + pathName: "/one", + parameters: { + values: 2 + }, + requestContentType: "one", + responseContentType: "two", + spec: { + spec: 1 + } + }) + }) + + xit("should allow passing _extra_ properties to executeRequest", function(){ + + // Given + const system = { + fn: {}, + specActions: { + executeRequest: jest.fn() + }, + specSelectors: { + spec: () => fromJS({}), + parameterValues: () => fromJS({}), + contentTypeValues: () => fromJS({}) + } + } + + // When + let executeFn = execute({ hi: "hello" }) + executeFn(system) + + // Then + expect(system.specActions.executeRequest.calls[0][0]).toContain({hi: "hello"}) + }) + + }) + + describe("executeRequest", function(){ + + xit("should call fn.execute with arg ", function(){ + + const system = { + fn: { + execute: jest.fn().mockImplementation(() => Promise.resolve({})) + }, + specActions: { + setResponse: jest.fn() + } + } + + // When + let executeFn = executeRequest({one: 1}) + let res = executeFn(system) + + // Then + expect(res).toBeInstanceOf(Promise) + expect(system.fn.execute.mock.calls.length).toEqual(1) + expect(system.fn.execute.mock.calls[0][0]).toEqual({ + one: 1 + }) + }) + + it("should pass requestInterceptor/responseInterceptor to fn.execute", async () => { + // Given + let configs = { + requestInterceptor: jest.fn(), + responseInterceptor: jest.fn() + } + const system = { + fn: { + buildRequest: jest.fn(), + execute: jest.fn().mockImplementation(() => Promise.resolve({})) + }, + specActions: { + executeRequest: jest.fn(), + setMutatedRequest: jest.fn(), + setRequest: jest.fn(), + setResponse: jest.fn() + }, + specSelectors: { + spec: () => fromJS({}), + parameterValues: () => fromJS({}), + contentTypeValues: () => fromJS({}), + url: () => fromJS({}), + isOAS3: () => false + }, + getConfigs: () => configs + } + // When + let executeFn = executeRequest({ + pathName: "/one", + method: "GET", + operation: fromJS({operationId: "getOne"}) + }) + await executeFn(system) + + // Then + expect(system.fn.execute.mock.calls.length).toEqual(1) + expect(Object.keys(system.fn.execute.mock.calls[0][0])).toContain("requestInterceptor") + expect(system.fn.execute.mock.calls[0][0]).toEqual(expect.objectContaining({ + responseInterceptor: configs.responseInterceptor + })) + expect(system.specActions.setMutatedRequest.mock.calls.length).toEqual(0) + expect(system.specActions.setRequest.mock.calls.length).toEqual(1) + + + let wrappedRequestInterceptor = system.fn.execute.mock.calls[0][0].requestInterceptor + await wrappedRequestInterceptor(system.fn.execute.mock.calls[0][0]) + expect(configs.requestInterceptor.mock.calls.length).toEqual(1) + expect(system.specActions.setMutatedRequest.mock.calls.length).toEqual(1) + expect(system.specActions.setRequest.mock.calls.length).toEqual(1) + }) + }) + + xit("should call specActions.setResponse, when fn.execute resolves", function(){ + + const response = {serverResponse: true} + const system = { + fn: { + execute: jest.fn().mockImplementation(() => Promise.resolve(response)) + }, + specActions: { + setResponse: jest.fn() + }, + errActions: { + newSpecErr: jest.fn() + } + } + + // When + let executeFn = executeRequest({ + pathName: "/one", + method: "GET" + }) + let executePromise = executeFn(system) + + // Then + return executePromise.then( () => { + expect(system.specActions.setResponse.calls.length).toEqual(1) + expect(system.specActions.setResponse.calls[0].arguments).toEqual([ + "/one", + "GET", + response + ]) + }) + }) + + describe.skip("requestResolvedSubtree", () => { + it("should return a promise ", function() { + }) + }) + + it.skip("should call errActions.newErr, if the fn.execute rejects", function(){ + }) + + describe("changeParamByIdentity", function () { + it("should map its arguments to a payload", function () { + const pathMethod = ["/one", "get"] + const param = fromJS({ + name: "body", + in: "body" + }) + const value = "my value" + const isXml = false + + const result = changeParamByIdentity(pathMethod, param, value, isXml) + + expect(result).toEqual({ + type: "spec_update_param", + payload: { + path: pathMethod, + param, + value, + isXml + } + }) + }) + }) + + describe("updateEmptyParamInclusion", function () { + it("should map its arguments to a payload", function () { + const pathMethod = ["/one", "get"] + + const result = updateEmptyParamInclusion(pathMethod, "param", "query", true) + + expect(result).toEqual({ + type: "spec_update_empty_param_inclusion", + payload: { + pathMethod, + paramName: "param", + paramIn: "query", + includeEmptyValue: true + } + }) + }) + }) +}) diff --git a/frontend/web/api-doc/test/unit/core/plugins/spec/assets/petstore.json b/frontend/web/api-doc/test/unit/core/plugins/spec/assets/petstore.json new file mode 100644 index 0000000..5a8081a --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/plugins/spec/assets/petstore.json @@ -0,0 +1,1035 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", + "version": "1.0.0", + "title": "Swagger Petstore", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "host": "petstore.swagger.io", + "basePath": "/v2", + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } + }, + { + "name": "store", + "description": "Access to Petstore orders" + }, + { + "name": "user", + "description": "Operations about user", + "externalDocs": { + "description": "Find out more about our store", + "url": "http://swagger.io" + } + } + ], + "schemes": [ + "http" + ], + "paths": { + "/pet": { + "post": { + "tags": [ + "pet" + ], + "summary": "Add a new pet to the store", + "description": "", + "operationId": "addPet", + "consumes": [ + "application/json", + "application/xml" + ], + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Pet object that needs to be added to the store", + "required": true, + "schema": { + "$ref": "#/definitions/Pet" + } + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "put": { + "tags": [ + "pet" + ], + "summary": "Update an existing pet", + "description": "", + "operationId": "updatePet", + "consumes": [ + "application/json", + "application/xml" + ], + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Pet object that needs to be added to the store", + "required": true, + "schema": { + "$ref": "#/definitions/Pet" + } + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "405": { + "description": "Validation exception" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByStatus": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by status", + "description": "Multiple status values can be provided with comma separated strings", + "operationId": "findPetsByStatus", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": true, + "type": "array", + "items": { + "type": "string", + "enum": [ + "available", + "pending", + "sold" + ], + "default": "available" + }, + "collectionFormat": "multi" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Pet" + } + } + }, + "400": { + "description": "Invalid status value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByTags": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by tags", + "description": "Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + "operationId": "findPetsByTags", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "Tags to filter by", + "required": true, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Pet" + } + } + }, + "400": { + "description": "Invalid tag value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ], + "deprecated": true + } + }, + "/pet/{petId}": { + "get": { + "tags": [ + "pet" + ], + "summary": "Find pet by ID", + "description": "Returns a single pet", + "operationId": "getPetById", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to return", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/Pet" + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Updates a pet in the store with form data", + "description": "", + "operationId": "updatePetWithForm", + "consumes": [ + "application/x-www-form-urlencoded" + ], + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet that needs to be updated", + "required": true, + "type": "integer", + "format": "int64" + }, + { + "name": "name", + "in": "formData", + "description": "Updated name of the pet", + "required": false, + "type": "string" + }, + { + "name": "status", + "in": "formData", + "description": "Updated status of the pet", + "required": false, + "type": "string" + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "delete": { + "tags": [ + "pet" + ], + "summary": "Deletes a pet", + "description": "", + "operationId": "deletePet", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "api_key", + "in": "header", + "required": false, + "type": "string" + }, + { + "name": "petId", + "in": "path", + "description": "Pet id to delete", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/{petId}/uploadImage": { + "post": { + "tags": [ + "pet" + ], + "summary": "uploads an image", + "description": "", + "operationId": "uploadFile", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to update", + "required": true, + "type": "integer", + "format": "int64" + }, + { + "name": "additionalMetadata", + "in": "formData", + "description": "Additional data to pass to server", + "required": false, + "type": "string" + }, + { + "name": "file", + "in": "formData", + "description": "file to upload", + "required": false, + "type": "file" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/ApiResponse" + } + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/store/inventory": { + "get": { + "tags": [ + "store" + ], + "summary": "Returns pet inventories by status", + "description": "Returns a map of status codes to quantities", + "operationId": "getInventory", + "produces": [ + "application/json" + ], + "parameters": [], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } + } + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/store/order": { + "post": { + "tags": [ + "store" + ], + "summary": "Place an order for a pet", + "description": "", + "operationId": "placeOrder", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "order placed for purchasing the pet", + "required": true, + "schema": { + "$ref": "#/definitions/Order" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/Order" + } + }, + "400": { + "description": "Invalid Order" + } + } + } + }, + "/store/order/{orderId}": { + "get": { + "tags": [ + "store" + ], + "summary": "Find purchase order by ID", + "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", + "operationId": "getOrderById", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of pet that needs to be fetched", + "required": true, + "type": "integer", + "maximum": 10, + "minimum": 1, + "format": "int64" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/Order" + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + }, + "delete": { + "tags": [ + "store" + ], + "summary": "Delete purchase order by ID", + "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", + "operationId": "deleteOrder", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of the order that needs to be deleted", + "required": true, + "type": "integer", + "minimum": 1, + "format": "int64" + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + } + }, + "/user": { + "post": { + "tags": [ + "user" + ], + "summary": "Create user", + "description": "This can only be done by the logged in user.", + "operationId": "createUser", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Created user object", + "required": true, + "schema": { + "$ref": "#/definitions/User" + } + } + ], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/createWithArray": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array", + "description": "", + "operationId": "createUsersWithArrayInput", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "List of user object", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/User" + } + } + } + ], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/createWithList": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array", + "description": "", + "operationId": "createUsersWithListInput", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "List of user object", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/User" + } + } + } + ], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/login": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs user into the system", + "description": "", + "operationId": "loginUser", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "username", + "in": "query", + "description": "The user name for login", + "required": true, + "type": "string" + }, + { + "name": "password", + "in": "query", + "description": "The password for login in clear text", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "string" + }, + "headers": { + "X-Rate-Limit": { + "type": "integer", + "format": "int32", + "description": "calls per hour allowed by the user" + }, + "X-Expires-After": { + "type": "string", + "format": "date-time", + "description": "date in UTC when token expires" + } + } + }, + "400": { + "description": "Invalid username/password supplied" + } + } + } + }, + "/user/logout": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs out current logged in user session", + "description": "", + "operationId": "logoutUser", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/{username}": { + "get": { + "tags": [ + "user" + ], + "summary": "Get user by user name", + "description": "", + "operationId": "getUserByName", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be fetched. Use user1 for testing. ", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/User" + } + }, + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + }, + "put": { + "tags": [ + "user" + ], + "summary": "Updated user", + "description": "This can only be done by the logged in user.", + "operationId": "updateUser", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "username", + "in": "path", + "description": "name that need to be updated", + "required": true, + "type": "string" + }, + { + "in": "body", + "name": "body", + "description": "Updated user object", + "required": true, + "schema": { + "$ref": "#/definitions/User" + } + } + ], + "responses": { + "400": { + "description": "Invalid user supplied" + }, + "404": { + "description": "User not found" + } + } + }, + "delete": { + "tags": [ + "user" + ], + "summary": "Delete user", + "description": "This can only be done by the logged in user.", + "operationId": "deleteUser", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be deleted", + "required": true, + "type": "string" + } + ], + "responses": { + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + } + } + }, + "securityDefinitions": { + "petstore_auth": { + "type": "oauth2", + "authorizationUrl": "http://petstore.swagger.io/oauth/dialog", + "flow": "implicit", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + }, + "definitions": { + "Order": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "petId": { + "type": "integer", + "format": "int64" + }, + "quantity": { + "type": "integer", + "format": "int32" + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "enum": [ + "placed", + "approved", + "delivered" + ] + }, + "complete": { + "type": "boolean", + "default": false + } + }, + "xml": { + "name": "Order" + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "username": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "userStatus": { + "type": "integer", + "format": "int32", + "description": "User Status" + } + }, + "xml": { + "name": "User" + } + }, + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Category" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Tag" + } + }, + "Pet": { + "type": "object", + "required": [ + "name", + "photoUrls" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "category": { + "$ref": "#/definitions/Category" + }, + "name": { + "type": "string", + "example": "doggie" + }, + "photoUrls": { + "type": "array", + "xml": { + "name": "photoUrl", + "wrapped": true + }, + "items": { + "type": "string" + } + }, + "tags": { + "type": "array", + "xml": { + "name": "tag", + "wrapped": true + }, + "items": { + "$ref": "#/definitions/Tag" + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "xml": { + "name": "Pet" + } + }, + "ApiResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "type": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + }, + "externalDocs": { + "description": "Find out more about Swagger", + "url": "http://swagger.io" + } +} diff --git a/frontend/web/api-doc/test/unit/core/plugins/spec/reducer.js b/frontend/web/api-doc/test/unit/core/plugins/spec/reducer.js new file mode 100644 index 0000000..9c56a4a --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/plugins/spec/reducer.js @@ -0,0 +1,202 @@ + +import { fromJS } from "immutable" +import reducer from "corePlugins/spec/reducers" + +describe("spec plugin - reducer", function(){ + + describe("update operation meta value", function() { + it("should update the operation metadata at the specified key", () => { + const updateOperationValue = reducer["spec_update_operation_meta_value"] + + const state = fromJS({ + resolved: { + "paths": { + "/pet": { + "post": { + "description": "my operation" + } + } + } + } + }) + + let result = updateOperationValue(state, { + payload: { + path: ["/pet", "post"], + value: "application/json", + key: "consumes_value" + } + }) + + let expectedResult = { + resolved: { + "paths": { + "/pet": { + "post": { + "description": "my operation" + } + } + } + }, + meta: { + paths: { + "/pet": { + post: { + "consumes_value": "application/json" + } + } + } + } + } + + expect(result.toJS()).toEqual(expectedResult) + }) + + it("shouldn't throw an error if we try to update the consumes_value of a null operation", () => { + const updateOperationValue = reducer["spec_update_operation_meta_value"] + + const state = fromJS({ + resolved: { + "paths": { + "/pet": { + "post": null + } + } + } + }) + + let result = updateOperationValue(state, { + payload: { + path: ["/pet", "post"], + value: "application/json", + key: "consumes_value" + } + }) + + expect(result.toJS()).toEqual(state.toJS()) + }) + }) + + describe("set response value", function() { + it("should combine the response and error objects", () => { + const setResponse = reducer["spec_set_response"] + + const path = "/pet/post" + const method = "POST" + + const state = fromJS({}) + const result = setResponse(state, { + payload: { + path: path, + method: method, + res: { + error: true, + err: { + message: "Not Found", + name: "Error", + response: { + data: "response data", + headers: { + key: "value" + }, + ok: false, + status: 404, + statusText: "Not Found" + }, + status: 404, + statusCode: 404 + } + } + } + }) + + let expectedResult = { + error: true, + message: "Not Found", + name: "Error", + data: "response data", + headers: { + key: "value" + }, + ok: false, + status: 404, + statusCode: 404, + statusText: "Not Found" + } + + const response = result.getIn(["responses", path, method]).toJS() + expect(response).toEqual(expectedResult) + }) + }) + describe("SPEC_UPDATE_PARAM", function() { + it("should store parameter values by {in}.{name}", () => { + const updateParam = reducer["spec_update_param"] + + const path = "/pet/post" + const method = "POST" + + const state = fromJS({}) + const result = updateParam(state, { + payload: { + path: [path, method], + paramName: "myBody", + paramIn: "body", + value: `{ "a": 123 }`, + isXml: false + } + }) + + const response = result.getIn(["meta", "paths", path, method, "parameters", "body.myBody", "value"]) + expect(response).toEqual(`{ "a": 123 }`) + }) + it("should store parameter values by identity", () => { + const updateParam = reducer["spec_update_param"] + + const path = "/pet/post" + const method = "POST" + + const param = fromJS({ + name: "myBody", + in: "body", + schema: { + type: "string" + } + }) + + const state = fromJS({}) + const result = updateParam(state, { + payload: { + param, + path: [path, method], + value: `{ "a": 123 }`, + isXml: false + } + }) + + const value = result.getIn(["meta", "paths", path, method, "parameters", `body.myBody.hash-${param.hashCode()}`, "value"]) + expect(value).toEqual(`{ "a": 123 }`) + }) + }) + describe("SPEC_UPDATE_EMPTY_PARAM_INCLUSION", function() { + it("should store parameter values by {in}.{name}", () => { + const updateParam = reducer["spec_update_empty_param_inclusion"] + + const path = "/pet/post" + const method = "POST" + + const state = fromJS({}) + + const result = updateParam(state, { + payload: { + pathMethod: [path, method], + paramName: "param", + paramIn: "query", + includeEmptyValue: true + } + }) + + const response = result.getIn(["meta", "paths", path, method, "parameter_inclusions", "query.param"]) + expect(response).toEqual(true) + }) + }) +}) diff --git a/frontend/web/api-doc/test/unit/core/plugins/spec/selectors.js b/frontend/web/api-doc/test/unit/core/plugins/spec/selectors.js new file mode 100644 index 0000000..b496237 --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/plugins/spec/selectors.js @@ -0,0 +1,1378 @@ + +import { fromJS } from "immutable" +import { fromJSOrdered } from "core/utils" +import { + definitions, + parameterValues, + contentTypeValues, + operationScheme, + specJsonWithResolvedSubtrees, + producesOptionsFor, + operationWithMeta, + parameterWithMeta, + parameterWithMetaByIdentity, + parameterInclusionSettingFor, + consumesOptionsFor, + taggedOperations, + isMediaTypeSchemaPropertiesEqual +} from "corePlugins/spec/selectors" + +import Petstore from "./assets/petstore.json" + +describe("definitions", function(){ + it("should return definitions by default", function(){ + + // Given + const spec = fromJS({ + json: { + swagger: "2.0", + definitions: { + a: { + type: "string" + }, + b: { + type: "string" + } + } + } + }) + + // When + let res = definitions(spec) + + // Then + expect(res.toJS()).toEqual({ + a: { + type: "string" + }, + b: { + type: "string" + } + }) + }) + it("should return an empty Map when missing definitions", function(){ + + // Given + const spec = fromJS({ + json: { + swagger: "2.0" + } + }) + + // When + let res = definitions(spec) + + // Then + expect(res.toJS()).toEqual({}) + }) + it("should return an empty Map when given non-object definitions", function(){ + + // Given + const spec = fromJS({ + json: { + swagger: "2.0", + definitions: "..." + } + }) + + // When + let res = definitions(spec) + + // Then + expect(res.toJS()).toEqual({}) + }) +}) + +describe("parameterValue", function(){ + + it("should return Map({}) if no path found", function(){ + + // Given + const spec = fromJS({ }) + + // When + let paramValues = parameterValues(spec, ["/one", "get"]) + + // Then + expect(paramValues.toJS()).toEqual({}) + + }) + + it("should return a hash of [parameterName]: value", function(){ + + // Given + const spec = fromJS({ + json: { + paths: { + "/one": { + get: { + parameters: [ + { name: "one", in: "query", value: 1}, + { name: "two", in: "query", value: "duos"} + ] + } + } + } + } + }) + + // When + let paramValues = parameterValues(spec, ["/one", "get"]) + + // Then + expect(paramValues.toJS()).toEqual({ + "query.one": 1, + "query.two": "duos" + }) + + }) + +}) + +describe("contentTypeValues", function(){ + it("should return { requestContentType, responseContentType } from an operation", function(){ + // Given + let state = fromJS({ + json: { + paths: { + "/one": { + get: {} + } + } + }, + meta: { + paths: { + "/one": { + get: { + "consumes_value": "one", + "produces_value": "two" + } + } + } + } + }) + + // When + let contentTypes = contentTypeValues(state, [ "/one", "get" ]) + // Then + expect(contentTypes.toJS()).toEqual({ + requestContentType: "one", + responseContentType: "two" + }) + }) + + it("should default to the first `produces` array value if current is not set", function(){ + // Given + let state = fromJS({ + json: { + paths: { + "/one": { + get: { + produces: [ + "application/xml", + "application/whatever" + ] + } + } + } + }, + meta: { + paths: { + "/one": { + get: { + "consumes_value": "one" + } + } + } + } + }) + + // When + let contentTypes = contentTypeValues(state, [ "/one", "get" ]) + // Then + expect(contentTypes.toJS()).toEqual({ + requestContentType: "one", + responseContentType: "application/xml" + }) + }) + + it("should default to `application/json` if a default produces value is not available", function(){ + // Given + let state = fromJS({ + json: { + paths: { + "/one": { + get: {} + } + } + }, + meta: { + paths: { + "/one": { + get: { + "consumes_value": "one" + } + } + } + } + }) + + // When + let contentTypes = contentTypeValues(state, [ "/one", "get" ]) + // Then + expect(contentTypes.toJS()).toEqual({ + requestContentType: "one", + responseContentType: "application/json" + }) + }) + + it("should prioritize consumes value first from an operation", function(){ + // Given + let state = fromJS({ + json: { + paths: { + "/one": { + get: { + "parameters": [{ + "type": "file" + }], + } + } + } + }, + meta: { + paths: { + "/one": { + get: { + "consumes_value": "one", + } + } + } + } + }) + + // When + let contentTypes = contentTypeValues(state, [ "/one", "get" ]) + // Then + expect(contentTypes.toJS().requestContentType).toEqual("one") + }) + + it("should fallback to multipart/form-data if there is no consumes value but there is a file parameter", function(){ + // Given + let state = fromJS({ + json: { + paths: { + "/one": { + get: { + "parameters": [{ + "type": "file" + }], + } + } + } + } + }) + + // When + let contentTypes = contentTypeValues(state, [ "/one", "get" ]) + // Then + expect(contentTypes.toJS().requestContentType).toEqual("multipart/form-data") + }) + + it("should fallback to application/x-www-form-urlencoded if there is no consumes value, no file parameter, but there is a formData parameter", function(){ + // Given + let state = fromJS({ + json: { + paths: { + "/one": { + get: { + "parameters": [{ + "type": "formData" + }], + } + } + } + } + }) + + // When + let contentTypes = contentTypeValues(state, [ "/one", "get" ]) + // Then + expect(contentTypes.toJS().requestContentType).toEqual("application/x-www-form-urlencoded") + }) + + it("should return nothing, if the operation does not exist", function(){ + // Given + let state = fromJS({ }) + + // When + let contentTypes = contentTypeValues(state, [ "/one", "get" ]) + // Then + expect(contentTypes.toJS()).toEqual({ + requestContentType: undefined, + responseContentType: undefined + }) + }) + +}) + +describe("operationScheme", function(){ + + it("should return the correct scheme for a remote spec that doesn't specify a scheme", function(){ + // Given + let state = fromJS({ + url: "https://generator.swagger.io/api/swagger.json", + json: { + paths: { + "/one": { + get: { + "consumes_value": "one", + "produces_value": "two" + } + } + } + } + }) + + // When + let scheme = operationScheme(state, ["/one"], "get") + // Then + expect(scheme).toEqual("https") + }) + + // it("should be ok, if no operation found", function(){ + // // Given + // let state = fromJS({ }) + // + // // When + // let contentTypes = contentTypeValues(state, [ "/one", "get" ]) + // // Then + // expect(contentTypes.toJS()).toEqual({ + // requestContentType: undefined, + // responseContentType: undefined + // }) + // }) + +}) + +describe("specJsonWithResolvedSubtrees", function(){ + + it("should return a correctly merged tree", function(){ + // Given + let state = fromJS({ + json: { + definitions: { + Asdf: { + $ref: "#/some/path", + randomKey: "this should be removed b/c siblings of $refs must be removed, per the specification", + description: "same for this key" + }, + Fgsfds: { + $ref: "#/another/path" + }, + OtherDef: { + description: "has no refs" + } + } + }, + resolvedSubtrees: { + definitions: { + Asdf: { + type: "object", + $$ref: "#/some/path" + } + } + } + }) + + // When + let result = specJsonWithResolvedSubtrees(state) + // Then + expect(result.toJS()).toEqual({ + definitions: { + Asdf: { + type: "object", + $$ref: "#/some/path" + }, + Fgsfds: { + $ref: "#/another/path" + }, + OtherDef: { + description: "has no refs" + } + } + }) + }) + it("should preserve initial map key ordering", function(){ + // Given + let state = fromJSOrdered({ + json: Petstore, + resolvedSubtrees: { + paths: { + "/pet/{petId}": { + post: { + tags: [ + "pet" + ], + summary: "Updates a pet in the store with form data", + description: "", + operationId: "updatePetWithForm", + consumes: [ + "application/x-www-form-urlencoded" + ], + produces: [ + "application/xml", + "application/json" + ], + parameters: [ + { + name: "petId", + "in": "path", + description: "ID of pet that needs to be updated", + required: true, + type: "integer", + format: "int64" + }, + { + name: "name", + "in": "formData", + description: "Updated name of the pet", + required: false, + type: "string" + }, + { + name: "status", + "in": "formData", + description: "Updated status of the pet", + required: false, + type: "string" + } + ], + responses: { + "405": { + description: "Invalid input" + } + }, + security: [ + { + petstore_auth: [ + "write:pets", + "read:pets" + ] + } + ], + __originalOperationId: "updatePetWithForm" + } + } + } + } + }) + + // When + let result = specJsonWithResolvedSubtrees(state) + + // Then + const correctOrder = [ + "/pet", + "/pet/findByStatus", + "/pet/findByTags", + "/pet/{petId}", + "/pet/{petId}/uploadImage", + "/store/inventory", + "/store/order", + "/store/order/{orderId}", + "/user", + "/user/createWithArray", + "/user/createWithList", + "/user/login", + "/user/logout", + "/user/{username}" + ] + expect(state.getIn(["json", "paths"]).keySeq().toJS()).toEqual(correctOrder) + expect(result.getIn(["paths"]).keySeq().toJS()).toEqual(correctOrder) + }) +}) + +describe("operationWithMeta", function() { + it("should support merging in {in}.{name} keyed param metadata", function () { + const state = fromJS({ + json: { + paths: { + "/": { + "get": { + parameters: [ + { + name: "myBody", + in: "body" + } + ] + } + } + } + }, + meta: { + paths: { + "/": { + "get": { + parameters: { + "body.myBody": { + value: "abc123" + } + } + } + } + } + } + }) + + const result = operationWithMeta(state, "/", "get") + + expect(result.toJS()).toEqual({ + parameters: [ + { + name: "myBody", + in: "body", + value: "abc123" + } + ] + }) + }) + it("should support merging in hash-keyed param metadata", function () { + const bodyParam = fromJS({ + name: "myBody", + in: "body" + }) + + const state = fromJS({ + json: { + paths: { + "/": { + "get": { + parameters: [ + bodyParam + ] + } + } + } + }, + meta: { + paths: { + "/": { + "get": { + parameters: { + [`body.myBody.hash-${bodyParam.hashCode()}`]: { + value: "abc123" + } + } + } + } + } + } + }) + + const result = operationWithMeta(state, "/", "get") + + expect(result.toJS()).toEqual({ + parameters: [ + { + name: "myBody", + in: "body", + value: "abc123" + } + ] + }) + }) +}) +describe("parameterWithMeta", function() { + it("should support merging in {in}.{name} keyed param metadata", function () { + const state = fromJS({ + json: { + paths: { + "/": { + "get": { + parameters: [ + { + name: "myBody", + in: "body" + } + ] + } + } + } + }, + meta: { + paths: { + "/": { + "get": { + parameters: { + "body.myBody": { + value: "abc123" + } + } + } + } + } + } + }) + + const result = parameterWithMeta(state, ["/", "get"], "myBody", "body") + + expect(result.toJS()).toEqual({ + name: "myBody", + in: "body", + value: "abc123" + }) + }) + it("should give best-effort when encountering hash-keyed param metadata", function () { + const bodyParam = fromJS({ + name: "myBody", + in: "body" + }) + + const state = fromJS({ + json: { + paths: { + "/": { + "get": { + parameters: [ + bodyParam + ] + } + } + } + }, + meta: { + paths: { + "/": { + "get": { + parameters: { + [`body.myBody.hash-${bodyParam.hashCode()}`]: { + value: "abc123" + } + } + } + } + } + } + }) + + const result = parameterWithMeta(state, ["/", "get"], "myBody", "body") + + expect(result.toJS()).toEqual({ + name: "myBody", + in: "body", + value: "abc123" + }) + }) + +}) +describe("parameterWithMetaByIdentity", function() { + it("should support merging in {in}.{name} keyed param metadata", function () { + const bodyParam = fromJS({ + name: "myBody", + in: "body" + }) + + const state = fromJS({ + json: { + paths: { + "/": { + "get": { + parameters: [bodyParam] + } + } + } + }, + meta: { + paths: { + "/": { + "get": { + parameters: { + "body.myBody": { + value: "abc123" + } + } + } + } + } + } + }) + + const result = parameterWithMetaByIdentity(state, ["/", "get"], bodyParam) + + expect(result.toJS()).toEqual({ + name: "myBody", + in: "body", + value: "abc123" + }) + }) + it("should support merging in hash-keyed param metadata", function () { + const bodyParam = fromJS({ + name: "myBody", + in: "body" + }) + + const state = fromJS({ + json: { + paths: { + "/": { + "get": { + parameters: [ + bodyParam + ] + } + } + } + }, + meta: { + paths: { + "/": { + "get": { + parameters: { + [`body.myBody.hash-${bodyParam.hashCode()}`]: { + value: "abc123" + } + } + } + } + } + } + }) + + const result = parameterWithMetaByIdentity(state, ["/", "get"], bodyParam) + + expect(result.toJS()).toEqual({ + name: "myBody", + in: "body", + value: "abc123" + }) + }) +}) +describe("parameterInclusionSettingFor", function() { + it("should support getting {in}.{name} param inclusion settings", function () { + const param = fromJS({ + name: "param", + in: "query", + allowEmptyValue: true + }) + + const state = fromJS({ + json: { + paths: { + "/": { + "get": { + parameters: [ + param + ] + } + } + } + }, + meta: { + paths: { + "/": { + "get": { + "parameter_inclusions": { + [`query.param`]: true + } + } + } + } + } + }) + + const result = parameterInclusionSettingFor(state, ["/", "get"], "param", "query") + + expect(result).toEqual(true) + }) +}) +describe("producesOptionsFor", function() { + it("should return an operation produces value", function () { + const state = fromJS({ + json: { + paths: { + "/": { + "get": { + description: "my operation", + produces: [ + "operation/one", + "operation/two", + ] + } + } + } + } + }) + + const result = producesOptionsFor(state, ["/", "get"]) + + expect(result.toJS()).toEqual([ + "operation/one", + "operation/two", + ]) + }) + it("should return a path item produces value", function () { + const state = fromJS({ + json: { + paths: { + "/": { + "get": { + description: "my operation", + produces: [ + "path-item/one", + "path-item/two", + ] + } + } + } + } + }) + + const result = producesOptionsFor(state, ["/", "get"]) + + expect(result.toJS()).toEqual([ + "path-item/one", + "path-item/two", + ]) + }) + it("should return a global produces value", function () { + const state = fromJS({ + json: { + produces: [ + "global/one", + "global/two", + ], + paths: { + "/": { + "get": { + description: "my operation" + } + } + } + } + }) + + const result = producesOptionsFor(state, ["/", "get"]) + + expect(result.toJS()).toEqual([ + "global/one", + "global/two", + ]) + }) + it("should favor an operation produces value over a path-item value", function () { + const state = fromJS({ + json: { + paths: { + "/": { + produces: [ + "path-item/one", + "path-item/two", + ], + "get": { + description: "my operation", + produces: [ + "operation/one", + "operation/two", + ] + } + } + } + } + }) + + const result = producesOptionsFor(state, ["/", "get"]) + + expect(result.toJS()).toEqual([ + "operation/one", + "operation/two", + ]) + }) + it("should favor a path-item produces value over a global value", function () { + const state = fromJS({ + json: { + produces: [ + "global/one", + "global/two", + ], + paths: { + "/": { + produces: [ + "path-item/one", + "path-item/two", + ], + "get": { + description: "my operation" + } + } + } + } + }) + + const result = producesOptionsFor(state, ["/", "get"]) + + expect(result.toJS()).toEqual([ + "path-item/one", + "path-item/two", + ]) + }) +}) +describe("consumesOptionsFor", function() { + it("should return an operation consumes value", function () { + const state = fromJS({ + json: { + paths: { + "/": { + "get": { + description: "my operation", + consumes: [ + "operation/one", + "operation/two", + ] + } + } + } + } + }) + + const result = consumesOptionsFor(state, ["/", "get"]) + + expect(result.toJS()).toEqual([ + "operation/one", + "operation/two", + ]) + }) + it("should return a path item consumes value", function () { + const state = fromJS({ + json: { + paths: { + "/": { + "get": { + description: "my operation", + consumes: [ + "path-item/one", + "path-item/two", + ] + } + } + } + } + }) + + const result = consumesOptionsFor(state, ["/", "get"]) + + expect(result.toJS()).toEqual([ + "path-item/one", + "path-item/two", + ]) + }) + it("should return a global consumes value", function () { + const state = fromJS({ + json: { + consumes: [ + "global/one", + "global/two", + ], + paths: { + "/": { + "get": { + description: "my operation" + } + } + } + } + }) + + const result = consumesOptionsFor(state, ["/", "get"]) + + expect(result.toJS()).toEqual([ + "global/one", + "global/two", + ]) + }) + it("should favor an operation consumes value over a path-item value", function () { + const state = fromJS({ + json: { + paths: { + "/": { + consumes: [ + "path-item/one", + "path-item/two", + ], + "get": { + description: "my operation", + consumes: [ + "operation/one", + "operation/two", + ] + } + } + } + } + }) + + const result = consumesOptionsFor(state, ["/", "get"]) + + expect(result.toJS()).toEqual([ + "operation/one", + "operation/two", + ]) + }) + it("should favor a path-item consumes value over a global value", function () { + const state = fromJS({ + json: { + consumes: [ + "global/one", + "global/two", + ], + paths: { + "/": { + consumes: [ + "path-item/one", + "path-item/two", + ], + "get": { + description: "my operation" + } + } + } + } + }) + + const result = consumesOptionsFor(state, ["/", "get"]) + + expect(result.toJS()).toEqual([ + "path-item/one", + "path-item/two", + ]) + }) +}) +describe("taggedOperations", function () { + it("should return a List of ad-hoc tagged operations", function () { + const system = { + getConfigs: () => ({}) + } + const state = fromJS({ + json: { + // tags: [ + // "myTag" + // ], + paths: { + "/": { + "get": { + produces: [], + tags: ["myTag"], + description: "my operation", + consumes: [ + "operation/one", + "operation/two", + ] + } + } + } + } + }) + + const result = taggedOperations(state)(system) + + const op = state.getIn(["json", "paths", "/", "get"]).toJS() + + expect(result.toJS()).toEqual({ + myTag: { + tagDetails: undefined, + operations: [{ + id: "get-/", + method: "get", + path: "/", + operation: op + }] + } + }) + }) + it("should return a List of defined tagged operations", function () { + const system = { + getConfigs: () => ({}) + } + const state = fromJS({ + json: { + tags: [ + { + name: "myTag" + } + ], + paths: { + "/": { + "get": { + produces: [], + tags: ["myTag"], + description: "my operation", + consumes: [ + "operation/one", + "operation/two", + ] + } + } + } + } + }) + + const result = taggedOperations(state)(system) + + const op = state.getIn(["json", "paths", "/", "get"]).toJS() + + expect(result.toJS()).toEqual({ + myTag: { + tagDetails: { + name: "myTag" + }, + operations: [{ + id: "get-/", + method: "get", + path: "/", + operation: op + }] + } + }) + }) + it("should gracefully handle a malformed global tags array", function () { + const system = { + getConfigs: () => ({}) + } + const state = fromJS({ + json: { + tags: [null], + paths: { + "/": { + "get": { + produces: [], + tags: ["myTag"], + description: "my operation", + consumes: [ + "operation/one", + "operation/two", + ] + } + } + } + } + }) + + const result = taggedOperations(state)(system) + + const op = state.getIn(["json", "paths", "/", "get"]).toJS() + + expect(result.toJS()).toEqual({ + myTag: { + tagDetails: undefined, + operations: [{ + id: "get-/", + method: "get", + path: "/", + operation: op + }] + } + }) + }) + it("should gracefully handle a non-array global tags entry", function () { + const system = { + getConfigs: () => ({}) + } + const state = fromJS({ + json: { + tags: "asdf", + paths: { + "/": { + "get": { + produces: [], + tags: ["myTag"], + description: "my operation", + consumes: [ + "operation/one", + "operation/two", + ] + } + } + } + } + }) + + const result = taggedOperations(state)(system) + + const op = state.getIn(["json", "paths", "/", "get"]).toJS() + + expect(result.toJS()).toEqual({ + myTag: { + tagDetails: undefined, + operations: [{ + id: "get-/", + method: "get", + path: "/", + operation: op + }] + } + }) + }) +}) +describe("isMediaTypeSchemaPropertiesEqual", () => { + const stateSingleMediaType = fromJS({ + resolvedSubtrees: { + paths: { + "/test": { + post: { + requestBody: { + content: { + "application/json": { + schema: { + properties: { + "test": "some" + } + } + } + } + } + } + } + } + } + }) + const pathMethod = ["/test", "post"] + + describe("Only one media type defined", () => { + const state = stateSingleMediaType + + it("should return false if currentMediaType is null", () => { + const currentMediaType = null + const targetMediaType = "application/json" + + const result = isMediaTypeSchemaPropertiesEqual( + state, + pathMethod, + currentMediaType, + targetMediaType + ) + + expect(result).toEqual(false) + }) + it("should return false if currentMediaType is undefined", () => { + const currentMediaType = undefined + const targetMediaType = "application/json" + + const result = isMediaTypeSchemaPropertiesEqual( + state, + pathMethod, + currentMediaType, + targetMediaType + ) + + expect(result).toEqual(false) + }) + it("should return false if targetMediaType is null", () => { + const currentMediaType = "application/json" + const targetMediaType = null + + const result = isMediaTypeSchemaPropertiesEqual( + state, + pathMethod, + currentMediaType, + targetMediaType + ) + + expect(result).toEqual(false) + }) + it("should return false if targetMediaType is undefined", () => { + const currentMediaType = "application/json" + const targetMediaType = undefined + + const result = isMediaTypeSchemaPropertiesEqual( + state, + pathMethod, + currentMediaType, + targetMediaType + ) + + expect(result).toEqual(false) + }) + it("should return true when currentMediaType and targetMediaType are the same", () => { + const currentMediaType = "application/json" + const targetMediaType = "application/json" + + const result = isMediaTypeSchemaPropertiesEqual( + state, + pathMethod, + currentMediaType, + targetMediaType + ) + + expect(result).toEqual(true) + }) + + it("should return false if currentMediaType is not targetMediaType, but only one media type defined", () => { + const currentMediaType = "application/json" + const targetMediaType = "application/xml" + + const result = isMediaTypeSchemaPropertiesEqual( + state, + pathMethod, + currentMediaType, + targetMediaType + ) + + expect(result).toEqual(false) + }) + }) + describe("Multiple media types defined", () => { + const keyPath = ["resolvedSubtrees", "paths", ...pathMethod, "requestBody", "content"] + const state = stateSingleMediaType + .setIn( + [...keyPath, "application/xml"], + stateSingleMediaType.getIn([...keyPath, "application/json"]) + ) + .setIn( + [...keyPath, "application/other"], + stateSingleMediaType + .getIn([...keyPath, "application/json"]) + .setIn(["schema", "properties"], "someOther") + ) + + it("should return true if same media type", () => { + const currentMediaType = "application/json" + const targetMediaType = "application/json" + + const result = isMediaTypeSchemaPropertiesEqual( + state, + pathMethod, + currentMediaType, + targetMediaType + ) + + expect(result).toEqual(true) + }) + + it("should return true if target has same properties", () => { + const currentMediaType = "application/json" + const targetMediaType = "application/xml" + + const result = isMediaTypeSchemaPropertiesEqual( + state, + pathMethod, + currentMediaType, + targetMediaType + ) + + expect(result).toEqual(true) + }) + + it("should return false if target has other properties", () => { + const currentMediaType = "application/json" + const targetMediaType = "application/other" + + const result = isMediaTypeSchemaPropertiesEqual( + state, + pathMethod, + currentMediaType, + targetMediaType + ) + + expect(result).toEqual(false) + }) + }) +}) diff --git a/frontend/web/api-doc/test/unit/core/plugins/swagger-js/withCredentials.js b/frontend/web/api-doc/test/unit/core/plugins/swagger-js/withCredentials.js new file mode 100644 index 0000000..f0be4ef --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/plugins/swagger-js/withCredentials.js @@ -0,0 +1,93 @@ +import { loaded } from "corePlugins/swagger-js/configs-wrap-actions" + +describe("swagger-js plugin - withCredentials", () => { + it("should have no effect by default", () => { + const system = { + fn: { + fetch: jest.fn().mockImplementation(() => Promise.resolve()) + }, + getConfigs: () => ({}) + } + const oriExecute = jest.fn() + + const loadedFn = loaded(oriExecute, system) + loadedFn() + + expect(oriExecute.mock.calls.length).toBe(1) + expect(system.fn.fetch.withCredentials).toBe(undefined) + }) + + it("should allow setting flag to true via config", () => { + const system = { + fn: { + fetch: jest.fn().mockImplementation(() => Promise.resolve()) + }, + getConfigs: () => ({ + withCredentials: true + }) + } + const oriExecute = jest.fn() + + const loadedFn = loaded(oriExecute, system) + loadedFn() + + expect(oriExecute.mock.calls.length).toBe(1) + expect(system.fn.fetch.withCredentials).toBe(true) + }) + + it("should allow setting flag to false via config", () => { + const system = { + fn: { + fetch: jest.fn().mockImplementation(() => Promise.resolve()) + }, + getConfigs: () => ({ + withCredentials: false + }) + } + const oriExecute = jest.fn() + + const loadedFn = loaded(oriExecute, system) + loadedFn() + + expect(oriExecute.mock.calls.length).toBe(1) + expect(system.fn.fetch.withCredentials).toBe(false) + }) + + it("should allow setting flag to true via config as string", () => { + // for query string config + const system = { + fn: { + fetch: jest.fn().mockImplementation(() => Promise.resolve()) + }, + getConfigs: () => ({ + withCredentials: "true" + }) + } + const oriExecute = jest.fn() + + const loadedFn = loaded(oriExecute, system) + loadedFn() + + expect(oriExecute.mock.calls.length).toBe(1) + expect(system.fn.fetch.withCredentials).toBe(true) + }) + + it("should allow setting flag to false via config as string", () => { + // for query string config + const system = { + fn: { + fetch: jest.fn().mockImplementation(() => Promise.resolve()) + }, + getConfigs: () => ({ + withCredentials: "false" + }) + } + const oriExecute = jest.fn() + + const loadedFn = loaded(oriExecute, system) + loadedFn() + + expect(oriExecute.mock.calls.length).toBe(1) + expect(system.fn.fetch.withCredentials).toBe(false) + }) +}) diff --git a/frontend/web/api-doc/test/unit/core/system/system.jsx b/frontend/web/api-doc/test/unit/core/system/system.jsx new file mode 100644 index 0000000..507983b --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/system/system.jsx @@ -0,0 +1,915 @@ +import React, { PureComponent } from "react" +import { fromJS } from "immutable" +import { render, mount } from "enzyme" +import { Provider } from "react-redux" + +import System from "core/system" +import ViewPlugin from "core/plugins/view/index.js" +import filterPlugin from "core/plugins/filter/index.js" + +describe("bound system", function(){ + + describe("wrapActions", function(){ + + it("should replace an action", function(){ + // Given + const system = new System({ + plugins: { + statePlugins: { + josh: { + actions: { + simple: () => { + return { type: "simple" } + } + }, + wrapActions: { + simple: () => () => { + return { type: "newSimple" } + } + } + } + } + } + }) + + // When + let action = system.getSystem().joshActions.simple(1) + expect(action).toEqual({ + type: "newSimple" + }) + + }) + + it("should expose the original action, and the system as args", function(){ + // Given + const simple = () => ({type: "simple" }) + const system = new System({ + plugins: { + statePlugins: { + josh: { + actions: { simple }, + wrapActions: { + simple: (oriAction, system) => (actionArg) => { + return { + type: "newSimple", + oriActionResult: oriAction(), + system: system.getSystem(), + actionArg + } + } + } + } + } + } + }) + + // When + let action = system.getSystem().joshActions.simple(1) + expect(action).toEqual({ + type: "newSimple", + oriActionResult: { type: "simple" }, + system: system.getSystem(), + actionArg: 1 + }) + + }) + + it("should support multiple wraps of the same action", function(){ + const system = new System({ + plugins: [ + { + statePlugins: { + kyle: { + actions: { + simple: () => { + return { + type: "simple", + } + } + } + } + } + }, + { + statePlugins: { + kyle: { + wrapActions: { + simple: (ori) => () => { + return { + ...ori(), + firstWrap: true + } + } + } + } + } + }, + { + statePlugins: { + kyle: { + wrapActions: { + simple: (ori) => () => { + return { + ...ori(), + secondWrap: true + } + } + } + } + } + } + ] + }) + + // When + let action = system.getSystem().kyleActions.simple(1) + expect(action).toEqual({ + type: "simple", + firstWrap: true, + secondWrap: true, + }) + + }) + + it("should execute wrapActions in the order they appear ( via plugins )", function(){ + const system = new System({ + plugins: [ + { + statePlugins: { + kyle: { + actions: { + simple: () => { + return { + type: "one", + } + } + } + } + } + }, + { + statePlugins: { + kyle: { + wrapActions: { + simple: (ori) => () => { + const obj = ori() + obj.type += "-two" + return obj + } + } + } + } + }, + { + statePlugins: { + kyle: { + wrapActions: { + simple: (ori) => () => { + const obj = ori() + obj.type += "-three" + return obj + } + } + } + } + } + ] + }) + + // When + let action = system.getSystem().kyleActions.simple(1) + expect(action.type).toEqual("one-two-three") + + }) + + it("should have a the latest system", function(){ + // Given + const system = new System({ + plugins: [ + { + statePlugins: { + kyle: { + actions: { + simple: () => { + return { + type: "one", + } + } + }, + wrapActions: { + simple: (ori, {joshActions}) => () => { + return joshActions.hello() + } + } + } + } + }, + ] + }) + + // When + const kyleActions = system.getSystem().kyleActions + + system.register({ + statePlugins: { + josh: { + actions: { + hello(){ return {type: "hello" } } + } + } + } + }) + + const action = kyleActions.simple() + expect(action).toEqual({ type: "hello"}) + }) + + it.skip("should be able to create async actions", function(){ + const system = new System({ + plugins: [ + { + statePlugins: { + kyle: { + actions: { + simple: () => { + return { + type: "one", + } + } + } + } + } + }, + { + statePlugins: { + kyle: { + wrapActions: { + // eslint-disable-next-line no-unused-vars + simple: (ori) => (arg) => (sys) => { + return { type: "called" } + } + } + } + } + }, + ] + }) + + // When + let action = system.getSystem().kyleActions.simple(1) + expect(action.type).toEqual("called") + + }) + + + }) + + describe("fn", function() { + + it("should return helper functions", function () { + // Given + const system = new System({ + plugins: [ + filterPlugin + ] + }) + + // When + const fn = system.getSystem().fn.opsFilter + expect(typeof fn).toEqual("function") + }) + }) + + describe("selectors", function(){ + + it("should have the first arg be the nested state, and all other args to follow", function(){ + + // Given + const system = new System({ + state: { + josh: { + one: 1 + } + }, + plugins: { + statePlugins: { + josh: { + selectors: { + simple: (state, arg1) => { + return { state, arg1 } + } + } + } + } + } + + }) + + // When + let res = system.getSystem().joshSelectors.simple(1) + expect(res).toEqual({ + state: fromJS({ + one: 1 + }), + arg1: 1 + }) + + }) + + describe("when selector returns a function", function(){ + + it("should pass the system to that function", function(){ + + // Given + const system = new System({ + plugins: { + statePlugins: { + josh: { + selectors: { + advanced: () => (mySystem) => { + // Then + expect(mySystem).toEqual(system.getSystem()) + return "hi" + } + } + } + } + } + + }) + + // When + let res = system.getSystem().joshSelectors.advanced(1) + expect(res).toEqual("hi") + + }) + + }) + + describe("wrapSelectors", () => { + it("should wrap a selector and provide a reference to the original", function(){ + + // Given + const system = new System({ + plugins: [ + { + statePlugins: { + doge: { + selectors: { + wow: () => (system) => { + return "original" + } + } + } + } + }, + { + statePlugins: { + doge: { + wrapSelectors: { + wow: (ori) => (system) => { + // Then + return ori() + " wrapper" + } + } + } + } + } + ] + }) + + // When + let res = system.getSystem().dogeSelectors.wow(1) + expect(res).toEqual("original wrapper") + + }) + + it("should provide a live reference to the system to a wrapper", function(done){ + + // Given + const mySystem = new System({ + plugins: [ + { + statePlugins: { + doge: { + selectors: { + wow: () => (system) => { + return "original" + } + } + } + } + }, + { + statePlugins: { + doge: { + wrapSelectors: { + wow: (ori, system) => () => { + // Then + expect(mySystem.getSystem()).toEqual(system.getSystem()) + done() + return ori() + " wrapper" + } + } + } + } + } + ] + }) + + mySystem.getSystem().dogeSelectors.wow(1) + }) + + it("should provide the state as the first argument to the inner function", function(done){ + + // Given + const mySystem = new System({ + state: { + doge: { + abc: "123" + } + }, + plugins: [ + { + statePlugins: { + doge: { + selectors: { + wow: () => (system) => { + return "original" + } + } + } + } + }, + { + statePlugins: { + doge: { + wrapSelectors: { + wow: (ori, system) => (dogeState) => { + // Then + expect(dogeState.toJS().abc).toEqual("123") + done() + return ori() + " wrapper" + } + } + } + } + } + ] + }) + + mySystem.getSystem().dogeSelectors.wow(1) + }) + }) + + }) + + describe("getComponent", function() { + it("returns a component from the system", function() { + const system = new System({ + plugins: [ + ViewPlugin, + { + components: { + test: ({ name }) =>
{name} component
+ } + } + ] + }) + + // When + let Component = system.getSystem().getComponent("test") + const renderedComponent = render() + expect(renderedComponent.text()).toEqual("Test component") + }) + + it("allows container components to provide their own `mapStateToProps` function", function() { + // Given + class ContainerComponent extends PureComponent { + mapStateToProps(nextState, props) { + return { + "fromMapState": "This came from mapStateToProps" + } + } + + static defaultProps = { + "fromMapState" : "" + } + + render() { + const { exampleSelectors, fromMapState, fromOwnProps } = this.props + return ( +
{ fromMapState } {exampleSelectors.foo()} {fromOwnProps}
+ ) + } + } + const system = new System({ + plugins: [ + ViewPlugin, + { + components: { + ContainerComponent + } + }, + { + statePlugins: { + example: { + selectors: { + foo() { return "and this came from the system" } + } + } + } + } + ] + }) + + // When + let Component = system.getSystem().getComponent("ContainerComponent", true) + const renderedComponent = render( + + + + ) + + // Then + expect(renderedComponent.text()).toEqual("This came from mapStateToProps and this came from the system and this came from my own props") + }) + + it("gives the system and own props as props to a container's `mapStateToProps` function", function() { + // Given + class ContainerComponent extends PureComponent { + mapStateToProps(nextState, props) { + const { exampleSelectors, fromMapState, fromOwnProps } = props + return { + "fromMapState": `This came from mapStateToProps ${exampleSelectors.foo()} ${fromOwnProps}` + } + } + + static defaultProps = { + "fromMapState" : "" + } + + render() { + const { fromMapState } = this.props + return ( +
{ fromMapState }
+ ) + } + } + const system = new System({ + plugins: [ + ViewPlugin, + { + components: { + ContainerComponent + } + }, + { + statePlugins: { + example: { + selectors: { + foo() { return "and this came from the system" } + } + } + } + } + ] + }) + + // When + let Component = system.getSystem().getComponent("ContainerComponent", true) + const renderedComponent = render( + + + + ) + + // Then + expect(renderedComponent.text()).toEqual("This came from mapStateToProps and this came from the system and this came from my own props") + }) + }) + + describe("afterLoad", function() { + it("should call a plugin's `afterLoad` method after the plugin is loaded", function() { + // Given + const system = new System({ + plugins: [ + { + afterLoad(system) { + this.rootInjects.wow = system.dogeSelectors.wow + }, + statePlugins: { + doge: { + selectors: { + wow: () => (system) => { + return "so selective" + } + } + } + } + } + ] + }) + + // When + let res = system.getSystem().wow() + expect(res).toEqual("so selective") + }) + it("should call a preset plugin's `afterLoad` method after the plugin is loaded", function() { + // Given + const MyPlugin = { + afterLoad(system) { + this.rootInjects.wow = system.dogeSelectors.wow + }, + statePlugins: { + doge: { + selectors: { + wow: () => (system) => { + return "so selective" + } + } + } + } + } + + const system = new System({ + plugins: [ + [MyPlugin] + ] + }) + + // When + let res = system.getSystem().wow() + expect(res).toEqual("so selective") + }) + it("should call a function preset plugin's `afterLoad` method after the plugin is loaded", function() { + // Given + const MyPlugin = { + afterLoad(system) { + this.rootInjects.wow = system.dogeSelectors.wow + }, + statePlugins: { + doge: { + selectors: { + wow: () => (system) => { + return "so selective" + } + } + } + } + } + + const system = new System({ + plugins: [ + () => { + return [MyPlugin] + } + ] + }) + + // When + let res = system.getSystem().wow() + expect(res).toEqual("so selective") + }) + it("should call a registered plugin's `afterLoad` method after the plugin is loaded", function() { + // Given + const MyPlugin = { + afterLoad(system) { + this.rootInjects.wow = system.dogeSelectors.wow + }, + statePlugins: { + doge: { + selectors: { + wow: () => (system) => { + return "so selective" + } + } + } + } + } + + const system = new System({ + plugins: [] + }) + + system.register([MyPlugin]) + + // When + let res = system.getSystem().wow() + expect(res).toEqual("so selective") + }) + }) + + describe("rootInjects", function() { + it("should attach a rootInject function as an instance method", function() { + // This is the same thing as the `afterLoad` tests, but is here for posterity + + // Given + const system = new System({ + plugins: [ + { + afterLoad(system) { + this.rootInjects.wow = system.dogeSelectors.wow + }, + statePlugins: { + doge: { + selectors: { + wow: () => (system) => { + return "so selective" + } + } + } + } + } + ] + }) + + // When + let res = system.getSystem().wow() + expect(res).toEqual("so selective") + }) + }) + + describe("error catching", function() { + it("should encapsulate thrown errors in an afterLoad method", function() { + // Given + const ThrowyPlugin = { + afterLoad(system) { + throw new Error("afterLoad BREAKS STUFF!") + }, + statePlugins: { + doge: { + selectors: { + wow: () => (system) => { + return "so selective" + } + } + } + } + } + + const system = new System({ + plugins: [] + }) + + + // When + expect(() => { + system.register([ThrowyPlugin]) + // let resSystem = system.getSystem() + }).not.toThrow() + }) + + it("should encapsulate thrown errors in an action creator", function(){ + + // Given + const system = new System({ + plugins: { + statePlugins: { + throw: { + actions: { + func() { + throw new Error("this action creator THROWS!") + } + } + } + } + } + + }) + + expect(() => { + // TODO: fix existing action error catcher that creates THROWN ERR actions + system.getSystem().throwActions.func() + }).not.toThrow() + }) + + it("should encapsulate thrown errors in a reducer", function(){ + + // Given + const system = new System({ + plugins: { + statePlugins: { + throw: { + actions: { + func: () => { + return { + type: "THROW_FUNC", + payload: "BOOM!" + } + } + }, + reducers: { + "THROW_FUNC": (state, action) => { + throw new Error("this reducer EXPLODES!") + } + } + } + } + } + + }) + + expect(() => { + system.getSystem().throwActions.func() + }).not.toThrow() + }) + + it("should encapsulate thrown errors in a selector", function(){ + + // Given + const system = new System({ + plugins: { + statePlugins: { + throw: { + selectors: { + func: (state, arg1) => { + throw new Error("this selector THROWS!") + } + } + } + } + } + + }) + + expect(system.getSystem().throwSelectors.func).not.toThrow() + }) + + it("should encapsulate thrown errors in a complex selector", function(){ + + // Given + const system = new System({ + plugins: { + statePlugins: { + throw: { + selectors: { + func: (state, arg1) => system => { + throw new Error("this selector THROWS!") + } + } + } + } + } + + }) + + expect(system.getSystem().throwSelectors.func).not.toThrow() + }) + + it("should encapsulate thrown errors in a wrapAction", function(){ + + // Given + const system = new System({ + plugins: { + statePlugins: { + throw: { + actions: { + func: () => { + return { + type: "THROW_FUNC", + payload: "this original action does NOT throw" + } + } + }, + wrapActions: { + func: (ori) => (...args) => { + throw new Error("this wrapAction UNRAVELS EVERYTHING!") + } + } + } + } + } + + }) + + expect(system.getSystem().throwActions.func).not.toThrow() + }) + + it("should encapsulate thrown errors in a wrapSelector", function(){ + + // Given + const system = new System({ + plugins: { + statePlugins: { + throw: { + selectors: { + func: (state, arg1) => { + return 123 + } + }, + wrapSelectors: { + func: (ori) => (...props) => { + return ori(...props) + } + } + } + } + } + + }) + + expect(system.getSystem().throwSelectors.func).not.toThrow() + }) + }) +}) diff --git a/frontend/web/api-doc/test/unit/core/system/wrapComponent.jsx b/frontend/web/api-doc/test/unit/core/system/wrapComponent.jsx new file mode 100644 index 0000000..3972527 --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/system/wrapComponent.jsx @@ -0,0 +1,307 @@ +import React from "react" + +import { render } from "enzyme" +import System from "core/system" + +describe("wrapComponents", () => { + describe("should wrap a component and provide a reference to the original", () => { + it("with stateless components", function () { + // Given + const system = new System({ + plugins: [ + { + components: { + wow: ({ name }) =>
{name} component
+ } + }, + { + wrapComponents: { + wow: (OriginalComponent) => (props) => { + return + + + + } + } + } + ] + }) + + // When + let Component = system.getSystem().getComponents("wow") + const wrapper = render() + + expect(wrapper.get(0).name).toEqual("container") + + const children = wrapper.children() + expect(children.length).toEqual(2) + expect(children.eq(0).text()).toEqual("Normal component") + expect(children.eq(1).text()).toEqual("Wrapped component") + }) + + it("with React classes", function () { + class MyComponent extends React.Component { + render() { + return
{this.props.name} component
+ } + } + + // Given + const system = new System({ + plugins: [ + { + components: { + wow: MyComponent + } + }, + { + wrapComponents: { + wow: (OriginalComponent) => { + return class WrapperComponent extends React.Component { + render() { + return + + + + } + } + } + } + } + ] + }) + + // When + let Component = system.getSystem().getComponents("wow") + const wrapper = render() + + expect(wrapper.get(0).name).toEqual("container") + + const children = wrapper.children() + expect(children.length).toEqual(2) + expect(children.eq(0).text()).toEqual("Normal component") + expect(children.eq(1).text()).toEqual("Wrapped component") + }) + }) + + it("should provide a reference to the system to the wrapper", function () { + + // Given + + const mySystem = new System({ + plugins: [ + { + // Make a selector + statePlugins: { + doge: { + selectors: { + wow: () => () => { + return "WOW much data" + } + } + } + } + }, + { + // Create a component + components: { + wow: () =>
Original component
+ } + }, + { + // Wrap the component and use the system + wrapComponents: { + wow: (OriginalComponent, system) => (props) => { + return + +
{system.dogeSelectors.wow()}
+
+ } + } + } + ] + }) + + // Then + let Component = mySystem.getSystem().getComponents("wow") + const wrapper = render() + + expect(wrapper.get(0).name).toEqual("container") + + const children = wrapper.children() + expect(children.length).toEqual(2) + expect(children.eq(0).text()).toEqual("Original component") + expect(children.eq(1).text()).toEqual("WOW much data") + }) + + it("should wrap correctly when registering more plugins", function () { + + // Given + + const mySystem = new System({ + plugins: [ + () => { + return { + statePlugins: { + doge: { + selectors: { + wow: () => () => { + return "WOW much data" + } + } + } + }, + components: { + wow: () =>
Original component
+ } + } + } + ] + }) + + mySystem.register([ + function () { + return { + // Wrap the component and use the system + wrapComponents: { + wow: (OriginalComponent, system) => (props) => { + return + +
{system.dogeSelectors.wow()}
+
+ } + } + } + } + ]) + + // Then + let Component = mySystem.getSystem().getComponents("wow") + const wrapper = render() + + expect(wrapper.get(0).name).toEqual("container") + + const children = wrapper.children() + expect(children.length).toEqual(2) + expect(children.eq(0).text()).toEqual("Original component") + expect(children.eq(1).text()).toEqual("WOW much data") + }) + + it("should wrap correctly when registering multiple plugins targeting the same component", function () { + + // Given + + const mySystem = new System({ + pluginsOptions: { + pluginLoadType: "chain" + }, + plugins: [ + () => { + return { + components: { + wow: () =>
Original component
+ } + } + } + ] + }) + + mySystem.register([ + () => { + return { + wrapComponents: { + wow: (OriginalComponent, system) => (props) => { + return + +
Injected after
+
+ } + } + } + }, + () => { + return { + wrapComponents: { + wow: (OriginalComponent, system) => (props) => { + return +
Injected before
+ +
+ } + } + } + } + ]) + + // Then + let Component = mySystem.getSystem().getComponents("wow") + const wrapper = render() + + expect(wrapper.get(0).name).toEqual("container2") + + const children2 = wrapper.children() + expect(children2.length).toEqual(2) + expect(children2[0].name).toEqual("div") + expect(children2.eq(0).text()).toEqual("Injected before") + expect(children2[1].name).toEqual("container1") + + const children1 = children2.children() + expect(children1.length).toEqual(2) + expect(children1.eq(0).text()).toEqual("Original component") + expect(children1[0].name).toEqual("div") + expect(children1.eq(1).text()).toEqual("Injected after") + }) + + it("should wrap correctly when building a system twice", function () { + + // Given + + const pluginOne = { + statePlugins: { + doge: { + selectors: { + wow: () => () => { + return "WOW much data" + } + } + } + }, + components: { + wow: () =>
Original component
+ } + } + + const pluginTwo = { + // Wrap the component and use the system + wrapComponents: { + wow: (OriginalComponent, system) => (props) => { + return + +
{system.dogeSelectors.wow()}
+
+ } + } + } + + const bothPlugins = () => [pluginOne, pluginTwo] + + new System({ + plugins: bothPlugins + }) + + const secondSystem = new System({ + plugins: bothPlugins + }) + + // Then + let Component = secondSystem.getSystem().getComponents("wow") + const wrapper = render() + + expect(wrapper.get(0).name).toEqual("container") + + const children = wrapper.children() + expect(children.length).toEqual(2) + expect(children.eq(0).text()).toEqual("Original component") + expect(children.eq(1).text()).toEqual("WOW much data") + }) +}) diff --git a/frontend/web/api-doc/test/unit/core/system/wrapSelectors.js b/frontend/web/api-doc/test/unit/core/system/wrapSelectors.js new file mode 100644 index 0000000..076dc0d --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/system/wrapSelectors.js @@ -0,0 +1,45 @@ +import System from "core/system" + +describe("wrapSelectors", () => { + it("should wrap correctly when registering multiple plugins targeting the same selector", function() { + const probeBase = { + statePlugins: { + probe: { + selectors: { + selectProbe: () => { + return "base" + } + } + } + } + } + const probeWrap1 = { + statePlugins: { + probe: { + wrapSelectors: { + selectProbe: (oriSelector) => (state, ...args) => { + const selectedValue = oriSelector(state, ...args) + return `${selectedValue}wrap1` + } + } + } + } + } + const probeWrap2 = { + statePlugins: { + probe: { + wrapSelectors: { + selectProbe: (oriSelector) => (state, ...args) => { + const selectedValue = oriSelector(state, ...args) + return `${selectedValue}wrap2` + } + } + } + } + } + + const system = new System({ plugins: [probeBase, probeWrap1, probeWrap2] }) + + expect(system.getSystem().probeSelectors.selectProbe()).toEqual("basewrap1wrap2") + }) +}) diff --git a/frontend/web/api-doc/test/unit/core/utils.js b/frontend/web/api-doc/test/unit/core/utils.js new file mode 100644 index 0000000..42ab9b5 --- /dev/null +++ b/frontend/web/api-doc/test/unit/core/utils.js @@ -0,0 +1,1913 @@ +import { Map, fromJS } from "immutable" +import { + mapToList, + parseSearch, + serializeSearch, + validatePattern, + validateMinLength, + validateMaxLength, + validateDateTime, + validateGuid, + validateNumber, + validateInteger, + validateParam, + validateFile, + validateMaximum, + validateMinimum, + fromJSOrdered, + getAcceptControllingResponse, + createDeepLinkPath, + escapeDeepLinkPath, + getExtensions, + getCommonExtensions, + sanitizeUrl, + requiresValidationURL, + extractFileNameFromContentDispositionHeader, + deeplyStripKey, + getSampleSchema, + paramToIdentifier, + paramToValue, + generateCodeVerifier, + createCodeChallenge, +} from "core/utils" + +import { + isAbsoluteUrl, + buildBaseUrl, + buildUrl, + safeBuildUrl, +} from "core/utils/url" + +import win from "core/window" +import { afterAll, beforeAll, expect, jest } from "@jest/globals" + +describe("utils", () => { + + describe("mapToList", () =>{ + + it("should convert a map to a list, setting `key`", () =>{ + // With + const aMap = fromJS({ + a: { + one: 1, + }, + b: { + two: 2, + } + }) + + // When + const aList = mapToList(aMap, "someKey") + + // Then + expect(aList.toJS()).toEqual([ + { someKey: "a", one: 1 }, + { someKey: "b", two: 2 }, + ]) + }) + + it("should flatten an arbitrarily deep map", () =>{ + // With + const aMap = fromJS({ + a: { + one: { + alpha: true + } + }, + b: { + two: { + bravo: true + }, + three: { + charlie: true + } + } + }) + + // When + const aList = mapToList(aMap, ["levelA", "levelB"]) + + // Then + expect(aList.toJS()).toEqual([ + { levelA: "a", levelB: "one", alpha: true }, + { levelA: "b", levelB: "two", bravo: true }, + { levelA: "b", levelB: "three", charlie: true }, + ]) + + }) + + it("should handle an empty map", () =>{ + // With + const aMap = fromJS({}) + + // When + const aList = mapToList(aMap, ["levelA", "levelB"]) + + // Then + expect(aList.toJS()).toEqual([]) + }) + + }) + + describe("extractFileNameFromContentDispositionHeader", () =>{ + it("should extract quoted filename", () =>{ + let cdHeader = "attachment; filename=\"file name.jpg\"" + let expectedResult = "file name.jpg" + expect(extractFileNameFromContentDispositionHeader(cdHeader)).toEqual(expectedResult) + }) + + it("should extract filename", () =>{ + let cdHeader = "attachment; filename=filename.jpg" + let expectedResult = "filename.jpg" + expect(extractFileNameFromContentDispositionHeader(cdHeader)).toEqual(expectedResult) + }) + + it("should extract quoted filename in utf-8", () =>{ + let cdHeader = "attachment; filename*=UTF-8''\"%D1%84%D0%B0%D0%B9%D0%BB.txt\"" + let expectedResult = "файл.txt" + expect(extractFileNameFromContentDispositionHeader(cdHeader)).toEqual(expectedResult) + }) + + it("should extract filename in utf-8", () =>{ + let cdHeader = "attachment; filename*=utf-8'ru'%D1%84%D0%B0%D0%B9%D0%BB.txt" + let expectedResult = "файл.txt" + expect(extractFileNameFromContentDispositionHeader(cdHeader)).toEqual(expectedResult) + }) + + it("should not extract filename and return null", () =>{ + let cdHeader = "attachment; no file name provided" + let expectedResult = null + expect(extractFileNameFromContentDispositionHeader(cdHeader)).toEqual(expectedResult) + }) + }) + + describe("validateMaximum", () => { + it("doesn't return for valid input", () => { + expect(validateMaximum(9, 10)).toBeFalsy() + expect(validateMaximum(19, 20)).toBeFalsy() + }) + + it("returns a message for invalid input", () => { + expect(validateMaximum(1, 0)).toEqual("Value must be less than 0") + expect(validateMaximum(10, 9)).toEqual("Value must be less than 9") + expect(validateMaximum(20, 19)).toEqual("Value must be less than 19") + }) + }) + + describe("validateMinimum", () => { + it("doesn't return for valid input", () => { + expect(validateMinimum(2, 1)).toBeFalsy() + expect(validateMinimum(20, 10)).toBeFalsy() + }) + + it("returns a message for invalid input", () => { + expect(validateMinimum(-1, 0)).toEqual("Value must be greater than 0") + expect(validateMinimum(1, 2)).toEqual("Value must be greater than 2") + expect(validateMinimum(10, 20)).toEqual("Value must be greater than 20") + }) + }) + + describe("validateNumber", () => { + let errorMessage = "Value must be a number" + + it("doesn't return for whole numbers", () => { + expect(validateNumber(0)).toBeFalsy() + expect(validateNumber(1)).toBeFalsy() + expect(validateNumber(20)).toBeFalsy() + expect(validateNumber(5000000)).toBeFalsy() + expect(validateNumber("1")).toBeFalsy() + expect(validateNumber("2")).toBeFalsy() + expect(validateNumber(-1)).toBeFalsy() + expect(validateNumber(-20)).toBeFalsy() + expect(validateNumber(-5000000)).toBeFalsy() + }) + + it("doesn't return for negative numbers", () => { + expect(validateNumber(-1)).toBeFalsy() + expect(validateNumber(-20)).toBeFalsy() + expect(validateNumber(-5000000)).toBeFalsy() + }) + + it("doesn't return for decimal numbers", () => { + expect(validateNumber(1.1)).toBeFalsy() + expect(validateNumber(2.5)).toBeFalsy() + expect(validateNumber(-30.99)).toBeFalsy() + }) + + it("returns a message for strings", () => { + expect(validateNumber("")).toEqual(errorMessage) + expect(validateNumber(" ")).toEqual(errorMessage) + expect(validateNumber("test")).toEqual(errorMessage) + }) + + it("returns a message for invalid input", () => { + expect(validateNumber(undefined)).toEqual(errorMessage) + expect(validateNumber(null)).toEqual(errorMessage) + expect(validateNumber({})).toEqual(errorMessage) + expect(validateNumber([])).toEqual(errorMessage) + expect(validateNumber(true)).toEqual(errorMessage) + expect(validateNumber(false)).toEqual(errorMessage) + }) + }) + + describe("validateInteger", () => { + let errorMessage = "Value must be an integer" + + it("doesn't return for positive integers", () => { + expect(validateInteger(0)).toBeFalsy() + expect(validateInteger(1)).toBeFalsy() + expect(validateInteger(20)).toBeFalsy() + expect(validateInteger(5000000)).toBeFalsy() + expect(validateInteger("1")).toBeFalsy() + expect(validateInteger("2")).toBeFalsy() + expect(validateInteger(-1)).toBeFalsy() + expect(validateInteger(-20)).toBeFalsy() + expect(validateInteger(-5000000)).toBeFalsy() + }) + + it("doesn't return for negative integers", () => { + expect(validateInteger(-1)).toBeFalsy() + expect(validateInteger(-20)).toBeFalsy() + expect(validateInteger(-5000000)).toBeFalsy() + }) + + it("returns a message for decimal values", () => { + expect(validateInteger(1.1)).toEqual(errorMessage) + expect(validateInteger(2.5)).toEqual(errorMessage) + expect(validateInteger(-30.99)).toEqual(errorMessage) + }) + + it("returns a message for strings", () => { + expect(validateInteger("")).toEqual(errorMessage) + expect(validateInteger(" ")).toEqual(errorMessage) + expect(validateInteger("test")).toEqual(errorMessage) + }) + + it("returns a message for invalid input", () => { + expect(validateInteger(undefined)).toEqual(errorMessage) + expect(validateInteger(null)).toEqual(errorMessage) + expect(validateInteger({})).toEqual(errorMessage) + expect(validateInteger([])).toEqual(errorMessage) + expect(validateInteger(true)).toEqual(errorMessage) + expect(validateInteger(false)).toEqual(errorMessage) + }) + }) + + describe("validateFile", () => { + let errorMessage = "Value must be a file" + + it("validates against objects which are instances of 'File'", () => { + let fileObj = new win.File([], "Test File") + expect(validateFile(fileObj)).toBeFalsy() + expect(validateFile(null)).toBeFalsy() + expect(validateFile(undefined)).toBeFalsy() + expect(validateFile(1)).toEqual(errorMessage) + expect(validateFile("string")).toEqual(errorMessage) + }) + }) + + describe("validateDateTime", () => { + let errorMessage = "Value must be a DateTime" + + it("doesn't return for valid dates", () => { + expect(validateDateTime("Mon, 25 Dec 1995 13:30:00 +0430")).toBeFalsy() + }) + + it("returns a message for invalid input'", () => { + expect(validateDateTime(null)).toEqual(errorMessage) + expect(validateDateTime("string")).toEqual(errorMessage) + }) + }) + + describe("validateGuid", () => { + let errorMessage = "Value must be a Guid" + + it("doesn't return for valid guid", () => { + expect(validateGuid("8ce4811e-cec5-4a29-891a-15d1917153c1")).toBeFalsy() + expect(validateGuid("{8ce4811e-cec5-4a29-891a-15d1917153c1}")).toBeFalsy() + expect(validateGuid("8CE4811E-CEC5-4A29-891A-15D1917153C1")).toBeFalsy() + expect(validateGuid("6ffefd8e-a018-e811-bbf9-60f67727d806")).toBeFalsy() + expect(validateGuid("6FFEFD8E-A018-E811-BBF9-60F67727D806")).toBeFalsy() + expect(validateGuid("00000000-0000-0000-0000-000000000000")).toBeFalsy() + }) + + it("returns a message for invalid input'", () => { + expect(validateGuid(1)).toEqual(errorMessage) + expect(validateGuid("string")).toEqual(errorMessage) + }) + }) + + describe("validateMaxLength", () => { + it("doesn't return for valid input", () => { + expect(validateMaxLength("a", 1)).toBeFalsy() + expect(validateMaxLength("abc", 5)).toBeFalsy() + }) + + it("returns a message for invalid input'", () => { + expect(validateMaxLength("abc", 0)).toEqual("Value must be no longer than 0 characters") + expect(validateMaxLength("abc", 1)).toEqual("Value must be no longer than 1 character") + expect(validateMaxLength("abc", 2)).toEqual("Value must be no longer than 2 characters") + }) + }) + + describe("validateMinLength", () => { + it("doesn't return for valid input", () => { + expect(validateMinLength("a", 1)).toBeFalsy() + expect(validateMinLength("abc", 2)).toBeFalsy() + }) + + it("returns a message for invalid input'", () => { + expect(validateMinLength("", 1)).toEqual("Value must be at least 1 character") + expect(validateMinLength("abc", 5)).toEqual("Value must be at least 5 characters") + expect(validateMinLength("abc", 8)).toEqual("Value must be at least 8 characters") + }) + }) + + describe("validatePattern", () => { + let rxPattern = "^(red|blue)" + let errorMessage = "Value must follow pattern " + rxPattern + + it("doesn't return for a match", () => { + expect(validatePattern("red", rxPattern)).toBeFalsy() + expect(validatePattern("blue", rxPattern)).toBeFalsy() + }) + + it("returns a message for invalid pattern", () => { + expect(validatePattern("pink", rxPattern)).toEqual(errorMessage) + expect(validatePattern("123", rxPattern)).toEqual(errorMessage) + }) + + it("fails gracefully when an invalid regex value is passed", () => { + expect(() => validatePattern("aValue", "---")).not.toThrow() + expect(() => validatePattern("aValue", 1234)).not.toThrow() + expect(() => validatePattern("aValue", null)).not.toThrow() + expect(() => validatePattern("aValue", [])).not.toThrow() + }) + }) + + describe("validateParam", () => { + let param = null + let value = null + let result = null + + const assertValidateParam = (param, value, expectedError) => { + // Swagger 2.0 version + result = validateParam( fromJS(param), fromJS(value)) + expect( result ).toEqual( expectedError ) + + // OAS3 version, using `schema` sub-object + let oas3Param = { + required: param.required, + schema: { + ...param, + required: undefined + } + } + result = validateParam( fromJS(oas3Param), fromJS(value), { + isOAS3: true + }) + expect( result ).toEqual( expectedError ) + } + + const assertValidateOas3Param = (param, value, expectedError) => { + // for cases where you _only_ want to try OAS3 + result = validateParam(fromJS(param), value, { + isOAS3: true + }) + expect( result ).toEqual( expectedError ) + } + + it("should check the isOAS3 flag when validating parameters", () => { + // This should "skip" validation because there is no `schema` property + // and we are telling `validateParam` this is an OAS3 spec + param = fromJS({ + required: true + }) + value = "" + result = validateParam( param, value, { + isOAS3: true + } ) + expect( result ).toEqual( [] ) + }) + + it("validates required OAS3 objects", () => { + // valid object + param = { + required: true, + schema: { + type: "object" + } + } + value = { + abc: 123 + } + assertValidateOas3Param(param, value, []) + + // valid object-as-string + param = { + required: true, + schema: { + type: "object" + } + } + value = JSON.stringify({ + abc: 123 + }) + assertValidateOas3Param(param, value, []) + + // invalid object-as-string + param = { + required: true, + schema: { + type: "object" + } + } + value = "{{}" + assertValidateOas3Param(param, value, ["Parameter string value must be valid JSON"]) + + // missing when required + param = { + required: true, + schema: { + type: "object" + }, + } + value = undefined + assertValidateOas3Param(param, value, ["Required field is not provided"]) + }) + + it("validates optional OAS3 objects", () => { + // valid object + param = { + schema: { + type: "object" + } + } + value = { + abc: 123 + } + assertValidateOas3Param(param, value, []) + + // valid object-as-string + param = { + schema: { + type: "object" + } + } + value = JSON.stringify({ + abc: 123 + }) + assertValidateOas3Param(param, value, []) + + // invalid object-as-string + param = { + schema: { + type: "object" + } + } + value = "{{}" + assertValidateOas3Param(param, value, ["Parameter string value must be valid JSON"]) + + // missing when not required + param = { + schema: { + type: "object" + }, + } + value = undefined + assertValidateOas3Param(param, value, []) + }) + + it("validates required strings", () => { + // invalid string + param = { + required: true, + type: "string" + } + value = "" + assertValidateParam(param, value, ["Required field is not provided"]) + + // valid string + param = { + required: true, + type: "string" + } + value = "test string" + assertValidateParam(param, value, []) + + // valid string with min and max length + param = { + required: true, + type: "string", + maxLength: 50, + minLength: 1 + } + value = "test string" + assertValidateParam(param, value, []) + }) + + it("handles OAS3 `Parameter.content`", () => { + // invalid string + param = { + content: { + "text/plain": { + schema: { + required: true, + type: "string" + } + } + } + } + value = "" + assertValidateOas3Param(param, value, ["Required field is not provided"]) + + // valid string + param = { + content: { + "text/plain": { + schema: { + required: true, + type: "string" + } + } + } + } + value = "test string" + assertValidateOas3Param(param, value, []) + + + // invalid (empty) JSON string + param = { + content: { + "application/json": { + schema: { + required: true, + type: "object" + } + } + } + } + value = "" + assertValidateOas3Param(param, value, ["Required field is not provided"]) + + // invalid (malformed) JSON string + param = { + content: { + "application/json": { + schema: { + required: true, + type: "object" + } + } + } + } + value = "{{}" + assertValidateOas3Param(param, value, ["Parameter string value must be valid JSON"]) + + + // valid (empty object) JSON string + param = { + content: { + "application/json": { + schema: { + required: true, + type: "object" + } + } + } + } + value = "{}" + assertValidateOas3Param(param, value, []) + + // valid (empty object) JSON object + param = { + content: { + "application/json": { + schema: { + required: true, + type: "object" + } + } + } + } + value = {} + assertValidateOas3Param(param, value, []) + + // should skip JSON validation for non-JSON media types + param = { + content: { + "application/definitely-not-json": { + schema: { + required: true, + type: "object" + } + } + } + } + value = "{{}" + assertValidateOas3Param(param, value, []) + }) + + it("validates required strings with min and max length", () => { + // invalid string with max length + param = { + required: true, + type: "string", + maxLength: 5 + } + value = "test string" + assertValidateParam(param, value, ["Value must be no longer than 5 characters"]) + + // invalid string with max length 0 + param = { + required: true, + type: "string", + maxLength: 0 + } + value = "test string" + assertValidateParam(param, value, ["Value must be no longer than 0 characters"]) + + // invalid string with min length + param = { + required: true, + type: "string", + minLength: 50 + } + value = "test string" + assertValidateParam(param, value, ["Value must be at least 50 characters"]) + }) + + it("validates optional strings", () => { + // valid (empty) string + param = { + required: false, + type: "string" + } + value = "" + assertValidateParam(param, value, []) + + // valid string + param = { + required: false, + type: "string" + } + value = "test" + assertValidateParam(param, value, []) + }) + + it("validates required files", () => { + // invalid file + param = { + required: true, + type: "file" + } + value = undefined + assertValidateParam(param, value, ["Required field is not provided"]) + + // valid file + param = { + required: true, + type: "file" + } + value = new win.File([""], "file.txt") + assertValidateParam(param, value, []) + }) + + it("validates optional files", () => { + // invalid file + param = { + required: false, + type: "file" + } + value = "not a file" + assertValidateParam(param, value, ["Value must be a file"]) + + // valid (empty) file + param = { + required: false, + type: "file" + } + value = undefined + assertValidateParam(param, value, []) + + // valid file + param = { + required: false, + type: "file" + } + value = new win.File([""], "file.txt") + assertValidateParam(param, value, []) + }) + + it("validates required arrays", () => { + // invalid (empty) array + param = { + required: true, + type: "array" + } + value = [] + assertValidateParam(param, value, ["Required field is not provided"]) + + // invalid (empty) array, represented as a string + param = { + required: true, + type: "array" + } + value = "" + assertValidateParam(param, value, ["Required field is not provided"]) + + // invalid (not an array) + param = { + required: true, + type: "array" + } + value = undefined + assertValidateParam(param, value, ["Required field is not provided"]) + + // invalid array, items do not match correct type + param = { + required: true, + type: "array", + items: { + type: "string" + } + } + value = [1] + assertValidateParam(param, value, [{index: 0, error: "Value must be a string"}]) + + // valid array, with no 'type' for items + param = { + required: true, + type: "array" + } + value = [1] + assertValidateParam(param, value, []) + + // valid array, with no 'type' for items, represented as a string + param = { + required: true, + type: "array" + } + value = "[1]" + assertValidateParam(param, value, []) + + // valid array, items match type + param = { + required: true, + type: "array", + items: { + type: "string" + } + } + value = ["1"] + assertValidateParam(param, value, []) + }) + + it("validates optional arrays", () => { + // valid, empty array + param = { + required: false, + type: "array" + } + value = [] + assertValidateParam(param, value, []) + + // valid, empty array, with validation constraint + param = { + required: false, + schema: { + type: "array", + minItems: 1 + } + } + value = undefined + assertValidateOas3Param(param, value, []) + + // invalid, empty array, with minItems validation constraint + param = { + required: false, + schema: { + type: "array", + minItems: 2 + } + } + value = ["12"] + assertValidateOas3Param(param, value, ["Array must contain at least 2 items"]) + + // valid, valid array with satisfied minItems validation constraint + param = { + required: false, + schema: { + type: "array", + minItems: 1 + } + } + value = ["probe"] + assertValidateOas3Param(param, value, []) + + // invalid, items do not match correct type + param = { + required: false, + type: "array", + items: { + type: "number" + } + } + value = ["number"] + assertValidateParam(param, value, [{index: 0, error: "Value must be a number"}]) + + // valid + param = { + required: false, + type: "array", + items: { + type: "string" + } + } + value = ["test"] + assertValidateParam(param, value, []) + }) + + it("validates required booleans", () => { + // invalid boolean value + param = { + required: true, + type: "boolean" + } + value = undefined + assertValidateParam(param, value, ["Required field is not provided"]) + + // invalid boolean value (not a boolean) + param = { + required: true, + type: "boolean" + } + value = "test string" + assertValidateParam(param, value, ["Value must be a boolean"]) + + // valid boolean value + param = { + required: true, + type: "boolean" + } + value = "true" + assertValidateParam(param, value, []) + + // valid boolean value + param = { + required: true, + type: "boolean" + } + value = false + assertValidateParam(param, value, []) + }) + + it("validates optional booleans", () => { + // valid (empty) boolean value + param = { + required: false, + type: "boolean" + } + value = undefined + assertValidateParam(param, value, []) + + // invalid boolean value (not a boolean) + param = { + required: false, + type: "boolean" + } + value = "test string" + assertValidateParam(param, value, ["Value must be a boolean"]) + + // valid boolean value + param = { + required: false, + type: "boolean" + } + value = "true" + assertValidateParam(param, value, []) + + // valid boolean value + param = { + required: false, + type: "boolean" + } + value = false + assertValidateParam(param, value, []) + }) + + it("validates required numbers", () => { + // invalid number, string instead of a number + param = { + required: true, + type: "number" + } + value = "test" + assertValidateParam(param, value, ["Value must be a number"]) + + // invalid number, undefined value + param = { + required: true, + type: "number" + } + value = undefined + assertValidateParam(param, value, ["Required field is not provided"]) + + // valid number with min and max + param = { + required: true, + type: "number", + minimum: 5, + maximum: 99 + } + value = 10 + assertValidateParam(param, value, []) + + // valid negative number with min and max + param = { + required: true, + type: "number", + minimum: -50, + maximum: -5 + } + value = -10 + assertValidateParam(param, value, []) + + // invalid number with maximum:0 + param = { + required: true, + type: "number", + + maximum: 0 + } + value = 1 + assertValidateParam(param, value, ["Value must be less than 0"]) + + // invalid number with minimum:0 + param = { + required: true, + type: "number", + minimum: 0 + } + value = -10 + assertValidateParam(param, value, ["Value must be greater than 0"]) + }) + + it("validates optional numbers", () => { + // invalid number, string instead of a number + param = { + required: false, + type: "number" + } + value = "test" + assertValidateParam(param, value, ["Value must be a number"]) + + // valid (empty) number + param = { + required: false, + type: "number" + } + value = undefined + assertValidateParam(param, value, []) + + // valid number + param = { + required: false, + type: "number" + } + value = 10 + assertValidateParam(param, value, []) + }) + + it("validates required integers", () => { + // invalid integer, string instead of an integer + param = { + required: true, + type: "integer" + } + value = "test" + assertValidateParam(param, value, ["Value must be an integer"]) + + // invalid integer, undefined value + param = { + required: true, + type: "integer" + } + value = undefined + assertValidateParam(param, value, ["Required field is not provided"]) + + // valid integer, but 0 is falsy in JS + param = { + required: true, + type: "integer" + } + value = 0 + assertValidateParam(param, value, []) + + // valid integer + param = { + required: true, + type: "integer" + } + value = 10 + assertValidateParam(param, value, []) + }) + + it("validates optional integers", () => { + // invalid integer, string instead of an integer + param = { + required: false, + type: "integer" + } + value = "test" + assertValidateParam(param, value, ["Value must be an integer"]) + + // valid (empty) integer + param = { + required: false, + type: "integer" + } + value = undefined + assertValidateParam(param, value, []) + + // integers + param = { + required: false, + type: "integer" + } + value = 10 + assertValidateParam(param, value, []) + }) + }) + + describe("fromJSOrdered", () => { + it("should create an OrderedMap from an object", () => { + const param = { + value: "test" + } + + const result = fromJSOrdered(param).toJS() + expect( result ).toEqual( { value: "test" } ) + }) + + it("should not use an object's length property for Map size", () => { + const param = { + length: 5 + } + + const result = fromJSOrdered(param).toJS() + expect( result ).toEqual( { length: 5 } ) + }) + + it("should create an OrderedMap from an array", () => { + const param = [1, 1, 2, 3, 5, 8] + + const result = fromJSOrdered(param).toJS() + expect( result ).toEqual( [1, 1, 2, 3, 5, 8] ) + }) + }) + + describe("getAcceptControllingResponse", () => { + it("should return the first 2xx response with a media type", () => { + const responses = fromJSOrdered({ + "200": { + content: { + "application/json": { + schema: { + type: "object" + } + } + } + }, + "201": { + content: { + "application/json": { + schema: { + type: "object" + } + } + } + } + }) + + expect(getAcceptControllingResponse(responses)).toEqual(responses.get("200")) + }) + it("should skip 2xx responses without defined media types", () => { + const responses = fromJSOrdered({ + "200": { + content: { + "application/json": { + schema: { + type: "object" + } + } + } + }, + "201": { + content: { + "application/json": { + schema: { + type: "object" + } + } + } + } + }) + + expect(getAcceptControllingResponse(responses)).toEqual(responses.get("201")) + }) + it("should default to the `default` response if it has defined media types", () => { + const responses = fromJSOrdered({ + "200": { + description: "quite empty" + }, + "201": { + description: "quite empty" + }, + default: { + content: { + "application/json": { + schema: { + type: "object" + } + } + } + } + }) + + expect(getAcceptControllingResponse(responses)).toEqual(responses.get("default")) + }) + it("should return null if there are no suitable controlling responses", () => { + const responses = fromJSOrdered({ + "200": { + description: "quite empty" + }, + "201": { + description: "quite empty" + }, + "default": { + description: "also empty.." + } + }) + + expect(getAcceptControllingResponse(responses)).toBe(null) + }) + it("should return null if an empty OrderedMap is passed", () => { + const responses = fromJSOrdered() + + expect(getAcceptControllingResponse(responses)).toBe(null) + }) + it("should return null if anything except an OrderedMap is passed", () => { + const responses = {} + + expect(getAcceptControllingResponse(responses)).toBe(null) + }) + }) + + describe("createDeepLinkPath", () => { + it("creates a deep link path replacing spaces with underscores", () => { + const result = createDeepLinkPath("tag id with spaces") + expect(result).toEqual("tag%20id%20with%20spaces") + }) + + it("trims input when creating a deep link path", () => { + let result = createDeepLinkPath(" spaces before and after ") + expect(result).toEqual("spaces%20before%20and%20after") + + result = createDeepLinkPath(" ") + expect(result).toEqual("") + }) + + it("creates a deep link path with special characters", () => { + const result = createDeepLinkPath("!@#$%^&*(){}[]") + expect(result).toEqual("!@#$%^&*(){}[]") + }) + + it("returns an empty string for invalid input", () => { + expect( createDeepLinkPath(null) ).toEqual("") + expect( createDeepLinkPath(undefined) ).toEqual("") + expect( createDeepLinkPath(1) ).toEqual("") + expect( createDeepLinkPath([]) ).toEqual("") + expect( createDeepLinkPath({}) ).toEqual("") + }) + }) + + describe("escapeDeepLinkPath", () => { + it("creates and escapes a deep link path", () => { + const result = escapeDeepLinkPath("tag id with spaces?") + expect(result).toEqual("tag_id_with_spaces\\?") + }) + + it("escapes a deep link path that starts with a number", () => { + const result = escapeDeepLinkPath("123") + expect(result).toEqual("\\31 23") + }) + + it("escapes a deep link path with a class selector", () => { + const result = escapeDeepLinkPath("hello.world") + expect(result).toEqual("hello\\.world") + }) + + it("escapes a deep link path with an id selector", () => { + const result = escapeDeepLinkPath("hello#world") + expect(result).toEqual("hello\\#world") + }) + + it("escapes a deep link path with a space", () => { + const result = escapeDeepLinkPath("hello world") + expect(result).toEqual("hello_world") + }) + + it("escapes a deep link path with a percent-encoded space", () => { + const result = escapeDeepLinkPath("hello%20world") + expect(result).toEqual("hello_world") + }) + }) + + describe("getExtensions", () => { + const objTest = Map([[ "x-test", "a"], ["minimum", "b"]]) + it("does not error on empty array", () => { + const result1 = getExtensions([]) + expect(result1).toEqual([]) + const result2 = getCommonExtensions([]) + expect(result2).toEqual([]) + }) + it("gets only the x- keys", () => { + const result = getExtensions(objTest) + expect(result).toEqual(Map([[ "x-test", "a"]])) + }) + it("gets the common keys", () => { + const result = getCommonExtensions(objTest, true) + expect(result).toEqual(Map([[ "minimum", "b"]])) + }) + }) + + describe("deeplyStripKey", () => { + it("should filter out a specified key", () => { + const input = { + $$ref: "#/this/is/my/ref", + a: { + $$ref: "#/this/is/my/other/ref", + value: 12345 + } + } + const result = deeplyStripKey(input, "$$ref") + expect(result).toEqual({ + a: { + value: 12345 + } + }) + }) + + it("should filter out a specified key by predicate", () => { + const input = { + $$ref: "#/this/is/my/ref", + a: { + $$ref: "#/keep/this/one", + value: 12345 + } + } + const result = deeplyStripKey(input, "$$ref", (v) => v !== "#/keep/this/one") + expect(result).toEqual({ + a: { + value: 12345, + $$ref: "#/keep/this/one" + } + }) + }) + + it("should only call the predicate when the key matches", () => { + const input = { + $$ref: "#/this/is/my/ref", + a: { + $$ref: "#/this/is/my/other/ref", + value: 12345 + } + } + let count = 0 + + const result = deeplyStripKey(input, "$$ref", () => { + count++ + return true + }) + expect(count).toEqual(2) + }) + }) + + describe("parse and serialize search", () => { + beforeEach(() => { + // jsdom in Jest 25+ prevents modifying window.location, + // so we replace with a stubbed version + delete win.location + win.location = { + search: "" + } + }) + afterEach(() => { + win.location.search = "" + }) + + describe("parsing", () => { + it("works with empty search", () => { + win.location.search = "" + expect(parseSearch()).toEqual({}) + }) + + it("works with only one key", () => { + win.location.search = "?foo" + expect(parseSearch()).toEqual({foo: ""}) + }) + + it("works with keys and values", () => { + win.location.search = "?foo=fooval&bar&baz=bazval" + expect(parseSearch()).toEqual({foo: "fooval", bar: "", baz: "bazval"}) + }) + + it("decode url encoded components", () => { + win.location.search = "?foo=foo%20bar" + expect(parseSearch()).toEqual({foo: "foo bar"}) + }) + }) + + describe("serializing", () => { + it("works with empty map", () => { + expect(serializeSearch({})).toEqual("") + }) + + it("works with multiple keys with and without values", () => { + expect(serializeSearch({foo: "", bar: "barval"})).toEqual("foo=&bar=barval") + }) + + it("encode url components", () => { + expect(serializeSearch({foo: "foo bar"})).toEqual("foo=foo%20bar") + }) + }) + }) + + describe("sanitizeUrl", () => { + it("should sanitize a `javascript:` url", () => { + const res = sanitizeUrl("javascript:alert('bam!')") + + expect(res).toEqual("about:blank") + }) + + it("should sanitize a `data:` url", () => { + const res = sanitizeUrl(`data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=`) + + expect(res).toEqual("about:blank") + }) + + it("should not modify a `http:` url", () => { + const res = sanitizeUrl(`http://swagger.io/`) + + expect(res).toEqual("http://swagger.io/") + }) + + it("should not modify a `https:` url", () => { + const res = sanitizeUrl(`https://swagger.io/`) + + expect(res).toEqual("https://swagger.io/") + }) + + it("should gracefully handle empty strings", () => { + expect(sanitizeUrl("")).toEqual("") + }) + + it("should gracefully handle non-string values", () => { + expect(sanitizeUrl(123)).toEqual("") + expect(sanitizeUrl(null)).toEqual("") + expect(sanitizeUrl(undefined)).toEqual("") + expect(sanitizeUrl([])).toEqual("") + expect(sanitizeUrl({})).toEqual("") + }) + }) + + describe("isAbsoluteUrl", () => { + + it("check if url is absolute", () => { + expect(!!isAbsoluteUrl("http://example.com")).toEqual(true) + expect(!!isAbsoluteUrl("https://secure-example.com")).toEqual(true) + expect(!!isAbsoluteUrl("HTTP://uppercase-example.com")).toEqual(true) + expect(!!isAbsoluteUrl("HTTP://uppercase-secure-example.com")).toEqual(true) + expect(!!isAbsoluteUrl("http://trailing-slash.com/")).toEqual(true) + expect(!!isAbsoluteUrl("ftp://file-transfer-protocol.com")).toEqual(true) + expect(!!isAbsoluteUrl("//no-protocol.com")).toEqual(true) + }) + + it("check if url is not absolute", () => { + expect(!!isAbsoluteUrl("/url-relative-to-host/base-path/path")).toEqual(false) + expect(!!isAbsoluteUrl("url-relative-to-base/base-path/path")).toEqual(false) + }) + }) + + describe("buildBaseUrl", () => { + const specUrl = "https://petstore.swagger.io/v2/swagger.json" + + const noServerSelected = "" + const absoluteServerUrl = "https://server-example.com/base-path/path" + const serverUrlRelativeToBase = "server-example/base-path/path" + const serverUrlRelativeToHost = "/server-example/base-path/path" + + it("build base url with no server selected", () => { + expect(buildBaseUrl(noServerSelected, specUrl)).toBe("https://petstore.swagger.io/v2/swagger.json") + }) + + it("build base url from absolute server url", () => { + expect(buildBaseUrl(absoluteServerUrl, specUrl)).toBe("https://server-example.com/base-path/path") + }) + + it("build base url from relative server url", () => { + expect(buildBaseUrl(serverUrlRelativeToBase, specUrl)).toBe("https://petstore.swagger.io/v2/server-example/base-path/path") + expect(buildBaseUrl(serverUrlRelativeToHost, specUrl)).toBe("https://petstore.swagger.io/server-example/base-path/path") + }) + }) + + describe("buildUrl", () => { + const { location } = window + beforeAll(() => { + delete window.location + window.location = { + href: "http://localhost/", + } + }) + afterAll(() => { + window.location = location + }) + + const specUrl = "https://petstore.swagger.io/v2/swagger.json" + + const noUrl = "" + const absoluteUrl = "https://example.com/base-path/path" + const urlRelativeToBase = "relative-url/base-path/path" + const urlRelativeToHost = "/relative-url/base-path/path" + + const noServerSelected = "" + const absoluteServerUrl = "https://server-example.com/base-path/path" + const serverUrlRelativeToBase = "server-example/base-path/path" + const serverUrlRelativeToHost = "/server-example/base-path/path" + const serverUrlWithVariables = "https://api.example.com:{port}/{basePath}" + + const specUrlAsInvalidUrl = "./examples/test.yaml" + const specUrlOas2NonUrlString = "an allowed OAS2 TermsOfService description string" + + it("build no url", () => { + expect(buildUrl(noUrl, specUrl, { selectedServer: absoluteServerUrl })).toBe(undefined) + expect(buildUrl(noUrl, specUrl, { selectedServer: serverUrlRelativeToBase })).toBe(undefined) + expect(buildUrl(noUrl, specUrl, { selectedServer: serverUrlRelativeToHost })).toBe(undefined) + }) + + it("build absolute url", () => { + expect(buildUrl(absoluteUrl, specUrl, { selectedServer: absoluteServerUrl })).toBe("https://example.com/base-path/path") + expect(buildUrl(absoluteUrl, specUrl, { selectedServer: serverUrlRelativeToBase })).toBe("https://example.com/base-path/path") + expect(buildUrl(absoluteUrl, specUrl, { selectedServer: serverUrlRelativeToHost })).toBe("https://example.com/base-path/path") + }) + + it("build relative url with no server selected", () => { + expect(buildUrl(urlRelativeToBase, specUrl, { selectedServer: noServerSelected })).toBe("https://petstore.swagger.io/v2/relative-url/base-path/path") + expect(buildUrl(urlRelativeToHost, specUrl, { selectedServer: noServerSelected })).toBe("https://petstore.swagger.io/relative-url/base-path/path") + }) + + it("build relative url with absolute server url", () => { + expect(buildUrl(urlRelativeToBase, specUrl, { selectedServer: absoluteServerUrl })).toBe("https://server-example.com/base-path/relative-url/base-path/path") + expect(buildUrl(urlRelativeToHost, specUrl, { selectedServer: absoluteServerUrl })).toBe("https://server-example.com/relative-url/base-path/path") + }) + + it("build relative url with server url relative to base", () => { + expect(buildUrl(urlRelativeToBase, specUrl, { selectedServer: serverUrlRelativeToBase })).toBe("https://petstore.swagger.io/v2/server-example/base-path/relative-url/base-path/path") + expect(buildUrl(urlRelativeToHost, specUrl, { selectedServer: serverUrlRelativeToBase })).toBe("https://petstore.swagger.io/relative-url/base-path/path") + }) + + it("build relative url with server url relative to host", () => { + expect(buildUrl(urlRelativeToBase, specUrl, { selectedServer: serverUrlRelativeToHost })).toBe("https://petstore.swagger.io/server-example/base-path/relative-url/base-path/path") + expect(buildUrl(urlRelativeToHost, specUrl, { selectedServer: serverUrlRelativeToHost })).toBe("https://petstore.swagger.io/relative-url/base-path/path") + }) + + it("build relative url when no servers defined AND specUrl is invalid Url", () => { + expect(buildUrl(urlRelativeToHost, specUrlAsInvalidUrl, { selectedServer: noServerSelected })).toBe("http://localhost/relative-url/base-path/path") + }) + + it("build relative url when no servers defined AND specUrl is OAS2 non-url string", () => { + expect(buildUrl(urlRelativeToHost, specUrlOas2NonUrlString, { selectedServer: noServerSelected })).toBe("http://localhost/relative-url/base-path/path") + }) + + it("throws error when server url contains non-transcluded server variables", () => { + const buildUrlThunk = () => buildUrl(urlRelativeToHost, specUrl, { selectedServer: serverUrlWithVariables }) + + expect(buildUrlThunk).toThrow(/^Invalid base URL/) + }) + }) + + describe("safeBuildUrl", () => { + const { location } = window + beforeAll(() => { + delete window.location + window.location = { + href: "http://localhost/", + } + }) + afterAll(() => { + window.location = location + }) + + const specUrl = "https://petstore.swagger.io/v2/swagger.json" + + const noUrl = "" + const absoluteUrl = "https://example.com/base-path/path" + const urlRelativeToBase = "relative-url/base-path/path" + const urlRelativeToHost = "/relative-url/base-path/path" + + const noServerSelected = "" + const absoluteServerUrl = "https://server-example.com/base-path/path" + const serverUrlRelativeToBase = "server-example/base-path/path" + const serverUrlRelativeToHost = "/server-example/base-path/path" + const serverUrlWithVariables = "https://api.example.com:{port}/{basePath}" + + const specUrlAsInvalidUrl = "./examples/test.yaml" + const specUrlOas2NonUrlString = "an allowed OAS2 TermsOfService description string" + + it("build no url", () => { + expect(safeBuildUrl(noUrl, specUrl, { selectedServer: absoluteServerUrl })).toBe(undefined) + expect(safeBuildUrl(noUrl, specUrl, { selectedServer: serverUrlRelativeToBase })).toBe(undefined) + expect(safeBuildUrl(noUrl, specUrl, { selectedServer: serverUrlRelativeToHost })).toBe(undefined) + }) + + it("build absolute url", () => { + expect(safeBuildUrl(absoluteUrl, specUrl, { selectedServer: absoluteServerUrl })).toBe("https://example.com/base-path/path") + expect(safeBuildUrl(absoluteUrl, specUrl, { selectedServer: serverUrlRelativeToBase })).toBe("https://example.com/base-path/path") + expect(safeBuildUrl(absoluteUrl, specUrl, { selectedServer: serverUrlRelativeToHost })).toBe("https://example.com/base-path/path") + }) + + it("build relative url with no server selected", () => { + expect(safeBuildUrl(urlRelativeToBase, specUrl, { selectedServer: noServerSelected })).toBe("https://petstore.swagger.io/v2/relative-url/base-path/path") + expect(safeBuildUrl(urlRelativeToHost, specUrl, { selectedServer: noServerSelected })).toBe("https://petstore.swagger.io/relative-url/base-path/path") + }) + + it("build relative url with absolute server url", () => { + expect(safeBuildUrl(urlRelativeToBase, specUrl, { selectedServer: absoluteServerUrl })).toBe("https://server-example.com/base-path/relative-url/base-path/path") + expect(safeBuildUrl(urlRelativeToHost, specUrl, { selectedServer: absoluteServerUrl })).toBe("https://server-example.com/relative-url/base-path/path") + }) + + it("build relative url with server url relative to base", () => { + expect(safeBuildUrl(urlRelativeToBase, specUrl, { selectedServer: serverUrlRelativeToBase })).toBe("https://petstore.swagger.io/v2/server-example/base-path/relative-url/base-path/path") + expect(safeBuildUrl(urlRelativeToHost, specUrl, { selectedServer: serverUrlRelativeToBase })).toBe("https://petstore.swagger.io/relative-url/base-path/path") + }) + + it("build relative url with server url relative to host", () => { + expect(safeBuildUrl(urlRelativeToBase, specUrl, { selectedServer: serverUrlRelativeToHost })).toBe("https://petstore.swagger.io/server-example/base-path/relative-url/base-path/path") + expect(safeBuildUrl(urlRelativeToHost, specUrl, { selectedServer: serverUrlRelativeToHost })).toBe("https://petstore.swagger.io/relative-url/base-path/path") + }) + + it("build relative url when no servers defined AND specUrl is invalid Url", () => { + expect(safeBuildUrl(urlRelativeToHost, specUrlAsInvalidUrl, { selectedServer: noServerSelected })).toBe("http://localhost/relative-url/base-path/path") + }) + + it("build relative url when no servers defined AND specUrl is OAS2 non-url string", () => { + expect(safeBuildUrl(urlRelativeToHost, specUrlOas2NonUrlString, { selectedServer: noServerSelected })).toBe("http://localhost/relative-url/base-path/path") + }) + + it("build no url when server url contains non-transcluded server variables", () => { + expect(safeBuildUrl(urlRelativeToHost, specUrl, { selectedServer: serverUrlWithVariables })).toBe(undefined) + }) + }) + + describe("requiresValidationURL", () => { + it("Should tell us if we require a ValidationURL", () => { + const res = requiresValidationURL("https://example.com") + + expect(res).toBe(true) + }) + + it(".. and localhost is not", () => { + const res = requiresValidationURL("http://localhost") + + expect(res).toBe(false) + }) + + it(".. and neither does 127.0.0.1", () => { + const res = requiresValidationURL("http://127.0.0.1") + + expect(res).toBe(false) + }) + + it(".. even without the proto", () => { + const res = requiresValidationURL("127.0.0.1") + + expect(res).toBe(false) + }) + + it(".. and also not with 'none'", () => { + const res = requiresValidationURL("none") + + expect(res).toBe(false) + }) + + it(".. and also not with 'none'", () => { + const res = requiresValidationURL("none") + + expect(res).toBe(false) + }) + + it(".. and also not with ''", () => { + const res = requiresValidationURL("") + + expect(res).toBe(false) + }) + + }) + + describe("getSampleSchema", () => { + const oriDate = Date + + beforeEach(() => { + Date = function () { + this.toISOString = function () { + return "2018-07-07T07:07:05.189Z" + } + } + }) + + afterEach(() => { + Date = oriDate + }) + + it("should stringify string values if json content-type", () => { + // Given + const res = getSampleSchema({ + type: "string", + format: "date-time" + }, "text/json") + + // Then + expect(res).toEqual(JSON.stringify(new Date().toISOString())) + }) + + it("should not unnecessarily stringify string values for other content-types", () => { + // Given + const res = getSampleSchema({ + type: "string", + format: "date-time" + }) + + // Then + expect(res).toEqual(new Date().toISOString()) + }) + + it("should not unnecessarily stringify non-object values", () => { + // Given + const res = getSampleSchema({ + type: "number" + }) + + // Then + expect(res).toEqual(0) + }) + + it("should not unnecessarily stringify non-object values if content-type is json", () => { + // Given + const res = getSampleSchema({ + type: "number" + }, "application/json") + + // Then + expect(res).toEqual(0) + }) + + it("should stringify object when literal string example is provided if json content-type", () => { + // Given + const expected = "" + const res = getSampleSchema({ + type: "object", + }, "text/json", {}, expected) + + // Then + expect(res).toEqual(JSON.stringify(expected)) + }) + + it("should parse valid json literal example if json content-type", () => { + // Given + const expected = {test: 123} + const res = getSampleSchema({ + type: "object", + }, "text/json", {}, JSON.stringify(expected)) + + // Then + const actual = JSON.parse(res) + expect(actual.test).toEqual(123) + }) + + it("should handle number example with string schema as string", () => { + // Given + const expected = 123 + const res = getSampleSchema({ + type: "string", + }, "text/json", {}, expected) + + // Then + const actual = JSON.parse(res) + expect(actual).toEqual("123") + }) + + it("should handle number literal example with string schema as string", () => { + // Given + const expected = "123" + const res = getSampleSchema({ + type: "string", + }, "text/json", {}, expected) + + // Then + const actual = JSON.parse(res) + expect(actual).toEqual("123") + }) + + it("should handle number literal example with number schema as number", () => { + // Given + const expected = "123" + const res = getSampleSchema({ + type: "number", + }, "text/json", {}, expected) + + // Then + const actual = JSON.parse(res) + expect(actual).toEqual(123) + }) + + it("should return yaml example if yaml is contained in the content-type", () => { + const res = getSampleSchema({ + type: "object", + }, "text/yaml", {}, {test: 123}) + + expect(res).toEqual("test: 123") + }) + + it("should return yaml example if yml is contained in the content-type", () => { + const res = getSampleSchema({ + type: "object", + }, "text/yml", {}, {test: 123}) + + expect(res).toEqual("test: 123") + }) + }) + + describe("paramToIdentifier", () => { + it("should convert an Immutable parameter map to an identifier", () => { + const param = fromJS({ + name: "id", + in: "query" + }) + const res = paramToIdentifier(param) + + expect(res).toEqual("query.id.hash-606199662") + }) + it("should convert an Immutable parameter map to a set of identifiers", () => { + const param = fromJS({ + name: "id", + in: "query" + }) + const res = paramToIdentifier(param, { returnAll: true }) + + expect(res).toEqual([ + "query.id.hash-606199662", + "query.id", + "id" + ]) + }) + + it("should convert an unhashable Immutable parameter map to an identifier", () => { + const param = fromJS({ + name: "id", + in: "query" + }) + + param.hashCode = null + + const res = paramToIdentifier(param) + + expect(res).toEqual("query.id") + }) + + it("should convert an unhashable Immutable parameter map to a set of identifiers", () => { + const param = fromJS({ + name: "id", + in: "query" + }) + + param.hashCode = null + + const res = paramToIdentifier(param, { returnAll: true }) + + expect(res).toEqual([ + "query.id", + "id" + ]) + }) + + it("should convert an Immutable parameter map lacking an `in` value to an identifier", () => { + const param = fromJS({ + name: "id" + }) + + const res = paramToIdentifier(param) + + expect(res).toEqual("id") + }) + + it("should convert an Immutable parameter map lacking an `in` value to an identifier", () => { + const param = fromJS({ + name: "id" + }) + + const res = paramToIdentifier(param, { returnAll: true }) + + expect(res).toEqual(["id"]) + }) + + it("should throw gracefully when given a non-Immutable parameter input", () => { + const param = { + name: "id" + } + + let error = null + let res = null + + try { + const res = paramToIdentifier(param) + } catch(e) { + error = e + } + + expect(error).toBeInstanceOf(Error) + expect(error.message).toContain("received a non-Im.Map parameter as input") + expect(res).toEqual(null) + }) + }) + + describe("paramToValue", () => { + it("should identify a hash-keyed value", () => { + const param = fromJS({ + name: "id", + in: "query" + }) + + const paramValues = { + "query.id.hash-606199662": "asdf" + } + + const res = paramToValue(param, paramValues) + + expect(res).toEqual("asdf") + }) + + it("should identify a in+name value", () => { + const param = fromJS({ + name: "id", + in: "query" + }) + + const paramValues = { + "query.id": "asdf" + } + + const res = paramToValue(param, paramValues) + + expect(res).toEqual("asdf") + }) + + it("should identify a name value", () => { + const param = fromJS({ + name: "id", + in: "query" + }) + + const paramValues = { + "id": "asdf" + } + + const res = paramToValue(param, paramValues) + + expect(res).toEqual("asdf") + }) + }) + + describe("generateCodeVerifier", () => { + it("should generate a value of at least 43 characters", () => { + const codeVerifier = generateCodeVerifier() + + // Source: https://tools.ietf.org/html/rfc7636#section-4.1 + expect(codeVerifier.length).toBeGreaterThanOrEqual(43) + }) + }) + + describe("createCodeChallenge", () => { + it("should hash the input using SHA256 and output the base64 url encoded value", () => { + // The `codeVerifier` has been randomly generated + const codeVerifier = "cY8OJ9MKvZ7hxQeIyRYD7KFmKA5znSFJ2rELysvy2UI" + + // This value is the `codeVerifier` hashed using SHA256, which has been + // encoded using base64 url format. + // Source: https://tools.ietf.org/html/rfc7636#section-4.2 + const expectedCodeChallenge = "LD9lx2p2PbvGkojuJy7-Elex7RnckzmqR7oIXjd4u84" + + expect(createCodeChallenge(codeVerifier)).toBe(expectedCodeChallenge) + }) + }) +}) diff --git a/frontend/web/api-doc/test/unit/docker/oauth.js b/frontend/web/api-doc/test/unit/docker/oauth.js new file mode 100644 index 0000000..6aa9bcd --- /dev/null +++ b/frontend/web/api-doc/test/unit/docker/oauth.js @@ -0,0 +1,66 @@ +const oauthBlockBuilder = require("../../../docker/configurator/oauth") +const dedent = require("dedent") + +describe("docker: env translator - oauth block", function() { + it("should omit the block if there are no valid keys", function () { + const input = {} + + expect(oauthBlockBuilder(input)).toEqual(``) + }) + it("should omit the block if there are no valid keys", function () { + const input = { + NOT_A_VALID_KEY: "asdf1234" + } + + expect(oauthBlockBuilder(input)).toEqual(``) + }) + it("should generate a block from empty values", function() { + const input = { + OAUTH_CLIENT_ID: ``, + OAUTH_CLIENT_SECRET: ``, + OAUTH_REALM: ``, + OAUTH_APP_NAME: ``, + OAUTH_SCOPE_SEPARATOR: "", + OAUTH_ADDITIONAL_PARAMS: ``, + OAUTH_USE_BASIC_AUTH: false, + OAUTH_USE_PKCE: false + } + + expect(oauthBlockBuilder(input)).toEqual(dedent(` + ui.initOAuth({ + clientId: "", + clientSecret: "", + realm: "", + appName: "", + scopeSeparator: "", + additionalQueryStringParams: undefined, + useBasicAuthenticationWithAccessCodeGrant: false, + usePkceWithAuthorizationCodeGrant: false, + })`)) + }) + + it("should generate a full block", function() { + const input = { + OAUTH_CLIENT_ID: `myId`, + OAUTH_CLIENT_SECRET: `mySecret`, + OAUTH_REALM: `myRealm`, + OAUTH_APP_NAME: `myAppName`, + OAUTH_SCOPE_SEPARATOR: "%21", + OAUTH_ADDITIONAL_PARAMS: `{ "a": 1234, "b": "stuff" }`, + OAUTH_USE_BASIC_AUTH: true, + OAUTH_USE_PKCE: true + } + + expect(oauthBlockBuilder(input)).toEqual(dedent(` + ui.initOAuth({ + clientId: "myId", + clientSecret: "mySecret", + realm: "myRealm", + appName: "myAppName", + scopeSeparator: "%21", + additionalQueryStringParams: { "a": 1234, "b": "stuff" }, + useBasicAuthenticationWithAccessCodeGrant: true, + usePkceWithAuthorizationCodeGrant: true, + })`)) + }) +}) diff --git a/frontend/web/api-doc/test/unit/docker/translator.js b/frontend/web/api-doc/test/unit/docker/translator.js new file mode 100644 index 0000000..2829197 --- /dev/null +++ b/frontend/web/api-doc/test/unit/docker/translator.js @@ -0,0 +1,345 @@ +const translator = require("../../../docker/configurator/translator") +const dedent = require("dedent") + +describe("docker: env translator", function() { + describe("fundamentals", function() { + it("should generate an empty baseline config", function () { + const input = {} + + expect(translator(input)).toEqual(``) + }) + + it("should call an onFound callback", function () { + const input = { + MY_THING: "hey" + } + + const onFoundSpy = jest.fn() + + const schema = { + MY_THING: { + type: "string", + name: "myThing", + onFound: onFoundSpy + } + } + + const res = translator(input, { + schema + }) + expect(res).toEqual(`myThing: "hey",`) + expect(onFoundSpy.mock.calls.length).toEqual(1) + + }) + + it("should use a regular value over a legacy one, regardless of order", function () { + const schema = { + MY_THING: { + type: "string", + name: "myThing" + }, + MY_OTHER_THING: { + type: "string", + name: "myThing", + legacy: true + } + } + + // Regular value provided first + expect(translator({ + MY_THING: "hey", + MY_OTHER_THING: "hello" + }, { + schema + })).toEqual(`myThing: "hey",`) + + // Legacy value provided first + expect(translator({ + MY_OTHER_THING: "hello", + MY_THING: "hey" + }, { + schema + })).toEqual(`myThing: "hey",`) + }) + + it("should use a legacy value over a base one, regardless of order", function () { + const schema = { + MY_THING: { + type: "string", + name: "myThing", + legacy: true + } + } + + const baseConfig = { + myThing: { + value: "base", + schema: { + type: "string", + base: true + } + } + } + + // Regular value provided first + expect(translator({ + MY_THING: "legacy" + }, { + injectBaseConfig: true, + schema, + baseConfig + })).toEqual(`myThing: "legacy",`) + }) + }) + describe("Swagger UI configuration", function() { + it("should generate a base config including the base content", function () { + const input = {} + + expect(translator(input, { + injectBaseConfig: true + })).toEqual(dedent(` + url: "https://petstore.swagger.io/v2/swagger.json", + "dom_id": "#swagger-ui", + deepLinking: true, + presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIStandalonePreset + ], + plugins: [ + SwaggerUIBundle.plugins.DownloadUrl + ], + layout: "StandaloneLayout", + queryConfigEnabled: false, + `)) + }) + + it("should ignore an unknown config", function () { + const input = { + ASDF1234: "wow hello" + } + + expect(translator(input)).toEqual(dedent(``)) + }) + + it("should generate a string config", function () { + const input = { + URL: "http://petstore.swagger.io/v2/swagger.json", + FILTER: "" + } + + expect(translator(input)).toEqual(dedent(` + url: "http://petstore.swagger.io/v2/swagger.json", + filter: "",` + ).trim()) + }) + + it("should generate a boolean config", function () { + const input = { + DEEP_LINKING: "true", + SHOW_EXTENSIONS: "false", + SHOW_COMMON_EXTENSIONS: "" + } + + expect(translator(input)).toEqual(dedent(` + deepLinking: true, + showExtensions: false, + showCommonExtensions: undefined,` + )) + }) + + it("should generate an object config", function () { + const input = { + SPEC: `{ swagger: "2.0" }` + } + + expect(translator(input)).toEqual(dedent(` + spec: { swagger: "2.0" },` + ).trim()) + }) + + it("should generate an array config", function () { + const input = { + URLS: `["/one", "/two"]`, + SUPPORTED_SUBMIT_METHODS: "" + } + + expect(translator(input)).toEqual(dedent(` + urls: ["/one", "/two"], + supportedSubmitMethods: undefined,` + ).trim()) + }) + + it("should properly escape key names when necessary", function () { + const input = { + URLS: `["/one", "/two"]`, + URLS_PRIMARY_NAME: "one", + } + + expect(translator(input)).toEqual(dedent(` + urls: ["/one", "/two"], + "urls.primaryName": "one",` + ).trim()) + }) + + it("should disregard a legacy variable in favor of a regular one", function () { + const input = { + // Order is important to this test... legacy vars should be + // superseded regardless of what is fed in first. + API_URL: "/old.json", + URL: "/swagger.json", + URLS: `["/one", "/two"]`, + API_URLS: `["/three", "/four"]`, + } + + expect(translator(input)).toEqual(dedent(` + url: "/swagger.json", + urls: ["/one", "/two"],` + ).trim()) + }) + + + it("should pick up legacy variables when using base config", function () { + const input = { + API_URL: "/swagger.json", + API_URLS: `["/one", "/two"]`, + } + + expect(translator(input, { injectBaseConfig: true })).toEqual(dedent(` + "dom_id": "#swagger-ui", + deepLinking: true, + presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIStandalonePreset + ], + plugins: [ + SwaggerUIBundle.plugins.DownloadUrl + ], + layout: "StandaloneLayout", + queryConfigEnabled: false, + url: "/swagger.json", + urls: ["/one", "/two"],` + + ).trim()) + }) + + it("should generate a full config k:v string", function () { + const input = { + API_URL: "/old.yaml", + API_URLS: `["/old", "/older"]`, + CONFIG_URL: "/wow", + DOM_ID: "#swagger_ui", + SPEC: `{ swagger: "2.0" }`, + URL: "/swagger.json", + URLS: `["/one", "/two"]`, + URLS_PRIMARY_NAME: "one", + LAYOUT: "BaseLayout", + DEEP_LINKING: "false", + DISPLAY_OPERATION_ID: "true", + DEFAULT_MODELS_EXPAND_DEPTH: "0", + DEFAULT_MODEL_EXPAND_DEPTH: "1", + DEFAULT_MODEL_RENDERING: "example", + DISPLAY_REQUEST_DURATION: "true", + DOC_EXPANSION: "full", + FILTER: "wowee", + MAX_DISPLAYED_TAGS: "4", + SHOW_EXTENSIONS: "true", + SHOW_COMMON_EXTENSIONS: "false", + OAUTH2_REDIRECT_URL: "http://google.com/", + SHOW_MUTATED_REQUEST: "true", + SUPPORTED_SUBMIT_METHODS: `["get", "post"]`, + TRY_IT_OUT_ENABLED: "true", + VALIDATOR_URL: "http://smartbear.com/" + } + + expect(translator(input)).toEqual(dedent(` + configUrl: "/wow", + "dom_id": "#swagger_ui", + spec: { swagger: "2.0" }, + url: "/swagger.json", + urls: ["/one", "/two"], + "urls.primaryName": "one", + layout: "BaseLayout", + deepLinking: false, + displayOperationId: true, + defaultModelsExpandDepth: 0, + defaultModelExpandDepth: 1, + defaultModelRendering: "example", + displayRequestDuration: true, + docExpansion: "full", + filter: "wowee", + maxDisplayedTags: 4, + showExtensions: true, + showCommonExtensions: false, + oauth2RedirectUrl: "http://google.com/", + showMutatedRequest: true, + supportedSubmitMethods: ["get", "post"], + tryItOutEnabled: true, + validatorUrl: "http://smartbear.com/",` + ).trim()) + }) + + it("should generate a full config k:v string including base config", function () { + const input = { + API_URL: "/old.yaml", + API_URLS: `["/old", "/older"]`, + CONFIG_URL: "/wow", + DOM_ID: "#swagger_ui", + SPEC: `{ swagger: "2.0" }`, + URL: "/swagger.json", + URLS: `["/one", "/two"]`, + URLS_PRIMARY_NAME: "one", + LAYOUT: "BaseLayout", + DEEP_LINKING: "false", + DISPLAY_OPERATION_ID: "true", + DEFAULT_MODELS_EXPAND_DEPTH: "0", + DEFAULT_MODEL_EXPAND_DEPTH: "1", + DEFAULT_MODEL_RENDERING: "example", + DISPLAY_REQUEST_DURATION: "true", + DOC_EXPANSION: "full", + FILTER: "wowee", + MAX_DISPLAYED_TAGS: "4", + SHOW_EXTENSIONS: "true", + SHOW_COMMON_EXTENSIONS: "false", + OAUTH2_REDIRECT_URL: "http://google.com/", + SHOW_MUTATED_REQUEST: "true", + SUPPORTED_SUBMIT_METHODS: `["get", "post"]`, + TRY_IT_OUT_ENABLED: "false", + VALIDATOR_URL: "http://smartbear.com/" + } + + expect(translator(input, { injectBaseConfig: true })).toEqual(dedent(` + presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIStandalonePreset + ], + plugins: [ + SwaggerUIBundle.plugins.DownloadUrl + ], + queryConfigEnabled: false, + configUrl: "/wow", + "dom_id": "#swagger_ui", + spec: { swagger: "2.0" }, + url: "/swagger.json", + urls: ["/one", "/two"], + "urls.primaryName": "one", + layout: "BaseLayout", + deepLinking: false, + displayOperationId: true, + defaultModelsExpandDepth: 0, + defaultModelExpandDepth: 1, + defaultModelRendering: "example", + displayRequestDuration: true, + docExpansion: "full", + filter: "wowee", + maxDisplayedTags: 4, + showExtensions: true, + showCommonExtensions: false, + oauth2RedirectUrl: "http://google.com/", + showMutatedRequest: true, + supportedSubmitMethods: ["get", "post"], + tryItOutEnabled: false, + validatorUrl: "http://smartbear.com/",` + ).trim()) + }) + }) +}) diff --git a/frontend/web/api-doc/test/unit/jest-shim.js b/frontend/web/api-doc/test/unit/jest-shim.js new file mode 100644 index 0000000..90496ee --- /dev/null +++ b/frontend/web/api-doc/test/unit/jest-shim.js @@ -0,0 +1,4 @@ +import { TextDecoder, TextEncoder } from "node:util" + +global.TextDecoder = TextDecoder +global.TextEncoder = TextEncoder diff --git a/frontend/web/api-doc/test/unit/setup.js b/frontend/web/api-doc/test/unit/setup.js new file mode 100644 index 0000000..db6e23b --- /dev/null +++ b/frontend/web/api-doc/test/unit/setup.js @@ -0,0 +1,36 @@ +import { JSDOM } from "jsdom" +import Enzyme from "enzyme" +import Adapter from "@wojtekmaj/enzyme-adapter-react-17" + +import win from "../../src/core/window" + +Enzyme.configure({ adapter: new Adapter() }) + +function copyProps(src, target) { + const props = Object.getOwnPropertyNames(src) + .filter(prop => typeof target[prop] === "undefined") + .reduce((result, prop) => ({ + ...result, + [prop]: Object.getOwnPropertyDescriptor(src, prop), + }), {}) + Object.defineProperties(target, props) +} + +function setUpDomEnvironment() { + const jsdom = new JSDOM("", { + url: "http://localhost/", + }) + const { window } = jsdom + + global.window = window + global.document = window.document + global.navigator = { + userAgent: "node.js", + } + copyProps(win, window) // use UI's built-in window wrapper + copyProps(window, global) +} + +setUpDomEnvironment() + +// configure({ adapter: new Adapter() }) // enzyme@3 diff --git a/frontend/web/api-doc/test/unit/swagger-ui-dist-package/absolute-path.js b/frontend/web/api-doc/test/unit/swagger-ui-dist-package/absolute-path.js new file mode 100644 index 0000000..8967c89 --- /dev/null +++ b/frontend/web/api-doc/test/unit/swagger-ui-dist-package/absolute-path.js @@ -0,0 +1,12 @@ + +import path from "path" +import getAbsoluteFSPath from "../../../swagger-ui-dist-package/absolute-path" + +describe("swagger-ui-dist", function(){ + describe("getAbsoluteFSPath", function(){ + it("returns absolute path", function(){ + const expectedPath = path.resolve(__dirname, "../../../swagger-ui-dist-package") + expect(getAbsoluteFSPath()).toEqual(expectedPath) + }) + }) +}) diff --git a/frontend/web/api-doc/test/unit/xss/anchor-target-rel/info.jsx b/frontend/web/api-doc/test/unit/xss/anchor-target-rel/info.jsx new file mode 100644 index 0000000..085b9f0 --- /dev/null +++ b/frontend/web/api-doc/test/unit/xss/anchor-target-rel/info.jsx @@ -0,0 +1,106 @@ +import React from "react" +import { render } from "enzyme" +import { fromJS } from "immutable" +import Info, { InfoUrl } from "components/info" +import { Link } from "components/layout-utils" +import Markdown from "components/providers/markdown" + +describe(" Anchor Target Safety", function(){ + const dummyComponent = () => null + const components = { + Markdown, + InfoUrl, + Link + } + const baseProps = { + getComponent: c => components[c] || dummyComponent, + host: "example.test", + basePath: "/api", + info: fromJS({ + title: "Hello World" + }) + } + + it("renders externalDocs links with safe `rel` attributes", function () { + const props = { + ...baseProps, + externalDocs: fromJS({ + url: "http://google.com/" + }) + } + let wrapper = render() + const anchor = wrapper.find("a") + + expect(anchor.html()).toEqual("http://google.com/") + expect(anchor.attr("target")).toEqual("_blank") + expect(anchor.attr("rel") || "").toMatch("noopener") + expect(anchor.attr("rel") || "").toMatch("noreferrer") + }) + + it("renders Contact links with safe `rel` attributes", function () { + const props = { + ...baseProps, + info: fromJS({ + contact: { + url: "http://google.com/", + name: "My Site" + } + }) + } + let wrapper = render() + const anchor = wrapper.find("a") + + expect(anchor.attr("href")).toEqual("http://google.com/") + expect(anchor.attr("target")).toEqual("_blank") + expect(anchor.attr("rel") || "").toMatch("noopener") + expect(anchor.attr("rel") || "").toMatch("noreferrer") + }) + + it("renders License links with safe `rel` attributes", function () { + const props = { + ...baseProps, + info: fromJS({ + license: { + url: "http://mit.edu/" + } + }) + } + let wrapper = render() + const anchor = wrapper.find("a") + + expect(anchor.attr("href")).toEqual("http://mit.edu/") + expect(anchor.attr("target")).toEqual("_blank") + expect(anchor.attr("rel") || "").toMatch("noopener") + expect(anchor.attr("rel") || "").toMatch("noreferrer") + }) + + it("renders termsOfService links with safe `rel` attributes", function () { + const props = { + ...baseProps, + info: fromJS({ + termsOfService: "http://smartbear.com/" + }) + } + let wrapper = render() + const anchor = wrapper.find("a") + + expect(anchor.attr("href")).toEqual("http://smartbear.com/") + expect(anchor.attr("target")).toEqual("_blank") + expect(anchor.attr("rel") || "").toMatch("noopener") + expect(anchor.attr("rel") || "").toMatch("noreferrer") + }) + + it("renders definition URL links with safe `rel` attributes", function () { + const props = { + ...baseProps, + url: "http://petstore.swagger.io/v2/petstore.json" + } + let wrapper = render() + const anchor = wrapper.find("a") + + expect(anchor.attr("href")).toEqual("http://petstore.swagger.io/v2/petstore.json") + expect(anchor.attr("target")).toEqual("_blank") + expect(anchor.attr("rel") || "").toMatch("noopener") + expect(anchor.attr("rel") || "").toMatch("noreferrer") + }) +}) diff --git a/frontend/web/api-doc/test/unit/xss/anchor-target-rel/link.jsx b/frontend/web/api-doc/test/unit/xss/anchor-target-rel/link.jsx new file mode 100644 index 0000000..6bd95a6 --- /dev/null +++ b/frontend/web/api-doc/test/unit/xss/anchor-target-rel/link.jsx @@ -0,0 +1,39 @@ +import React from "react" +import { render } from "enzyme" +import { Link } from "components/layout-utils" + +describe(" Anchor Target Safety", function () { + const dummyComponent = () => null + const components = { + Link + } + const baseProps = { + getComponent: c => components[c] || dummyComponent + } + + it("renders regular links with `noreferrer` and `noopener`", function () { + const props = { + ...baseProps, + href: "http://google.com/" + } + let wrapper = render() + + expect(wrapper.attr("href")).toEqual("http://google.com/") + expect(wrapper.attr("rel") || "").toMatch("noopener") + expect(wrapper.attr("rel") || "").toMatch("noreferrer") + }) + + it("enforces `noreferrer` and `noopener` on target=_blank links", function () { + const props = { + ...baseProps, + href: "http://google.com/", + target: "_blank" + } + let wrapper = render() + + expect(wrapper.attr("href")).toEqual("http://google.com/") + expect(wrapper.attr("target")).toEqual("_blank") + expect(wrapper.attr("rel") || "").toMatch("noopener") + expect(wrapper.attr("rel") || "").toMatch("noreferrer") + }) +}) diff --git a/frontend/web/api-doc/test/unit/xss/anchor-target-rel/markdown.jsx b/frontend/web/api-doc/test/unit/xss/anchor-target-rel/markdown.jsx new file mode 100644 index 0000000..a247a14 --- /dev/null +++ b/frontend/web/api-doc/test/unit/xss/anchor-target-rel/markdown.jsx @@ -0,0 +1,64 @@ +import React from "react" +import { render } from "enzyme" +import Markdown from "components/providers/markdown" +import { Markdown as OAS3Markdown } from "corePlugins/oas3/wrap-components/markdown.jsx" + +describe("Markdown Link Anchor Safety", function () { + describe("Swagger 2.0", function () { + it("sanitizes Markdown links", function () { + const str = `Hello, [here](http://google.com/) is my link` + const wrapper = render() + + const anchor = wrapper.find("a") + + expect(anchor.attr("href")).toEqual("http://google.com/") + expect(anchor.attr("target")).toEqual("_blank") + expect(anchor.attr("rel") || "").toMatch("noopener") + expect(anchor.attr("rel") || "").toMatch("noreferrer") + }) + + it("sanitizes raw HTML links", function () { + const str = `Hello, here is my link` + const wrapper = render() + + const anchor = wrapper.find("a") + + expect(anchor.attr("href")).toEqual("http://google.com/") + expect(anchor.attr("rel") || "").toMatch("noopener") + expect(anchor.attr("rel") || "").toMatch("noreferrer") + }) + }) + + describe("OAS 3", function () { + it("sanitizes Markdown links", function () { + const str = `Hello, [here](http://google.com/) is my link` + const wrapper = render() + + const anchor = wrapper.find("a") + + expect(anchor.attr("href")).toEqual("http://google.com/") + expect(anchor.attr("target")).toEqual("_blank") + expect(anchor.attr("rel") || "").toMatch("noopener") + expect(anchor.attr("rel") || "").toMatch("noreferrer") + }) + + it("sanitizes raw HTML links", function () { + const str = `Hello, here is my link` + const wrapper = render() + + const anchor = wrapper.find("a") + + expect(anchor.attr("href")).toEqual("http://google.com/") + expect(anchor.attr("rel") || "").toMatch("noopener") + expect(anchor.attr("rel") || "").toMatch("noreferrer") + }) + }) +}) + +function withMarkdownWrapper(str, { isOAS3 = false } = {}) { + if(isOAS3) { + return `

${str}

` + } + + return `

${str}

\n
` +} diff --git a/frontend/web/api-doc/test/unit/xss/anchor-target-rel/online-validator-badge.jsx b/frontend/web/api-doc/test/unit/xss/anchor-target-rel/online-validator-badge.jsx new file mode 100644 index 0000000..ecce983 --- /dev/null +++ b/frontend/web/api-doc/test/unit/xss/anchor-target-rel/online-validator-badge.jsx @@ -0,0 +1,29 @@ +import React from "react" +import { mount } from "enzyme" +import OnlineValidatorBadge from "components/online-validator-badge" + +describe(" Anchor Target Safety", function () { + it("should render a validator link with safe `rel` attributes", function () { + // When + const props = { + getConfigs: () => ({}), + getComponent: () => null, + specSelectors: { + url: () => "swagger.json" + } + } + const wrapper = mount( + + ) + + const anchor = wrapper.find("a") + + // Then + expect(anchor.props().href).toEqual( + "https://validator.swagger.io/validator/debug?url=swagger.json" + ) + expect(anchor.props().target).toEqual("_blank") + expect(anchor.props().rel || "").toInclude("noopener") + expect(anchor.props().rel || "").toInclude("noreferrer") + }) +}) diff --git a/frontend/web/api-doc/test/unit/xss/info-sanitization.jsx b/frontend/web/api-doc/test/unit/xss/info-sanitization.jsx new file mode 100644 index 0000000..1ce595e --- /dev/null +++ b/frontend/web/api-doc/test/unit/xss/info-sanitization.jsx @@ -0,0 +1,32 @@ +import React from "react" +import { render } from "enzyme" +import { fromJS } from "immutable" +import Info from "components/info" +import Markdown from "components/providers/markdown" + +describe(" Sanitization", function(){ + const dummyComponent = () => null + const components = { + Markdown + } + const props = { + getComponent: c => components[c] || dummyComponent, + info: fromJS({ + title: "Test Title **strong** ", + description: "Description *with* " + }), + host: "example.test", + basePath: "/api", + selectedServer: "https://example.test", + } + + it("renders sanitized .title content", function(){ + let wrapper = render() + expect(wrapper.find(".title").html()).toEqual("Test Title **strong** <script>alert(1)</script>") + }) + + it("renders sanitized .description content", function() { + let wrapper = render() + expect(wrapper.find(".description").html()).toEqual("

Description with

\n
") + }) +}) diff --git a/frontend/web/api-doc/test/unit/xss/markdown-script-sanitization.jsx b/frontend/web/api-doc/test/unit/xss/markdown-script-sanitization.jsx new file mode 100644 index 0000000..c2c4e25 --- /dev/null +++ b/frontend/web/api-doc/test/unit/xss/markdown-script-sanitization.jsx @@ -0,0 +1,46 @@ +import React from "react" +import { render } from "enzyme" +import Markdown from "components/providers/markdown" +import { Markdown as OAS3Markdown } from "corePlugins/oas3/wrap-components/markdown.jsx" + +describe("Markdown Script Sanitization", function() { + describe("Swagger 2.0", function() { + it("sanitizes ` + const el = render() + expect(el.prop("outerHTML")).toEqual(`

script

\n
`) + }) + + it("sanitizes elements", function() { + const str = `` + const el = render() + expect(el.prop("outerHTML")).toEqual(`

\n
`) + }) + + it("sanitizes
elements", function() { + const str = `""` + const el = render() + expect(el.prop("outerHTML")).toEqual(`

"

"

\n
`) + }) + }) + + describe("OAS 3", function() { + it("sanitizes ` + const el = render() + expect(el.prop("outerHTML")).toEqual(`

script

`) + }) + + it("sanitizes elements", function() { + const str = `` + const el = render() + expect(el.prop("outerHTML")).toEqual(`

`) + }) + + it("sanitizes elements", function () { + const str = `""` + const el = render() + expect(el.prop("outerHTML")).toEqual(`

"

"

`) + }) + }) +}) diff --git a/frontend/web/api-doc/webpack/_config-builder.js b/frontend/web/api-doc/webpack/_config-builder.js new file mode 100644 index 0000000..e8d0779 --- /dev/null +++ b/frontend/web/api-doc/webpack/_config-builder.js @@ -0,0 +1,150 @@ +/** + * @prettier + */ + +import path from "path" +import deepExtend from "deep-extend" +import webpack from "webpack" +import TerserPlugin from "terser-webpack-plugin" +import nodeExternals from "webpack-node-externals" + +import { getRepoInfo } from "./_helpers" +import pkg from "../package.json" + +const projectBasePath = path.join(__dirname, "../") + +const baseRules = [ + { + test: /\.jsx?$/, + include: [ + path.join(projectBasePath, "src"), + path.join(projectBasePath, "node_modules", "object-assign-deep"), + ], + loader: "babel-loader", + options: { + retainLines: true, + cacheDirectory: true, + }, + }, + { test: /\.(txt|yaml)$/, + type: "asset/source", + }, + { + test: /\.(png|jpg|jpeg|gif|svg)$/, + type: "asset/inline", + }, +] + +export default function buildConfig( + { + minimize = true, + mangle = true, + sourcemaps = true, + includeDependencies = true, + }, + customConfig +) { + const gitInfo = getRepoInfo() + + var plugins = [ + new webpack.DefinePlugin({ + buildInfo: JSON.stringify({ + PACKAGE_VERSION: pkg.version, + GIT_COMMIT: gitInfo.hash, + GIT_DIRTY: gitInfo.dirty, + BUILD_TIME: new Date().toUTCString(), + }), + }), + new webpack.ProvidePlugin({ + process: "process/browser", + Buffer: ["buffer", "Buffer"], + }), + ] + + const completeConfig = deepExtend( + {}, + { + mode: "production", + + entry: {}, + + output: { + path: path.join(projectBasePath, "dist"), + publicPath: "/dist", + filename: "[name].js", + chunkFilename: "[name].js", + globalObject: "this", + library: { + // when esm, library.name should be unset, so do not define here + // when esm, library.export should be unset, so do not define here + type: "umd", + } + }, + + target: "web", + + + module: { + rules: baseRules, + }, + + externals: includeDependencies + ? { + esprima: "esprima", + } + : [nodeExternals({ + importType: (moduleName) => { + return `commonjs ${moduleName}` + }})], + resolve: { + modules: [path.join(projectBasePath, "./src"), "node_modules"], + extensions: [".web.js", ".js", ".jsx", ".json", ".less"], + alias: { + // these aliases make sure that we don't bundle same libraries twice + // when the versions of these libraries diverge between swagger-js and swagger-ui + "@babel/runtime-corejs3": path.resolve(__dirname, "..", "node_modules/@babel/runtime-corejs3"), + "js-yaml": path.resolve(__dirname, "..", "node_modules/js-yaml"), + "lodash": path.resolve(__dirname, "..", "node_modules/lodash"), + "react-is": path.resolve(__dirname, "..", "node_modules/react-is"), + "safe-buffer": path.resolve(__dirname, "..", "node_modules/safe-buffer"), + }, + fallback: { + fs: false, + stream: require.resolve("stream-browserify"), + }, + }, + + // If we're mangling, size is a concern -- so use trace-only sourcemaps + // Otherwise, provide heavy souremaps suitable for development + devtool: sourcemaps + ? minimize + ? "nosources-source-map" + : "cheap-module-source-map" + : false, + + performance: { + hints: "error", + maxEntrypointSize: 1153434, + maxAssetSize: 1153434, + }, + + optimization: { + minimize: !!minimize, + minimizer: [ + compiler => + new TerserPlugin({ + terserOptions: { + mangle: !!mangle, + }, + }).apply(compiler) + ], + }, + }, + customConfig + ) + + // deepExtend mangles Plugin instances, this doesn't + completeConfig.plugins = plugins.concat(customConfig.plugins || []) + + return completeConfig +} diff --git a/frontend/web/api-doc/webpack/_helpers.js b/frontend/web/api-doc/webpack/_helpers.js new file mode 100644 index 0000000..4d06063 --- /dev/null +++ b/frontend/web/api-doc/webpack/_helpers.js @@ -0,0 +1,17 @@ +/** + * @prettier + */ + +import { gitDescribeSync } from "git-describe" + +export function getRepoInfo() { + try { + return gitDescribeSync(__dirname) + } catch (e) { + console.error(e) + return { + hash: "noGit", + dirty: false, + } + } +} diff --git a/frontend/web/api-doc/webpack/bundle.babel.js b/frontend/web/api-doc/webpack/bundle.babel.js new file mode 100644 index 0000000..12de71f --- /dev/null +++ b/frontend/web/api-doc/webpack/bundle.babel.js @@ -0,0 +1,54 @@ +/** + * @prettier + */ + +/** Dev Note: + * StatsWriterPlugin is disabled by default; uncomment to enable + * when enabled, rebuilding the bundle will cause error for assetSizeLimit, + * which we want to keep out of CI/CD + * post build, cli command: npx webpack-bundle-analyzer + */ + +import configBuilder from "./_config-builder" +import { DuplicatesPlugin } from "inspectpack/plugin" +import { WebpackBundleSizeAnalyzerPlugin } from "webpack-bundle-size-analyzer" +// import path from "path" +// import { StatsWriterPlugin } from "webpack-stats-plugin" + +const result = configBuilder( + { + minimize: true, + mangle: true, + sourcemaps: true, + includeDependencies: true, + }, + { + entry: { + "swagger-ui-bundle": [ + "./src/index.js", + ], + }, + output: { + globalObject: "this", + library: { + name: "SwaggerUIBundle", + export: "default", + }, + }, + plugins: [ + new DuplicatesPlugin({ + // emit compilation warning or error? (Default: `false`) + emitErrors: false, + // display full duplicates information? (Default: `false`) + verbose: false, + }), + new WebpackBundleSizeAnalyzerPlugin("log.bundle-sizes.swagger-ui.txt"), + // new StatsWriterPlugin({ + // filename: path.join("log.bundle-stats.swagger-ui.json"), + // fields: null, + // }), + ], + } +) + +export default result diff --git a/frontend/web/api-doc/webpack/core.babel.js b/frontend/web/api-doc/webpack/core.babel.js new file mode 100644 index 0000000..0ceed8b --- /dev/null +++ b/frontend/web/api-doc/webpack/core.babel.js @@ -0,0 +1,31 @@ +/** + * @prettier + */ + +import configBuilder from "./_config-builder" + +const result = configBuilder( + { + minimize: true, + mangle: true, + sourcemaps: true, + includeDependencies: false, + }, + { + entry: { + "swagger-ui": [ + "./src/index.js", + ], + }, + + output: { + globalObject: "this", + library: { + name: "SwaggerUICore", + export: "default", + }, + }, + } +) + +export default result diff --git a/frontend/web/api-doc/webpack/dev-e2e.babel.js b/frontend/web/api-doc/webpack/dev-e2e.babel.js new file mode 100644 index 0000000..a967866 --- /dev/null +++ b/frontend/web/api-doc/webpack/dev-e2e.babel.js @@ -0,0 +1,76 @@ +/** + * @prettier + */ + +import path from "path" + +import configBuilder from "./_config-builder" +import styleConfig from "./stylesheets.babel" + +// Pretty much the same as devConfig, but with changes to port and static.directory +const devE2eConfig = configBuilder( + { + minimize: false, + mangle: false, + sourcemaps: true, + includeDependencies: true, + }, + { + mode: "development", + entry: { + "swagger-ui-bundle": [ + "./src/core/index.js", + ], + "swagger-ui-standalone-preset": [ + "./src/standalone/index.js", + ], + "swagger-ui": "./src/style/main.scss", + }, + + performance: { + hints: false + }, + + output: { + filename: "[name].js", + chunkFilename: "[id].js", + library: { + name: "[name]", + export: "default", + }, + publicPath: "/", + }, + + devServer: { + allowedHosts: "all", // for development within VMs + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "*", + "Access-Control-Allow-Headers": "*", + }, + port: 3230, + host: "0.0.0.0", + hot: true, + static: { + directory: path.join(__dirname, "../", "test", "e2e-cypress", "static"), + publicPath: "/", + }, + client: { + logging: "info", + progress: true, + }, + devMiddleware: {}, + }, + }, +) + +// mix in the style config's plugins and loader rules + +devE2eConfig.plugins = [...devE2eConfig.plugins, ...styleConfig.plugins] + +devE2eConfig.module.rules = [ + ...devE2eConfig.module.rules, + ...styleConfig.module.rules, +] + +export default devE2eConfig diff --git a/frontend/web/api-doc/webpack/dev.babel.js b/frontend/web/api-doc/webpack/dev.babel.js new file mode 100644 index 0000000..1b49857 --- /dev/null +++ b/frontend/web/api-doc/webpack/dev.babel.js @@ -0,0 +1,121 @@ +/** + * @prettier + */ + +import path from "path" +import ReactRefreshWebpackPlugin from "@pmmmwh/react-refresh-webpack-plugin" +import HtmlWebpackPlugin from "html-webpack-plugin" +import { HtmlWebpackSkipAssetsPlugin } from "html-webpack-skip-assets-plugin" + +import configBuilder from "./_config-builder" +import styleConfig from "./stylesheets.babel" + +const projectBasePath = path.join(__dirname, "../") +const isDevelopment = process.env.NODE_ENV !== "production" + +const devConfig = configBuilder( + { + minimize: false, + mangle: false, + sourcemaps: true, + includeDependencies: true, + }, + { + mode: "development", + entry: { + "swagger-ui-bundle": [ + "./src/core/index.js", + ], + "swagger-ui-standalone-preset": [ + "./src/standalone/index.js", + ], + "swagger-ui": "./src/style/main.scss", + vendors: ["react-refresh/runtime"], + }, + + performance: { + hints: false + }, + + output: { + filename: "[name].js", + chunkFilename: "[id].js", + library: { + name: "[name]", + export: "default", + }, + publicPath: "/", + }, + + devServer: { + allowedHosts: "all", // for development within VMs + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "*", + "Access-Control-Allow-Headers": "*", + }, + port: 3200, + host: "0.0.0.0", + hot: true, + static: { + directory: path.resolve(projectBasePath, "dev-helpers"), + publicPath: "/", + }, + client: { + logging: "info", + progress: true, + }, + }, + + module: { + rules: [ + { + test: /\.jsx?$/, + include: [ + path.join(projectBasePath, "src"), + path.join(projectBasePath, "node_modules", "object-assign-deep"), + ], + loader: "babel-loader", + options: { + retainLines: true, + cacheDirectory: true, + plugins: [isDevelopment && require.resolve("react-refresh/babel")].filter(Boolean), + }, + }, + { + test: /\.(txt|yaml)$/, + type: "asset/source", + }, + { + test: /\.(png|jpg|jpeg|gif|svg)$/, + type: "asset/inline", + }, + ], + }, + + plugins: [ + isDevelopment && new ReactRefreshWebpackPlugin({ library: "[name]" }), + new HtmlWebpackPlugin({ + template: path.join(projectBasePath, "dev-helpers", "index.html"), + }), + new HtmlWebpackSkipAssetsPlugin({ + skipAssets: [/swagger-ui\.js/], + }), + ].filter(Boolean), + + optimization: { + runtimeChunk: "single", // for multiple entry points using ReactRefreshWebpackPlugin + }, + }, +) + +// mix in the style config's plugins and loader rules + +devConfig.plugins = [...devConfig.plugins, ...styleConfig.plugins] + +devConfig.module.rules = [ + ...devConfig.module.rules, + ...styleConfig.module.rules, +] + +export default devConfig diff --git a/frontend/web/api-doc/webpack/es-bundle-core.babel.js b/frontend/web/api-doc/webpack/es-bundle-core.babel.js new file mode 100644 index 0000000..064170e --- /dev/null +++ b/frontend/web/api-doc/webpack/es-bundle-core.babel.js @@ -0,0 +1,87 @@ +/** + * @prettier + */ + + /** Dev Note: + * StatsWriterPlugin is disabled by default; uncomment to enable + * when enabled, rebuilding the bundle will cause error for assetSizeLimit, + * which we want to keep out of CI/CD + * post build, cli command: npx webpack-bundle-analyzer + */ + +import configBuilder from "./_config-builder" +import { DuplicatesPlugin } from "inspectpack/plugin" +import { WebpackBundleSizeAnalyzerPlugin } from "webpack-bundle-size-analyzer" +import nodeExternals from "webpack-node-externals" +// import { StatsWriterPlugin } from "webpack-stats-plugin" + +const minimize = true +const sourcemaps = true + +const result = configBuilder( + { + minimize, + mangle: true, + sourcemaps, + includeDependencies: false, + }, + { + entry: { + "swagger-ui-es-bundle-core": [ + "./src/index.js", + ], + }, + experiments: { + outputModule: true, + }, + output: { + module: true, + libraryTarget: "module", + library: { + type: "module", + }, + environment: { + module: true, + }, + }, + devtool: sourcemaps && minimize ? "source-map" : false, + externalsType: "module", + externals: [ + { + esprima: "esprima", + }, + nodeExternals({ + allowlist: [ + /object\/define-property/, // @babel/runtime-corejs3 import which makes fragment work with Jest + "deep-extend", // uses Buffer as global symbol + "randombytes", // uses require('safe-buffer') + "sha.js", // uses require('safe-buffer') + "xml", // uses require('stream') + /process\/browser/, // is injected via ProvidePlugin + /readable-stream/, // byproduct of buffer ProvidePlugin injection + "util-deprecate", // dependency of readable-stream + "inherits", // dependency of readable-stream + "events", // dependency of readable-stream + /safe-buffer/, // contained in resolve.alias + /string_decoder/, // byproduct of buffer ProvidePlugin injection + "buffer", // buffer is injected via ProvidePlugin + ], + importType: (moduleName) => { + return `module ${moduleName}` + }}) + ], + plugins: [ + new DuplicatesPlugin({ + emitErrors: false, + verbose: false, + }), + new WebpackBundleSizeAnalyzerPlugin("log.es-bundle-core-sizes.swagger-ui.txt"), + // new StatsWriterPlugin({ + // filename: path.join("log.es-bundle-core-stats.swagger-ui.json"), + // fields: null, + // }), + ] + } +) + +export default result diff --git a/frontend/web/api-doc/webpack/es-bundle.babel.js b/frontend/web/api-doc/webpack/es-bundle.babel.js new file mode 100644 index 0000000..d24a95d --- /dev/null +++ b/frontend/web/api-doc/webpack/es-bundle.babel.js @@ -0,0 +1,54 @@ +/** + * @prettier + */ + +/** Dev Note: + * StatsWriterPlugin is disabled by default; uncomment to enable + * when enabled, rebuilding the bundle will cause error for assetSizeLimit, + * which we want to keep out of CI/CD + * post build, cli command: npx webpack-bundle-analyzer + */ + +import configBuilder from "./_config-builder" +import { DuplicatesPlugin } from "inspectpack/plugin" +import { WebpackBundleSizeAnalyzerPlugin } from "webpack-bundle-size-analyzer" +// import path from "path" +// import { StatsWriterPlugin } from "webpack-stats-plugin" + +const result = configBuilder( + { + minimize: true, + mangle: true, + sourcemaps: true, + includeDependencies: true, + }, + { + entry: { + "swagger-ui-es-bundle": [ + "./src/index.js", + ], + }, + output: { + globalObject: "this", + library: { + type: "commonjs2", + export: "default", + }, + }, + plugins: [ + new DuplicatesPlugin({ + // emit compilation warning or error? (Default: `false`) + emitErrors: false, + // display full duplicates information? (Default: `false`) + verbose: false, + }), + new WebpackBundleSizeAnalyzerPlugin("log.es-bundle-sizes.swagger-ui.txt"), + // new StatsWriterPlugin({ + // filename: path.join("log.es-bundle-stats.swagger-ui.json"), + // fields: null, + // }), + ] + } +) + +export default result diff --git a/frontend/web/api-doc/webpack/standalone.babel.js b/frontend/web/api-doc/webpack/standalone.babel.js new file mode 100644 index 0000000..a1998a1 --- /dev/null +++ b/frontend/web/api-doc/webpack/standalone.babel.js @@ -0,0 +1,28 @@ +/** + * @prettier + */ + +import configBuilder from "./_config-builder" + +const result = configBuilder( + { + minimize: true, + mangle: true, + sourcemaps: true, + }, + { + entry: { + "swagger-ui-standalone-preset": ["./src/standalone/index.js"], + }, + + output: { + globalObject: "this", + library: { + name: "SwaggerUIStandalonePreset", + export: "default", + }, + }, + } +) + +export default result diff --git a/frontend/web/api-doc/webpack/stylesheets.babel.js b/frontend/web/api-doc/webpack/stylesheets.babel.js new file mode 100644 index 0000000..ac5e8cb --- /dev/null +++ b/frontend/web/api-doc/webpack/stylesheets.babel.js @@ -0,0 +1,86 @@ +/** + * @prettier + */ + +// NOTE: this config *does not* inherit from `_config-builder`. +// It is also used in the dev config. + +import path from "path" +import MiniCssExtractPlugin from "mini-css-extract-plugin" + +export default { + mode: "production", + + entry: { + "swagger-ui": "./src/style/main.scss", + }, + + module: { + rules: [ + { + test: [/\.(scss)(\?.*)?$/], + use: [ + { + loader: MiniCssExtractPlugin.loader, + }, + { + loader: "css-loader", + options: { sourceMap: true }, + }, + { + loader: "postcss-loader", + options: { + postcssOptions: { + sourceMap: true, + plugins: [ + require("cssnano")(), + "postcss-preset-env" // applies autoprefixer + ], + + } + }, + }, + { + loader: "sass-loader", + options: { + // Prefer `dart-sass` + implementation: require("sass"), + sourceMap: true, + sassOptions: { + // `fibers` package is not compatible with Node.js v16.0.0 or later + fiber: false, // disable auto attempt to load `fiber` + // sourceMapContents: "true", // if sourceMap: true, sassOptions.sourceMapContents is ignored + }, + }, + }, + ], + }, + ], + }, + + plugins: [ + new MiniCssExtractPlugin({ + filename: "[name].css", + }), + ], + + devtool: "source-map", + + output: { + path: path.join(__dirname, "../", "dist"), + publicPath: "/dist", + }, + + optimization: { + splitChunks: { + cacheGroups: { + styles: { + name: "styles", + test: /\.css$/, + chunks: "all", + enforce: true, + }, + }, + }, + }, +} diff --git a/frontend/web/js/company_select.js b/frontend/web/js/company_select.js new file mode 100644 index 0000000..af35879 --- /dev/null +++ b/frontend/web/js/company_select.js @@ -0,0 +1,9 @@ +$(document).ready(function (){ + $('#company_select').on("change", function(){ + $.ajax({ + url: "/product_category/product-category/get-category-select-by-company-id?company_id=" + $(this).val() + }).done(function(html) { + $('#category_select_container').html(html) + }); + }) +}) \ No newline at end of file