From 4c3b4aa589e3d81cc5801f8fb7d4b6531b77d1c7 Mon Sep 17 00:00:00 2001 From: Roman Hotsiy Date: Thu, 12 Oct 2017 00:01:37 +0300 Subject: [PATCH] Initial React rewrite --- .editorconfig | 38 +- .gitignore | 14 +- .npmignore | 12 +- .vscode/settings.json | 3 + CHANGELOG.md | 9 - README.md | 9 + bower.json | 38 - build/helpers.js | 9 - build/join-module-css.js | 15 - build/paths.js | 12 - build/prepare_deploy.js | 28 - build/resource-override.js | 0 build/run_tests.js | 33 - build/webpack.common.js | 136 - build/webpack.dev.js | 50 - build/webpack.prod.js | 83 - build/webpack.test.js | 61 - custom.d.ts | 27 +- demo/big-swagger.json | 27362 ++++++++++++++++ demo/examples/multiple-apis/index.html | 79 - demo/index-gh.html | 39 - demo/index.html | 49 +- demo/main.css | 133 - demo/main.js | 74 - demo/petstore-logo.png | Bin 9002 -> 0 bytes demo/redoc-demo.png | Bin 202376 -> 0 bytes demo/swagger.yaml | 1021 +- empty.js | 1 + karma.conf.js | 53 - lib/app.module.ts | 13 - lib/bootstrap.dev.ts | 7 - lib/bootstrap.ts | 8 - lib/components/ApiInfo/api-info.html | 24 - lib/components/ApiInfo/api-info.scss | 33 - lib/components/ApiInfo/api-info.spec.ts | 66 - lib/components/ApiInfo/api-info.ts | 44 - lib/components/ApiLogo/api-logo.html | 4 - lib/components/ApiLogo/api-logo.scss | 19 - lib/components/ApiLogo/api-logo.spec.ts | 73 - lib/components/ApiLogo/api-logo.ts | 30 - .../EndpointLink/endpoint-link.html | 17 - .../EndpointLink/endpoint-link.scss | 153 - .../EndpointLink/endpoint-link.spec.ts | 82 - lib/components/EndpointLink/endpoint-link.ts | 63 - lib/components/ExternalDocs/external-docs.ts | 18 - .../JsonSchema/_json-schema-common.scss | 260 - .../JsonSchema/json-schema-lazy.spec.ts | 55 - lib/components/JsonSchema/json-schema-lazy.ts | 100 - lib/components/JsonSchema/json-schema.html | 107 - lib/components/JsonSchema/json-schema.scss | 221 - lib/components/JsonSchema/json-schema.spec.ts | 67 - lib/components/JsonSchema/json-schema.ts | 203 - lib/components/LoadingBar/loading-bar.scss | 21 - lib/components/LoadingBar/loading-bar.spec.ts | 58 - lib/components/LoadingBar/loading-bar.ts | 22 - lib/components/Operation/operation.html | 32 - lib/components/Operation/operation.scss | 148 - lib/components/Operation/operation.spec.ts | 65 - lib/components/Operation/operation.ts | 89 - .../OperationsList/operations-list.html | 12 - .../OperationsList/operations-list.scss | 37 - .../OperationsList/operations-list.spec.ts | 62 - .../OperationsList/operations-list.ts | 57 - lib/components/ParamsList/params-list.html | 53 - lib/components/ParamsList/params-list.scss | 99 - lib/components/ParamsList/params-list.ts | 88 - .../Redoc/redoc-initial-styles.scss | 56 - lib/components/Redoc/redoc.html | 30 - lib/components/Redoc/redoc.scss | 362 - lib/components/Redoc/redoc.spec.ts | 60 - lib/components/Redoc/redoc.ts | 162 - .../RequestSamples/request-samples.html | 15 - .../RequestSamples/request-samples.scss | 81 - .../RequestSamples/request-samples.ts | 60 - .../ResponsesList/responses-list.html | 26 - .../ResponsesList/responses-list.scss | 61 - .../ResponsesList/responses-list.spec.ts | 64 - .../ResponsesList/responses-list.ts | 108 - .../ResponsesSamples/responses-samples.html | 7 - .../ResponsesSamples/responses-samples.scss | 40 - .../ResponsesSamples/responses-samples.ts | 66 - .../SchemaSample/schema-sample.html | 34 - .../SchemaSample/schema-sample.scss | 195 - lib/components/SchemaSample/schema-sample.ts | 156 - lib/components/Search/redoc-search.html | 15 - lib/components/Search/redoc-search.scss | 85 - lib/components/Search/redoc-search.ts | 93 - .../security-definitions.html | 38 - .../security-definitions.scss | 36 - .../security-definitions.ts | 50 - lib/components/SideMenu/side-menu-items.html | 13 - lib/components/SideMenu/side-menu-items.scss | 134 - lib/components/SideMenu/side-menu.html | 17 - lib/components/SideMenu/side-menu.scss | 90 - lib/components/SideMenu/side-menu.spec.ts | 96 - lib/components/SideMenu/side-menu.ts | 163 - lib/components/Warnings/warnings.html | 4 - lib/components/Warnings/warnings.scss | 33 - lib/components/Warnings/warnings.ts | 36 - lib/components/base.spec.ts | 41 - lib/components/base.ts | 84 - lib/components/index.ts | 32 - lib/index.ts | 60 - lib/polyfills.ts | 30 - lib/redoc.module.ts | 65 - lib/services/app-state.service.ts | 24 - lib/services/clipboard.service.spec.ts | 56 - lib/services/component-parser.service.ts | 88 - lib/services/content-projector.service.ts | 43 - lib/services/hash.service.spec.ts | 21 - lib/services/hash.service.ts | 52 - lib/services/index.ts | 15 - lib/services/marker.service.ts | 87 - lib/services/menu.service.spec.ts | 193 - lib/services/menu.service.ts | 488 - lib/services/options.service.spec.ts | 65 - lib/services/options.service.ts | 122 - lib/services/schema-helper.service.spec.ts | 134 - lib/services/schema-helper.service.ts | 349 - .../schema-normalizer.service.spec.ts | 257 - lib/services/schema-normalizer.service.ts | 229 - lib/services/scroll.service.ts | 106 - lib/services/search.service.ts | 232 - lib/services/warnings.service.ts | 20 - .../CopyButton/copy-button.directive.ts | 55 - lib/shared/components/DropDown/drop-down.html | 3 - lib/shared/components/DropDown/drop-down.scss | 74 - lib/shared/components/DropDown/drop-down.ts | 37 - .../dynamic-ng2-viewer.component.ts | 45 - lib/shared/components/LazyFor/lazy-for.ts | 172 - .../PerfectScrollbar/perfect-scrollbar.ts | 49 - .../select-on-click.directive.ts | 17 - .../StickySidebar/sticky-sidebar.spec.ts | 91 - .../StickySidebar/sticky-sidebar.ts | 100 - lib/shared/components/Tabs/tab.html | 3 - lib/shared/components/Tabs/tab.scss | 10 - lib/shared/components/Tabs/tabs.html | 5 - lib/shared/components/Tabs/tabs.scss | 54 - lib/shared/components/Tabs/tabs.spec.ts | 151 - lib/shared/components/Tabs/tabs.ts | 70 - lib/shared/components/Zippy/zippy.html | 13 - lib/shared/components/Zippy/zippy.scss | 119 - lib/shared/components/Zippy/zippy.spec.ts | 111 - lib/shared/components/Zippy/zippy.ts | 29 - lib/shared/components/index.ts | 17 - lib/shared/styles/_share-link.scss | 23 - lib/shared/styles/_variables.scss | 83 - lib/utils/browser-adapter.ts | 53 - lib/utils/custom-error-handler.ts | 13 - lib/utils/helpers.ts | 183 - lib/utils/index.ts | 6 - lib/utils/md-renderer.spec.ts | 24 - lib/utils/md-renderer.ts | 158 - lib/utils/pipes.ts | 131 - lib/utils/spec-manager.ts | 281 - lib/utils/swagger-defs.ts | 26 - lib/utils/swagger-typings.ts | 24 - lib/vendor.ts | 57 - package.json | 226 +- perf/index.tsx | 23 + perf/mount-time.js | 54 + protractor.conf.js | 75 - src/common-elements/dropdown.ts | 112 + src/common-elements/fields-layout.ts | 156 + src/common-elements/fields.ts | 74 + src/common-elements/headers.ts | 33 + src/common-elements/index.ts | 9 + src/common-elements/linkify.ts | 31 + src/common-elements/mixins.ts | 15 + src/common-elements/panels.ts | 13 + src/common-elements/perfect-scrollbar.ts | 8 + src/common-elements/schema.ts | 56 + src/common-elements/shelfs.tsx | 57 + src/common-elements/tabs.ts | 88 + src/components/ApiInfo/ApiInfo.tsx | 107 + src/components/ApiInfo/ApiInfoContainer.tsx | 19 + src/components/ApiInfo/styled.elements.ts | 47 + src/components/ApiLogo/ApiLogo.tsx | 28 + src/components/ApiLogo/styled.elements.ts | 8 + .../ContentItems/ContentContainer.tsx | 19 + src/components/ContentItems/ContentItems.tsx | 81 + src/components/ContentRoot/ContentRoot.tsx | 27 + src/components/ContentRoot/elements.tsx | 69 + .../DropdownOrLabel/DropdownOrLabel.tsx | 16 + src/components/Endpoint/Endpoint.tsx | 68 + src/components/Endpoint/styled.elements.ts | 72 + src/components/ErrorBoundary.tsx | 32 + src/components/Fields/EnumValues.tsx | 23 + src/components/Fields/Field.tsx | 56 + src/components/Fields/FieldContstraints.tsx | 20 + src/components/Fields/FieldDetail.tsx | 19 + src/components/Fields/FieldDetails.tsx | 58 + src/components/JsonViewer/JsonViewer.tsx | 49 + src/components/JsonViewer/style.ts | 111 + src/components/LoadingWrap/LoadingWrap.tsx | 37 + src/components/LoadingWrap/Spinner.svg.tsx | 36 + src/components/Markdown/Markdown.tsx | 65 + src/components/Markdown/styles.ts | 116 + .../MediaTypeSwitch/MediaTypesSwitch.tsx | 47 + src/components/Operation/Operation.tsx | 58 + src/components/Parameters/Parameters.tsx | 73 + src/components/Parameters/ParametersGroup.tsx | 34 + .../PayloadSamples/MediaTypeSamples.tsx | 44 + .../PayloadSamples/PayloadSamples.tsx | 30 + .../PayloadSamples/styled.elements.ts | 35 + src/components/Redoc.tsx | 33 + .../RequestSamples/RequestSamples.tsx | 52 + .../ResponseSamples/ResponseSamples.tsx | 65 + src/components/Responses/Response.tsx | 57 + src/components/Responses/ResponseHeaders.tsx | 30 + src/components/Responses/ResponseTitle.tsx | 34 + src/components/Responses/ResponsesList.tsx | 31 + src/components/Responses/styled.elements.ts | 40 + src/components/Schema/ArraySchema.tsx | 18 + .../Schema/DiscriminatorDropdown.tsx | 54 + src/components/Schema/ObjectSchema.tsx | 80 + src/components/Schema/OneOfSchema.tsx | 33 + src/components/Schema/Schema.tsx | 74 + .../__tests__/DiscriminatorDropdown.test.tsx | 47 + .../DiscriminatorDropdown.test.tsx.snap | 253 + .../fixtures/simple-discriminator.json | 46 + src/components/Schema/index.ts | 1 + src/components/SecurityDefs/SecurityDefs.tsx | 7 + .../SelectOnClick/SelectOnClick.tsx | 18 + src/components/SideMenu/MenuItem.tsx | 60 + src/components/SideMenu/MenuItems.tsx | 28 + src/components/SideMenu/SideMenu.tsx | 30 + src/components/SideMenu/styled.elements.ts | 130 + src/components/SourceCode/SourceCode.tsx | 22 + src/components/StoreProvider.ts | 49 + src/components/index.ts | 1 + src/hmr-playground.tsx | 42 + src/index.ts | 2 + src/services/AppStore.ts | 55 + .../services/ClipboardService.ts | 30 +- src/services/HistoryService.ts | 64 + src/services/MarkdownRenderer.ts | 188 + src/services/MenuBuilder.ts | 195 + src/services/MenuStore.ts | 235 + src/services/OpenAPIParser.ts | 242 + src/services/ScrollService.ts | 71 + src/services/SpecStore.ts | 51 + .../__snapshots__/prism.test.ts.snap | 3 + .../__tests__/history.service.test.ts | 25 + src/services/__tests__/prism.test.ts | 19 + src/services/index.ts | 9 + src/services/models/ApiInfo.ts | 33 + src/services/models/Example.ts | 14 + src/services/models/Field.ts | 42 + src/services/models/Group.model.ts | 57 + src/services/models/MediaContent.ts | 49 + src/services/models/MediaType.ts | 59 + src/services/models/Operation.ts | 132 + src/services/models/RequestBody.ts | 20 + src/services/models/Response.ts | 42 + src/services/models/Schema.ts | 221 + src/services/models/index.ts | 12 + src/services/models/types.ts | 4 + src/styled-components.ts | 26 + src/theme.ts | 60 + src/types/components.ts | 5 + src/types/index.ts | 5 + src/types/open-api.ts | 227 + {lib => src}/utils/JsonPointer.ts | 80 +- src/utils/__tests__/helpers.test.ts | 23 + src/utils/__tests__/openapi.test.ts | 186 + src/utils/__tests__/styled.test.ts | 19 + src/utils/helpers.ts | 65 + src/utils/highlight.ts | 55 + src/utils/index.ts | 6 + .../utils/jsonToHtml.ts | 67 +- src/utils/openapi.ts | 163 + src/utils/styled.ts | 16 + tests/e2e/.gitignore | 1 - tests/e2e/favicon.ico | Bin 318 -> 0 bytes tests/e2e/helpers.js | 113 - tests/e2e/index.html | 23 - tests/e2e/redoc.e2e.js | 132 - tests/helpers.ts | 34 - tests/schemas/api-info-test.json | 30 - tests/schemas/base-component-dereference.json | 118 - tests/schemas/base-component-joinallof.json | 119 - tests/schemas/extended-petstore.yml | 855 - tests/schemas/operations-list-component.json | 28 - tests/schemas/responses-list-component.json | 59 - tests/schemas/schema-mgr-operationParams.json | 68 - tests/spec-bundle.js | 78 - tests/unit/JsonPointer.spec.ts | 27 - tests/unit/SpecManager.spec.ts | 192 - tests/unit/helpers.spec.ts | 91 - tests/unit/pipes.spec.js | 63 - tests/unit/x-extended-defs.json | 57 - tsconfig.aot.json | 37 - tsconfig.json | 42 +- tslint.json | 57 - webpack.config.js | 91 + yarn.lock | 5733 ++-- 297 files changed, 37697 insertions(+), 16661 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 bower.json delete mode 100644 build/helpers.js delete mode 100644 build/join-module-css.js delete mode 100644 build/paths.js delete mode 100755 build/prepare_deploy.js delete mode 100644 build/resource-override.js delete mode 100755 build/run_tests.js delete mode 100644 build/webpack.common.js delete mode 100644 build/webpack.dev.js delete mode 100644 build/webpack.prod.js delete mode 100644 build/webpack.test.js create mode 100644 demo/big-swagger.json delete mode 100644 demo/examples/multiple-apis/index.html delete mode 100644 demo/index-gh.html delete mode 100644 demo/main.css delete mode 100644 demo/main.js delete mode 100644 demo/petstore-logo.png delete mode 100644 demo/redoc-demo.png create mode 100644 empty.js delete mode 100644 karma.conf.js delete mode 100644 lib/app.module.ts delete mode 100644 lib/bootstrap.dev.ts delete mode 100644 lib/bootstrap.ts delete mode 100644 lib/components/ApiInfo/api-info.html delete mode 100644 lib/components/ApiInfo/api-info.scss delete mode 100644 lib/components/ApiInfo/api-info.spec.ts delete mode 100644 lib/components/ApiInfo/api-info.ts delete mode 100644 lib/components/ApiLogo/api-logo.html delete mode 100644 lib/components/ApiLogo/api-logo.scss delete mode 100644 lib/components/ApiLogo/api-logo.spec.ts delete mode 100644 lib/components/ApiLogo/api-logo.ts delete mode 100644 lib/components/EndpointLink/endpoint-link.html delete mode 100644 lib/components/EndpointLink/endpoint-link.scss delete mode 100644 lib/components/EndpointLink/endpoint-link.spec.ts delete mode 100644 lib/components/EndpointLink/endpoint-link.ts delete mode 100644 lib/components/ExternalDocs/external-docs.ts delete mode 100644 lib/components/JsonSchema/_json-schema-common.scss delete mode 100644 lib/components/JsonSchema/json-schema-lazy.spec.ts delete mode 100644 lib/components/JsonSchema/json-schema-lazy.ts delete mode 100644 lib/components/JsonSchema/json-schema.html delete mode 100644 lib/components/JsonSchema/json-schema.scss delete mode 100644 lib/components/JsonSchema/json-schema.spec.ts delete mode 100644 lib/components/JsonSchema/json-schema.ts delete mode 100644 lib/components/LoadingBar/loading-bar.scss delete mode 100644 lib/components/LoadingBar/loading-bar.spec.ts delete mode 100644 lib/components/LoadingBar/loading-bar.ts delete mode 100644 lib/components/Operation/operation.html delete mode 100644 lib/components/Operation/operation.scss delete mode 100644 lib/components/Operation/operation.spec.ts delete mode 100644 lib/components/Operation/operation.ts delete mode 100644 lib/components/OperationsList/operations-list.html delete mode 100644 lib/components/OperationsList/operations-list.scss delete mode 100644 lib/components/OperationsList/operations-list.spec.ts delete mode 100644 lib/components/OperationsList/operations-list.ts delete mode 100644 lib/components/ParamsList/params-list.html delete mode 100644 lib/components/ParamsList/params-list.scss delete mode 100644 lib/components/ParamsList/params-list.ts delete mode 100644 lib/components/Redoc/redoc-initial-styles.scss delete mode 100644 lib/components/Redoc/redoc.html delete mode 100644 lib/components/Redoc/redoc.scss delete mode 100644 lib/components/Redoc/redoc.spec.ts delete mode 100644 lib/components/Redoc/redoc.ts delete mode 100644 lib/components/RequestSamples/request-samples.html delete mode 100644 lib/components/RequestSamples/request-samples.scss delete mode 100644 lib/components/RequestSamples/request-samples.ts delete mode 100644 lib/components/ResponsesList/responses-list.html delete mode 100644 lib/components/ResponsesList/responses-list.scss delete mode 100644 lib/components/ResponsesList/responses-list.spec.ts delete mode 100644 lib/components/ResponsesList/responses-list.ts delete mode 100644 lib/components/ResponsesSamples/responses-samples.html delete mode 100644 lib/components/ResponsesSamples/responses-samples.scss delete mode 100644 lib/components/ResponsesSamples/responses-samples.ts delete mode 100644 lib/components/SchemaSample/schema-sample.html delete mode 100644 lib/components/SchemaSample/schema-sample.scss delete mode 100644 lib/components/SchemaSample/schema-sample.ts delete mode 100644 lib/components/Search/redoc-search.html delete mode 100644 lib/components/Search/redoc-search.scss delete mode 100644 lib/components/Search/redoc-search.ts delete mode 100644 lib/components/SecurityDefinitions/security-definitions.html delete mode 100644 lib/components/SecurityDefinitions/security-definitions.scss delete mode 100644 lib/components/SecurityDefinitions/security-definitions.ts delete mode 100644 lib/components/SideMenu/side-menu-items.html delete mode 100644 lib/components/SideMenu/side-menu-items.scss delete mode 100644 lib/components/SideMenu/side-menu.html delete mode 100644 lib/components/SideMenu/side-menu.scss delete mode 100644 lib/components/SideMenu/side-menu.spec.ts delete mode 100644 lib/components/SideMenu/side-menu.ts delete mode 100644 lib/components/Warnings/warnings.html delete mode 100644 lib/components/Warnings/warnings.scss delete mode 100644 lib/components/Warnings/warnings.ts delete mode 100644 lib/components/base.spec.ts delete mode 100644 lib/components/base.ts delete mode 100644 lib/components/index.ts delete mode 100644 lib/index.ts delete mode 100644 lib/polyfills.ts delete mode 100644 lib/redoc.module.ts delete mode 100644 lib/services/app-state.service.ts delete mode 100644 lib/services/clipboard.service.spec.ts delete mode 100644 lib/services/component-parser.service.ts delete mode 100644 lib/services/content-projector.service.ts delete mode 100644 lib/services/hash.service.spec.ts delete mode 100644 lib/services/hash.service.ts delete mode 100644 lib/services/index.ts delete mode 100644 lib/services/marker.service.ts delete mode 100644 lib/services/menu.service.spec.ts delete mode 100644 lib/services/menu.service.ts delete mode 100644 lib/services/options.service.spec.ts delete mode 100644 lib/services/options.service.ts delete mode 100644 lib/services/schema-helper.service.spec.ts delete mode 100644 lib/services/schema-helper.service.ts delete mode 100644 lib/services/schema-normalizer.service.spec.ts delete mode 100644 lib/services/schema-normalizer.service.ts delete mode 100644 lib/services/scroll.service.ts delete mode 100644 lib/services/search.service.ts delete mode 100644 lib/services/warnings.service.ts delete mode 100644 lib/shared/components/CopyButton/copy-button.directive.ts delete mode 100644 lib/shared/components/DropDown/drop-down.html delete mode 100644 lib/shared/components/DropDown/drop-down.scss delete mode 100644 lib/shared/components/DropDown/drop-down.ts delete mode 100644 lib/shared/components/DynamicNg2Viewer/dynamic-ng2-viewer.component.ts delete mode 100644 lib/shared/components/LazyFor/lazy-for.ts delete mode 100644 lib/shared/components/PerfectScrollbar/perfect-scrollbar.ts delete mode 100644 lib/shared/components/SelectOnClick/select-on-click.directive.ts delete mode 100644 lib/shared/components/StickySidebar/sticky-sidebar.spec.ts delete mode 100644 lib/shared/components/StickySidebar/sticky-sidebar.ts delete mode 100644 lib/shared/components/Tabs/tab.html delete mode 100644 lib/shared/components/Tabs/tab.scss delete mode 100644 lib/shared/components/Tabs/tabs.html delete mode 100644 lib/shared/components/Tabs/tabs.scss delete mode 100644 lib/shared/components/Tabs/tabs.spec.ts delete mode 100644 lib/shared/components/Tabs/tabs.ts delete mode 100644 lib/shared/components/Zippy/zippy.html delete mode 100644 lib/shared/components/Zippy/zippy.scss delete mode 100644 lib/shared/components/Zippy/zippy.spec.ts delete mode 100644 lib/shared/components/Zippy/zippy.ts delete mode 100644 lib/shared/components/index.ts delete mode 100644 lib/shared/styles/_share-link.scss delete mode 100644 lib/shared/styles/_variables.scss delete mode 100644 lib/utils/browser-adapter.ts delete mode 100644 lib/utils/custom-error-handler.ts delete mode 100644 lib/utils/helpers.ts delete mode 100644 lib/utils/index.ts delete mode 100644 lib/utils/md-renderer.spec.ts delete mode 100644 lib/utils/md-renderer.ts delete mode 100644 lib/utils/pipes.ts delete mode 100644 lib/utils/spec-manager.ts delete mode 100644 lib/utils/swagger-defs.ts delete mode 100644 lib/utils/swagger-typings.ts delete mode 100644 lib/vendor.ts create mode 100644 perf/index.tsx create mode 100644 perf/mount-time.js delete mode 100644 protractor.conf.js create mode 100644 src/common-elements/dropdown.ts create mode 100644 src/common-elements/fields-layout.ts create mode 100644 src/common-elements/fields.ts create mode 100644 src/common-elements/headers.ts create mode 100644 src/common-elements/index.ts create mode 100644 src/common-elements/linkify.ts create mode 100644 src/common-elements/mixins.ts create mode 100644 src/common-elements/panels.ts create mode 100644 src/common-elements/perfect-scrollbar.ts create mode 100644 src/common-elements/schema.ts create mode 100644 src/common-elements/shelfs.tsx create mode 100644 src/common-elements/tabs.ts create mode 100644 src/components/ApiInfo/ApiInfo.tsx create mode 100644 src/components/ApiInfo/ApiInfoContainer.tsx create mode 100644 src/components/ApiInfo/styled.elements.ts create mode 100644 src/components/ApiLogo/ApiLogo.tsx create mode 100644 src/components/ApiLogo/styled.elements.ts create mode 100644 src/components/ContentItems/ContentContainer.tsx create mode 100644 src/components/ContentItems/ContentItems.tsx create mode 100644 src/components/ContentRoot/ContentRoot.tsx create mode 100644 src/components/ContentRoot/elements.tsx create mode 100644 src/components/DropdownOrLabel/DropdownOrLabel.tsx create mode 100644 src/components/Endpoint/Endpoint.tsx create mode 100644 src/components/Endpoint/styled.elements.ts create mode 100644 src/components/ErrorBoundary.tsx create mode 100644 src/components/Fields/EnumValues.tsx create mode 100644 src/components/Fields/Field.tsx create mode 100644 src/components/Fields/FieldContstraints.tsx create mode 100644 src/components/Fields/FieldDetail.tsx create mode 100644 src/components/Fields/FieldDetails.tsx create mode 100644 src/components/JsonViewer/JsonViewer.tsx create mode 100644 src/components/JsonViewer/style.ts create mode 100644 src/components/LoadingWrap/LoadingWrap.tsx create mode 100644 src/components/LoadingWrap/Spinner.svg.tsx create mode 100644 src/components/Markdown/Markdown.tsx create mode 100644 src/components/Markdown/styles.ts create mode 100644 src/components/MediaTypeSwitch/MediaTypesSwitch.tsx create mode 100644 src/components/Operation/Operation.tsx create mode 100644 src/components/Parameters/Parameters.tsx create mode 100644 src/components/Parameters/ParametersGroup.tsx create mode 100644 src/components/PayloadSamples/MediaTypeSamples.tsx create mode 100644 src/components/PayloadSamples/PayloadSamples.tsx create mode 100644 src/components/PayloadSamples/styled.elements.ts create mode 100644 src/components/Redoc.tsx create mode 100644 src/components/RequestSamples/RequestSamples.tsx create mode 100644 src/components/ResponseSamples/ResponseSamples.tsx create mode 100644 src/components/Responses/Response.tsx create mode 100644 src/components/Responses/ResponseHeaders.tsx create mode 100644 src/components/Responses/ResponseTitle.tsx create mode 100644 src/components/Responses/ResponsesList.tsx create mode 100644 src/components/Responses/styled.elements.ts create mode 100644 src/components/Schema/ArraySchema.tsx create mode 100644 src/components/Schema/DiscriminatorDropdown.tsx create mode 100644 src/components/Schema/ObjectSchema.tsx create mode 100644 src/components/Schema/OneOfSchema.tsx create mode 100644 src/components/Schema/Schema.tsx create mode 100644 src/components/Schema/__tests__/DiscriminatorDropdown.test.tsx create mode 100644 src/components/Schema/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap create mode 100644 src/components/Schema/__tests__/fixtures/simple-discriminator.json create mode 100644 src/components/Schema/index.ts create mode 100644 src/components/SecurityDefs/SecurityDefs.tsx create mode 100644 src/components/SelectOnClick/SelectOnClick.tsx create mode 100644 src/components/SideMenu/MenuItem.tsx create mode 100644 src/components/SideMenu/MenuItems.tsx create mode 100644 src/components/SideMenu/SideMenu.tsx create mode 100644 src/components/SideMenu/styled.elements.ts create mode 100644 src/components/SourceCode/SourceCode.tsx create mode 100644 src/components/StoreProvider.ts create mode 100644 src/components/index.ts create mode 100644 src/hmr-playground.tsx create mode 100644 src/index.ts create mode 100644 src/services/AppStore.ts rename lib/services/clipboard.service.ts => src/services/ClipboardService.ts (72%) create mode 100644 src/services/HistoryService.ts create mode 100644 src/services/MarkdownRenderer.ts create mode 100644 src/services/MenuBuilder.ts create mode 100644 src/services/MenuStore.ts create mode 100644 src/services/OpenAPIParser.ts create mode 100644 src/services/ScrollService.ts create mode 100644 src/services/SpecStore.ts create mode 100644 src/services/__tests__/__snapshots__/prism.test.ts.snap create mode 100644 src/services/__tests__/history.service.test.ts create mode 100644 src/services/__tests__/prism.test.ts create mode 100644 src/services/index.ts create mode 100644 src/services/models/ApiInfo.ts create mode 100644 src/services/models/Example.ts create mode 100644 src/services/models/Field.ts create mode 100644 src/services/models/Group.model.ts create mode 100644 src/services/models/MediaContent.ts create mode 100644 src/services/models/MediaType.ts create mode 100644 src/services/models/Operation.ts create mode 100644 src/services/models/RequestBody.ts create mode 100644 src/services/models/Response.ts create mode 100644 src/services/models/Schema.ts create mode 100644 src/services/models/index.ts create mode 100644 src/services/models/types.ts create mode 100644 src/styled-components.ts create mode 100644 src/theme.ts create mode 100644 src/types/components.ts create mode 100644 src/types/index.ts create mode 100644 src/types/open-api.ts rename {lib => src}/utils/JsonPointer.ts (54%) create mode 100644 src/utils/__tests__/helpers.test.ts create mode 100644 src/utils/__tests__/openapi.test.ts create mode 100644 src/utils/__tests__/styled.test.ts create mode 100644 src/utils/helpers.ts create mode 100644 src/utils/highlight.ts create mode 100644 src/utils/index.ts rename lib/utils/JsonFormatterPipe.ts => src/utils/jsonToHtml.ts (65%) create mode 100644 src/utils/openapi.ts create mode 100644 src/utils/styled.ts delete mode 100644 tests/e2e/.gitignore delete mode 100644 tests/e2e/favicon.ico delete mode 100644 tests/e2e/helpers.js delete mode 100644 tests/e2e/index.html delete mode 100644 tests/e2e/redoc.e2e.js delete mode 100644 tests/helpers.ts delete mode 100644 tests/schemas/api-info-test.json delete mode 100644 tests/schemas/base-component-dereference.json delete mode 100644 tests/schemas/base-component-joinallof.json delete mode 100644 tests/schemas/extended-petstore.yml delete mode 100644 tests/schemas/operations-list-component.json delete mode 100644 tests/schemas/responses-list-component.json delete mode 100644 tests/schemas/schema-mgr-operationParams.json delete mode 100644 tests/spec-bundle.js delete mode 100644 tests/unit/JsonPointer.spec.ts delete mode 100644 tests/unit/SpecManager.spec.ts delete mode 100644 tests/unit/helpers.spec.ts delete mode 100644 tests/unit/pipes.spec.js delete mode 100644 tests/unit/x-extended-defs.json delete mode 100644 tsconfig.aot.json delete mode 100644 tslint.json create mode 100644 webpack.config.js diff --git a/.editorconfig b/.editorconfig index 737ec4aa..42c2e0b1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,30 +1,10 @@ +root = true + [*] -charset=utf-8 -end_of_line=lf -insert_final_newline=false -indent_style=space -indent_size=2 - -[{.eslintrc,.babelrc,.stylelintrc,jest.config,*.json,*.jsb3,*.jsb2,*.bowerrc}] -indent_style=space -indent_size=2 - -[*.scss] -indent_style=space -indent_size=2 - -[*.styl] -indent_style=space -indent_size=2 - -[*.coffee] -indent_style=space -indent_size=2 - -[{.analysis_options,*.yml,*.yaml}] -indent_style=space -indent_size=2 - -[tslint.json] -indent_style=space -indent_size=2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true diff --git a/.gitignore b/.gitignore index 51a2fb0f..05636425 100644 --- a/.gitignore +++ b/.gitignore @@ -20,20 +20,8 @@ npm-debug.log* # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git node_modules -# compiled css -lib/**/*.css +lib/ -# files produced by ngc -lib/**/*.ngfactory.ts -lib/**/*.css.shim.ts -**/*.ngsummary.json -lib/**/*.shim.ngstyle.ts - -# other -/dist -/demo/build -.tmp -compiled /coverage .ghpages-tmp stats.json diff --git a/.npmignore b/.npmignore index 7d2abb70..dd64d4ef 100644 --- a/.npmignore +++ b/.npmignore @@ -1,12 +1,8 @@ .DS_Store **/.* -compiled -node_modules -jspm_packages +node_modules/ -tests -lib -demo -build -coverage +perf/ +demo/ +coverage/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..8d9c9bc0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.formatOnSave": false +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 4da43598..25b3c827 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,3 @@ - -# [1.19.1](https://github.com/Rebilly/ReDoc/compare/v1.19.0...v1.19.1) (2017-10-02) - - -### Bug Fixes - -* snapshot crashing on `constructor` prop ([04e8606](https://github.com/Rebilly/ReDoc/commit/04e8606)), closes [#341](https://github.com/Rebilly/ReDoc/issues/341) - - # [1.19.0](https://github.com/Rebilly/ReDoc/compare/v1.18.1...v1.19.0) (2017-09-21) diff --git a/README.md b/README.md index e8ead823..9dc4fef9 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,15 @@ [![Browser Compatibility](https://saucelabs.com/browser-matrix/redoc.svg)](https://saucelabs.com/u/redoc) +**REACT-REWRITE: WORK IN PROGRESS** + +To try locally run: + +```shell +yarn install +npm start +``` + ![ReDoc demo](demo/redoc-demo.png) ## [Live demo](http://rebilly.github.io/ReDoc/) diff --git a/bower.json b/bower.json deleted file mode 100644 index d3d14e62..00000000 --- a/bower.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "redoc", - "description": "Swagger-generated API Reference Documentation", - "main": "dist/redoc.min.js", - "authors": [ - "Roman Hotsiy" - ], - "repository": { - "type": "git", - "url": "git://github.com/Rebilly/ReDoc" - }, - "license": "MIT", - "keywords": [ - "OpenAPI", - "OpenAPI Specification", - "Swagger", - "JSON-Schema", - "API", - "REST", - "documentation", - "Angular 2" - ], - "homepage": "https://github.com/Rebilly/ReDoc", - "moduleType": ["globals", "amd"], - "ignore": [ - "**/.*", - "node_modules", - "tests", - "compiled", - "lib", - "demo", - "build", - "docs", - "gulpfile.js", - "*.conf.js", - "*.config.js" - ] -} diff --git a/build/helpers.js b/build/helpers.js deleted file mode 100644 index 25bd2532..00000000 --- a/build/helpers.js +++ /dev/null @@ -1,9 +0,0 @@ -const path = require('path'); -function root(args) { - args = Array.prototype.slice.call(arguments, 0); - return path.join.apply(path, [__dirname, '..'].concat(args)); -} - -module.exports = { - root: root -} diff --git a/build/join-module-css.js b/build/join-module-css.js deleted file mode 100644 index c41e2b25..00000000 --- a/build/join-module-css.js +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env node -'use strict'; -require('shelljs/global'); - -set('-e'); -set('-v'); - - -cat([ - 'lib/components/Redoc/redoc-initial-styles.css', - 'node_modules/perfect-scrollbar/dist/css/perfect-scrollbar.css', - 'node_modules/dropkickjs/build/css/dropkick.css', - 'node_modules/prismjs/themes/prism-dark.css', - 'node_modules/hint.css/hint.base.css' -]).to('dist/redoc.css') diff --git a/build/paths.js b/build/paths.js deleted file mode 100644 index 2707c00c..00000000 --- a/build/paths.js +++ /dev/null @@ -1,12 +0,0 @@ -var path = require('path'); - -var paths = { - outputName: 'redoc.min.js', - output: 'dist/', - demo: 'demo/**/*', - releases: 'demo/releases/' -} - -paths.redocBuilt = path.join(paths.output, paths.outputName); - -module.exports = paths; diff --git a/build/prepare_deploy.js b/build/prepare_deploy.js deleted file mode 100755 index 566eae61..00000000 --- a/build/prepare_deploy.js +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env node -'use strict'; -require('shelljs/global'); - -var paths = require('./paths'); -var path = require('path'); - -set('-e'); -set('-v'); - -// build -exec('npm run build-dist'); -cd('demo'); -mv('index-gh.html', 'index.html'); -mkdir('-p', 'dist'); -cp('-R', '../dist/*', './dist/'); -cd('..'); - -var version = require(path.join(__dirname, '../package.json')).version; -var versionDir = path.join(paths.releases, 'v' + version + '/'); -var latestDir = path.join(paths.releases, 'latest/'); -var v1Dir = path.join(paths.releases, 'v' + version.split('.')[0] + '.x.x/'); -mkdir('-p', versionDir) -mkdir('-p', latestDir); -mkdir('-p', v1Dir); -cp(paths.redocBuilt, versionDir); -cp(paths.redocBuilt, latestDir); -cp(paths.redocBuilt, v1Dir); diff --git a/build/resource-override.js b/build/resource-override.js deleted file mode 100644 index e69de29b..00000000 diff --git a/build/run_tests.js b/build/run_tests.js deleted file mode 100755 index 805b4ef5..00000000 --- a/build/run_tests.js +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -require('shelljs/global'); -set('-e'); - -function isPR() { - return process.env.TRAVIS_PULL_REQUEST && process.env.TRAVIS_PULL_REQUEST !== 'false'; -} - -function isCI() { - return !!process.env.CI; -} - -if (process.env.JOB === 'e2e-guru') { - if (isPR()) { - console.log('Skiping E2E tests on PR'); - return; - } - exec('npm run e2e'); -} else { - exec('npm run unit'); - if (isPR()) { - console.log('Skiping E2E tests on PR'); - return; - } - if (!isCI()) { - console.log('Skiping E2E tests locally. Use `npm run e2e` to run'); - return; - } - console.log('Starting Basic E2E'); - exec('npm run e2e'); -} diff --git a/build/webpack.common.js b/build/webpack.common.js deleted file mode 100644 index da047652..00000000 --- a/build/webpack.common.js +++ /dev/null @@ -1,136 +0,0 @@ -const webpack = require('webpack'); - -const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin; -const StringReplacePlugin = require("string-replace-webpack-plugin"); -const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin'); - -const VERSION = JSON.stringify(require('../package.json').version); - -const root = require('./helpers').root; - -module.exports = function (options) { - const conf = { - performance: { hints: false }, - - output: { - path: root('dist'), - filename: '[name].js', - sourceMapFilename: '[name].[id].map', - chunkFilename: '[id].chunk.js' - }, - - resolve: { - extensions: ['.ts', '.js', '.json', '.css'], - alias: { - http: 'stream-http', - https: 'https-browserify' - } - }, - - externals: { - 'jquery': 'jquery', - 'esprima': 'esprima' // optional dep of ys-yaml not needed for redoc - }, - - module: { - exprContextCritical: false, - rules: [ - { - enforce: 'pre', - test: /\.ts$/, - exclude: [ - /node_modules/ - ], - loader: StringReplacePlugin.replace({ - replacements: [ - { - pattern: /styleUrls:\s*\[\s*'([\w\.\/-]*)\.css'\s*\][\s,]*$/gm, - replacement: function (match, p1, offset, string) { - return `styleUrls: ['${p1}.scss'],`; - } - }, - { - pattern: /(\.\/components\/Redoc\/redoc-initial-styles\.css)/gm, - replacement: function (match, p1, offset, string) { - return p1.replace('.css', '.scss'); - } - } - ] - }) - }, - { - enforce: 'pre', - test: /\.js$/, - loader: 'source-map-loader', - exclude: [ - /node_modules/ - ] - }, - { - test: /\.json$/, - use: 'json-loader' - }, - { - test: /lib[\\\/].*\.css$/, - loaders: ['raw-loader'], - exclude: [/redoc-initial-styles\.css$/] - }, { - test: /\.css$/, - loaders: ['style-loader', 'css-loader?-import'], - exclude: [/lib[\\\/](?!.*redoc-initial-styles).*\.css$/] - }, - { - test: /lib[\\\/].*\.scss$/, - loaders: ['raw-loader', 'sass-loader'], - exclude: [/redoc-initial-styles\.scss$/] - }, - { - test: /\.scss$/, - use: [ - 'style-loader', - 'css-loader?-import', - 'sass-loader' - ], - exclude: [/lib[\\\/](?!.*redoc-initial-styles).*\.scss$/] - }, - { - test: /\.html$/, - loader: 'raw-loader' - } - ], - - }, - - plugins: [ - new CheckerPlugin(), - new webpack.DefinePlugin({ - 'IS_PRODUCTION': options.IS_PRODUCTION, - 'LIB_VERSION': VERSION, - 'AOT': options.AOT - }), - - new StringReplacePlugin() - ], - node: { - global: true, - crypto: 'empty', - fs: 'empty', - process: true, - module: false, - clearImmediate: false, - setImmediate: false - } - }; - - // if (options.AOT) { - // conf.plugins.push( - // new ngcWebpack.NgcWebpackPlugin({ - // disable: !options.AOT, - // tsConfig: root('tsconfig.webpack.json'), - // resourceOverride: root('build/resource-override.js') - // }) - // ); - // } - - return conf; -} diff --git a/build/webpack.dev.js b/build/webpack.dev.js deleted file mode 100644 index 6c544102..00000000 --- a/build/webpack.dev.js +++ /dev/null @@ -1,50 +0,0 @@ -const webpack = require('webpack'); -const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin; -const StringReplacePlugin = require("string-replace-webpack-plugin"); - -const root = require('./helpers').root; -const VERSION = JSON.stringify(require('../package.json').version); -const IS_PRODUCTION = process.env.NODE_ENV === "production"; - -const webpackMerge = require('webpack-merge'); // used to merge webpack configs -const commonConfig = require('./webpack.common.js'); - -module.exports = webpackMerge(commonConfig({ - IS_PRODUCTION: IS_PRODUCTION, - AOT: IS_PRODUCTION -}), { - devtool: '#inline-source-map', - entry: { - 'polyfills': './lib/polyfills.ts', - 'redoc': './lib/index.ts', - }, - devServer: { - contentBase: root('demo'), - watchContentBase: true, - compress: true, - watchOptions: { - poll: true - }, - port: 9000, - hot: false, - stats: 'errors-only' - }, - module: { - rules: [ - { - test: /\.ts$/, - use: [ - 'awesome-typescript-loader', - 'angular2-template-loader', - ], - exclude: [/\.(spec|e2e)\.ts$/] - }, - ] - }, - plugins: [ - new webpack.optimize.CommonsChunkPlugin({ - name: ['vendor', 'polyfills'], - minChunks: Infinity - }) - ] -}) diff --git a/build/webpack.prod.js b/build/webpack.prod.js deleted file mode 100644 index aaf8e904..00000000 --- a/build/webpack.prod.js +++ /dev/null @@ -1,83 +0,0 @@ -const webpack = require('webpack'); - -const VERSION = JSON.stringify(require('../package.json').version); - -const root = require('./helpers').root; -const BANNER = -`ReDoc - OpenAPI/Swagger-generated API Reference Documentation -------------------------------------------------------------- - Version: ${VERSION} - Repo: https://github.com/Rebilly/ReDoc`; - -const IS_MODULE = process.env.IS_MODULE != null; - -const webpackMerge = require('webpack-merge'); // used to merge webpack configs -const commonConfig = require('./webpack.common.js'); - -const config = webpackMerge(commonConfig({ - IS_PRODUCTION: true, - AOT: true -}), { - devtool: 'source-map', - - entry: { - 'redoc': IS_MODULE ? ['./lib/redoc.module.ts'] : ['./lib/polyfills.ts', './lib/index.ts'] - }, - - output: { - path: root('dist'), - filename: IS_MODULE ? '[name]-module.js' : '[name].min.js', - sourceMapFilename: IS_MODULE ? '[name]-module.map' : '[name].min.map', - library: 'Redoc', - libraryTarget: 'umd', - umdNamedDefine: true - }, - module: { - rules: [ - { - test: /\.ts$/, - use: [ - 'awesome-typescript-loader', - 'angular2-template-loader', - ], - exclude: [/\.(spec|e2e)\.ts$/] - } - ] - }, - plugins: [ - new webpack.LoaderOptionsPlugin({ - minimize: true, - debug: false - }), - new webpack.optimize.UglifyJsPlugin({ - compress: { - warnings: false, - screw_ie8: true - }, - mangle: { screw_ie8 : true }, - output: { - comments: false - }, - sourceMap: true - }), - new webpack.optimize.ModuleConcatenationPlugin(), - new webpack.BannerPlugin(BANNER) - ] -}) - -if (IS_MODULE) { - config.externals = { - 'jquery': 'jquery', - 'esprima': 'esprima', // optional dep of ys-yaml not needed for redoc - '@angular/platform-browser-dynamic': '@angular/platform-browser-dynamic', - '@angular/platform-browser': '@angular/platform-browser', - '@angular/core': '@angular/core', - '@angular/common': '@angular/common', - '@angular/forms': '@angular/forms', - 'core-js': 'core-js', - 'rxjs': 'rxjs', - 'zone.js/dist/zone': 'zone.js/dist/zone' - }; -} - -module.exports = config; diff --git a/build/webpack.test.js b/build/webpack.test.js deleted file mode 100644 index 5f01ccd2..00000000 --- a/build/webpack.test.js +++ /dev/null @@ -1,61 +0,0 @@ -const webpack = require('webpack'); - -const root = require('./helpers').root; -const path = require('path'); - -const webpackMerge = require('webpack-merge'); // used to merge webpack configs -const commonConfig = require('./webpack.common.js'); - -module.exports = webpackMerge(commonConfig({ - IS_PRODUCTION: true, - AOT: false -}), { - devtool: 'inline-source-map', - - module: { - exprContextCritical: false, - rules: [ - { - test: /\.ts$/, - use: 'awesome-typescript-loader' - }, - { - test: /\.ts$/, - use: [ - 'angular2-template-loader', - ], - exclude: [/\.(spec|e2e)\.ts$/] - }, - { - enforce: 'post', - test: /\.(js|ts)$/, loader: 'istanbul-instrumenter-loader', - include: root('lib'), - exclude: [ - /\.(e2e|spec)\.ts$/, - /node_modules/ - ] - }] - }, - - plugins: [ - new webpack.LoaderOptionsPlugin({ - test: /\.ts$/, - sourceMap: false, - inlineSourceMap: true, - removeComments: true, - module: "commonjs" - }), - // ignore changes during tests - new webpack.WatchIgnorePlugin([ - /[\\\/]ReDoc$/i, // ignore change of ReDoc folder itself - /node_modules[\\\/].*$/, - /\.tmp[\\\/].*$/, - /dist[\\\/].*$/, - /(?:[^\\\/]*(?:[\\\/]|$))*[^\\\/]*\.css$/ // ignore css files - ]), - new webpack.ContextReplacementPlugin( - /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/, - path.resolve(__dirname, '../src') - ) - ], -}) diff --git a/custom.d.ts b/custom.d.ts index 35963020..7f77b4c2 100644 --- a/custom.d.ts +++ b/custom.d.ts @@ -1,25 +1,20 @@ -declare module "*.css" { +declare module '*.json' { + const content: any; + export = content; +} + +declare module '*.svg' { const content: string; export default content; } -declare module "*.json" { +declare module '*.css' { const content: string; export default content; } -declare var LIB_VERSION: any; -declare var IS_PRODUCTION: any; -declare var AOT: any; +declare var __DEV__: boolean; -interface ErrorStackTraceLimit { - stackTraceLimit: number; -} -interface History { - scrollRestoration: "auto"|"manual"; -} -interface Window { - HTMLElement: any -} -declare var safari: any; -interface ErrorConstructor extends ErrorStackTraceLimit {} +declare type Dict = { + [key: string]: T; +}; diff --git a/demo/big-swagger.json b/demo/big-swagger.json new file mode 100644 index 00000000..802918a6 --- /dev/null +++ b/demo/big-swagger.json @@ -0,0 +1,27362 @@ +{ + "openapi": "3.0.0", + "servers": [ + { + "url": "//api.rebilly.com/v2.1", + "description": "Live Server" + }, + { + "url": "//api-sandbox.rebilly.com/v2.1", + "description": "Sandbox Server" + } + ], + "info": { + "version": "2.1", + "title": "Rebilly REST API", + "contact": { + "name": "Rebilly API Support", + "url": "https://www.rebilly.com/contact/", + "email": "integrations@rebilly.com" + }, + "license": { + "name": "Rebilly", + "url": "https://my.rebilly.com/api/license/" + }, + "termsOfService": "https://www.rebilly.com/terms/", + "x-logo": { + "url": "https://rebilly.github.io/RebillyAPI/rb_apiLogo.svg", + "backgroundColor": "#0033A0" + }, + "description": "# Introduction\nThe Rebilly API is built on HTTP. Our API is RESTful. It has predictable\nresource URLs. It returns HTTP response codes to indicate errors. It also\naccepts and returns JSON in the HTTP body. You can use your favorite\nHTTP/REST library for your programming language to use Rebilly's API, or\nyou can use one of our SDKs (currently available in [PHP](https://github.com/Rebilly/rebilly-php)\nand [C#](https://github.com/Rebilly/rebilly-dotnet-client)).\n\n# Authentication\nWhen you sign up for an account, you are given your first API key.\nYou can generate additional API keys, and delete API keys (as you may\nneed to rotate your keys in the future). You authenticate to the\nRebilly API by providing your secret key in the request header.\n\nRebilly offers three forms of authentication: private key, JSON Web Tokens, and\npublic key.\n- private key: authenticates each request by searching for the presence\nof an HTTP header: REB-APIKEY.\n- JWT: authenticates each request by the HTTP header: Authorization.\n- public key: authenticates by the HTTP header: REB-AUTH (read more on this below).\n\nRebilly also offers JSON Web Tokens (JWT) authentication, where you can control\nthe specific granular permissions and expiration for that JWT. We call our resource\nfor generating JWT [Sessions](#tag/Sessions).\n\nRebilly also has a client-side authentication scheme that uses an\napiUser and HMAC-SHA1 signature (only for the Tokens resource), so\nthat you may safely create tokens from the client-side without compromising\nyour secret keys.\n\nNever share your secret keys. Keep them guarded and secure.\nThe client-side authentication scheme uses one HTTP header named REB-AUTH.\n\n\n\n# PHP SDK\nFor all PHP SDK examples provided in this spec you will need to configure `$client`.\nYou may do it like this:\n\n```php\n$client = new Rebilly\\Client([\n 'apiKey' => 'YourApiKeyHere',\n 'baseUrl' => 'https://api.rebilly.com',\n]);\n```\n" + }, + "tags": [ + { + "name": "3D Secure", + "description": "3D Secure is a way to authenticate and protect transactions. Typically,\nit's only possible to protect the initial transaction in a subscription\nwith 3D Secure.\n" + }, + { + "name": "API Keys", + "description": "Always keep your API Keys private. In addition to your API Keys, you may use\nJSON Web Tokens (JWT) to authenticate to the API. See\nour [Sessions](#tag/Sessions) resource for more information.\n" + }, + { + "name": "Bank Accounts", + "description": "Bank Accounts are a type of payment instrument used to collect\nACH (echeck) payments, similar to how a payment\ncard would be used to for a credit card payment.\n" + }, + { + "name": "Blacklists", + "description": "Your blacklists contains values of customerIds, email addresses,\nipAddresses, bank identification numbers, countries or payment cards that\nyou do NOT want to do business with. They are a good tool for managing\nrisk. A blacklist entry that expires after a period of time we call a\ngreylist.\n" + }, + { + "name": "Checkout Pages", + "description": "Hosted checkout pages.\n" + }, + { + "name": "Contacts", + "description": "Contacts are Customer's address book.\nAll contact information used in Invoices, Subscriptions, Transacions, etc is enlisted here. Hovewer, changing a Contact won't change corresponding contact information in related resources\n" + }, + { + "name": "Coupons", + "description": "Coupons allows to apply different types of discounts to Invoices, Subscriptions and Plans. Redeemed Coupons will be applied only to Invoices with the same currency.\n" + }, + { + "name": "Customers", + "description": "Customers are your customers, sometimes known as accounts, clients,\nmembers, patrons, or players in other systems.\n" + }, + { + "name": "Customer Authentication", + "description": "Create authentication credentials, login, logout, and verify your customers.\n" + }, + { + "name": "Custom Events", + "description": "If system events can't solve your problems, you are able to create a custom event\nthat can fit your requirements, and use it to solve your own business logic.\n" + }, + { + "name": "Custom Rules", + "description": "\"Don't conform to the rules. Create the rules.\"\nRather than adapt your workflow and business, Rebilly can align with your business\nobjectives by giving you the power to automate certain behaviors when key events happen.\nYou can use this to your advantage to mitigate risk, maximize conversions and minimize costs.\nYou have the controls at your fingertips here. If you need more control,\nor help dialing in on a strategy, feel free to contact us.\n\nWhen an event happens, it triggers the evaluation of conditions (that you set up),\nin order from top to bottom. If the condition is met, the corresponding actions are executed.\nThe conditions continue to be checked until either all of the conditions have been executed,\nor a special \"stop\" action is executed.\n" + }, + { + "name": "Custom Fields", + "description": "Create additional custom fields for particular resources. You may name,\ndescribe, and determine the type of the schema.\n" + }, + { + "name": "Credential Hashes", + "description": "Get and create SMTP and Webhook credential hashes.\n" + }, + { + "name": "Disputes", + "description": "Handle disputes (chargebacks and retrievals).\n" + }, + { + "name": "Email Credentials", + "description": "Send automated emails through our system by connecting to your third party\nSMTP server (or your third party email service provider's SMTP server).\n" + }, + { + "name": "Files", + "description": "A File is an entity that can store a phyiscal file and some metadata. It also provides an easy access to\nits size, mime-type, user-defined tags and description thus allowing easy sorting and searching among stored\nfiles.\nThere are several methods of file uploading available: multipart/form-data encoded form, RAW POST (by sending\nfile contents as POST body), fetching from URL (by providing the file URL via 'url' param)\nAttachment is an entity that is used to link a File to one or multiple objects like Customer, Dispute, Payment,\nTransaction, Subscription, Plan, Product, Invoice, Note. That allows to quickly find and use files related to\nthose specific entities.\n" + }, + { + "name": "Gateway Accounts", + "description": "Gateway accounts connect payment request to third party networks and platforms.\n" + }, + { + "name": "Invoices", + "description": "Invoices leave a record for both you and your customer of the products sold.\n" + }, + { + "name": "Layouts", + "description": "Layouts are used to hold collections of plans. A layout can be used to\npower a pricing page. You can make multiple layouts, and use rules to\ntarget them to different audiences.\n" + }, + { + "name": "Lists", + "description": "Lists contain sets of values and may be referenced within Rules criteria.\n\nYou may grant permissions to edit Lists to different people than those who can edit Rules.\nIt may be useful if your workflow involves frequent updates to value sets used in criteria.\n" + }, + { + "name": "Migrate payment cards", + "description": "Migrate payment cards from one gateway to another.\n" + }, + { + "name": "Notes", + "description": "Leave notes on a customer record to have a handy location to share with\nothers who may interface with the customer. It's great for customer service.\n" + }, + { + "name": "Organizations", + "description": "Organizations include the name and address of the entities related to your\naccount. An account may be multi-national, and support multiple\norganizations. Note: Organizations are share between \"Live\" and \"Sandbox\"\n" + }, + { + "name": "Payments", + "description": "Collect money from your customers with payments. You can schedule a payment\nto occur in the future. You can assign a dunning schedule to a payment to collect\nin the case of a decline.\n\nSome payments may be, what we term, suspended payments. These types of payments\nrequire user interaction. For example, an initial PayPal purchase, a 3D Secure\npurchase, China Union Pay, and more require the customer's interaction to\ncomplete the payment. We call these a \"suspended\" payment flow.\n" + }, + { + "name": "Payment Cards", + "description": "Payment cards are a type of payment instrument used for credit and debit card\nsales. Rebilly securely vaults the full payment card number, and can pass it\nonward securely to any gateway account to transact business.\n" + }, + { + "name": "Payment Tokens", + "description": "Payment tokens are used to reduce the scope of PCI DSS compliance. A payment\ntoken can be made using a different authentication scheme (refer to the public key\nauthentication scheme in the Authentication section), which allows you to\ncreate a payment token directly from the browser, bypassing the need to send\nsensitive cardholder info to your servers. We recommend using this with our\nRebilly.js library, which helps you wire a form into this API resource and create\npayment tokens.\n" + }, + { + "name": "Plans", + "description": "Plans are a template for making a subscription. For example, you may have a plan\nthat has a 30-day free trial followed by a recurring charge of $19.95 per month\nuntil canceled. The combination of the plan and a request to make a subscription\nwill apply those instructions to create the invoices according to the plan's\nschedule.\n" + }, + { + "name": "Products", + "description": "Proposed: Your product includes digital goods, services, and physical goods.\n" + }, + { + "name": "Reports", + "description": "The Rebilly Reporting API is currently experimental. You may see\nthe [Reports API Documentation here](https://rebilly.github.io/RebillyReportsAPI/).\n" + }, + { + "name": "Rules", + "description": "\"Don't conform to the rules. Create the rules.\"\nRather than adapt your workflow and business, Rebilly can align with your business\nobjectives by giving you the power to automate certain behaviors when key events happen.\nYou can use this to your advantage to mitigate risk, maximize conversions and minimize costs.\nYou have the controls at your fingertips here. If you need more control,\nor help dialing in on a strategy, feel free to contact us.\n\nWhen an event happens, it triggers the evaluation of conditions (that you set up),\nin order from top to bottom. If the condition is met, the corresponding actions are executed.\nThe conditions continue to be checked until either all of the conditions have been executed,\nor a special \"stop\" action is executed.\n" + }, + { + "name": "Sessions", + "description": "A session contains a token, which is a JSON Web Token. The token is created\nwith a user's signin credentials.\n\nThis token can be used to authenticate to the API. In addition, the session can be set to\nexpire at a particular time, and has very granular control over permissions.\nUse the token to then authenticate for further requests to the Rebilly API.\n\nThe token should be kept private, but could be stored on the user's browser\nclient to simulate a \"session.\"\n" + }, + { + "name": "Shipping Zones", + "description": "A shipping zone contains regions and countries that you ship to. Each shipping zone has its own shipping rates.\n" + }, + { + "name": "Status", + "description": "Check the status of the Rebilly API (no authentication required).\n" + }, + { + "name": "Subscriptions", + "description": "A subscription applies a plan's template to create invoices for a customer at the\nappropriate scheduled intervals. A subscription may also determine if the payment\nis collected automatically (with autopay set true).\n" + }, + { + "name": "Taxes", + "description": "Proposed: You can map a product to a tax category. The tax category is used by\ntax providers to calculate taxes for invoices.\n" + }, + { + "name": "Tracking", + "description": "Tracking is a layer for accessing all the activity (API requests,\nsubscriptions, webhooks, events, etc.), thus providing easier\ndebugging and issues auditing.\n" + }, + { + "name": "Transactions", + "description": "Get and refund transactions.\n" + }, + { + "name": "Users", + "description": "A User represents a person who can login to Rebilly, and take actions subject to\ntheir granted permissions.\n" + }, + { + "name": "Websites", + "description": "A Website represents the website/brand that customers interact with... You\ncould think of it like a brand. For example, Nestle owns Perrier and Purina\nand PowerBar.\n\nWe recognize that some enterprises have more than one website (or brand). The\nwebsite is related to each invoice and each payment gateway account. This feature\nwould allow you to have gateway accounts that are related to multiple websites, or\nexclusive to particular websites. And gives you more control over your business.\n" + }, + { + "name": "Webhooks", + "description": "Webhooks are designed to notify your systems when certain/all registered events happen in near real-time.\nThey allow you to collect information about those events. Rebilly can send these information via\nPOST to an URL of your choice.\n" + } + ], + "security": [ + { + "ApiKey": [] + } + ], + "paths": { + "/3dsecure": { + "get": { + "tags": [ + "3D Secure" + ], + "summary": "Retrieve a list of ThreeDSecure entries", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ThreeDSecure" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + } + }, + "post": { + "tags": [ + "3D Secure" + ], + "summary": "Create a ThreeDSecure entry", + "description": "Create a ThreeDSecure entry\n", + "responses": { + "201": { + "description": "ThreeDSecure entry was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ThreeDSecure" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ThreeDSecure" + } + } + }, + "description": "ThreeDSecure resource", + "required": true + } + } + }, + "/3dsecure/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "3D Secure" + ], + "summary": "Retrieve a ThreeDSecure entry", + "description": "Retrieve a ThreeDSecure entry with specified identifier string\n", + "responses": { + "200": { + "description": "ThreeDSecure entry was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ThreeDSecure" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/activation/{token}": { + "parameters": [ + { + "name": "token", + "in": "path", + "description": "The token string", + "required": true, + "schema": { + "type": "string" + } + } + ], + "post": { + "tags": [ + "Users" + ], + "summary": "Sends a token to activate user account", + "description": "Sends a token to activate user account\n", + "security": [ + { + "RebAuth": [] + } + ], + "responses": { + "204": { + "description": "User account was activated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "422": { + "description": "Invalid token was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "try {\n $client->users()->activate('token');\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ] + } + }, + "/api-keys": { + "get": { + "tags": [ + "API Keys" + ], + "summary": "Retrieve a list of api keys", + "description": "Retrieve a list of api keys\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of api keys was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApiKey" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$apiKeys = $client->apiKeys()->search([\n 'filter' => 'description:Test',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "API Keys" + ], + "summary": "Create an api key", + "description": "Create an api key\n", + "responses": { + "201": { + "description": "Api Key was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiKey" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$apiKeyForm = new Rebilly\\Entities\\ApiKey();\n$apiKeyForm->setDescription('Test');\n$apiKeyForm->setDatetimeFormat($apiKeyForm::DATETIME_FORMAT_MYSQL);\n\ntry {\n $apiKey = $client->apiKeys()->create($apiKeyForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/ApiKey" + } + } + }, + "/api-keys/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "API Keys" + ], + "summary": "Retrieve api key", + "description": "Retrieve api key with specified identifier string\n", + "responses": { + "200": { + "description": "Api key was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiKey" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$apiKeys = $client->apiKeys()->load('apiKeyID');\n" + } + ] + }, + "put": { + "tags": [ + "API Keys" + ], + "summary": "Create or update api key with predefined ID", + "description": "Create or update api key with predefined identifier string\n", + "responses": { + "200": { + "description": "ApiKey was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiKey" + } + } + } + }, + "201": { + "description": "ApiKey was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiKey" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$apiKeyForm = new Rebilly\\Entities\\ApiKey();\n$apiKeyForm->setDescription('TestPut');\n$apiKeyForm->setDatetimeFormat($apiKeyForm::DATETIME_FORMAT_MYSQL);\n\ntry {\n $apiKey = $client->apiKeys()->update('apiKeyID', $apiKeyForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/ApiKey" + } + }, + "delete": { + "tags": [ + "API Keys" + ], + "summary": "Delete api key", + "description": "Delete api key with predefined identifier string\n", + "responses": { + "204": { + "description": "ApiKey was deleted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$client->apiKeys()->delete('apiKeyID');\n" + } + ] + } + }, + "/attachments": { + "get": { + "tags": [ + "Files" + ], + "summary": "Retrieve a list of Attachments", + "description": "Retrieve a list of Attachments\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + }, + { + "$ref": "#/components/parameters/collectionFilter" + }, + { + "$ref": "#/components/parameters/collectionQuery" + }, + { + "$ref": "#/components/parameters/collectionExpand" + }, + { + "$ref": "#/components/parameters/collectionFields" + }, + { + "name": "sort", + "in": "query", + "description": "The collection items sort field and order (prefix with \"-\" for descending sort).", + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "id", + "-id", + "name", + "-name", + "relatedId", + "-relatedId", + "relatedType", + "-relatedType", + "fileId", + "-fileId", + "createdTime", + "-createdTime", + "updatedTime", + "-updatedTime" + ] + } + } + } + ], + "responses": { + "200": { + "description": "A list of Attachments was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Attachment" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$attachments = $client->attachments()->search([\n 'filter' => 'relatedType:customer',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Files" + ], + "summary": "Create an Attachment", + "description": "Create an Attachment\n", + "responses": { + "201": { + "description": "Attachment was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Attachment" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$attachmentForm = new Rebilly\\Entities\\Attachment();\n$attachmentForm->setFileId('fileId');\n$attachmentForm->setRelatedType($attachmentForm::TYPE_CUSTOMER);\n$attachmentForm->setRelatedId('customerId');\n\ntry {\n $attachment = $client->attachments()->create($attachmentForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Attachment" + } + } + }, + "/attachments/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Files" + ], + "summary": "Retrieve an Attachment", + "description": "Retrieve a Attachment with specified identifier string\n", + "responses": { + "200": { + "description": "Attachment was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Attachment" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$attachment = $client->attachments()->load('attachmentId');\n" + } + ] + }, + "put": { + "tags": [ + "Files" + ], + "summary": "Update the Attachment with predefined ID", + "description": "Update the Attachment with predefined ID\n", + "responses": { + "200": { + "description": "Attachment was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Attachment" + } + } + } + }, + "201": { + "description": "Attachment was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Attachment" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$attachmentForm = new Rebilly\\Entities\\Attachment();\n$attachmentForm->setFileId('fileId');\n$attachmentForm->setRelatedType($attachmentForm::TYPE_CUSTOMER);\n$attachmentForm->setRelatedId('customerId');\n\ntry {\n $attachment = $client->attachments()->update('attachmentId', $attachmentForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Attachment" + } + }, + "delete": { + "tags": [ + "Files" + ], + "summary": "Delete an Attachment", + "description": "Delete the Attachment with predefined identifier string\n", + "responses": { + "204": { + "description": "Attachment was deleted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$client->attachments()->delete('attachmentId');\n" + } + ] + } + }, + "/authentication-options": { + "get": { + "tags": [ + "Customer Authentication" + ], + "summary": "Read current authentication options", + "description": "Read current authentication options\n", + "responses": { + "200": { + "description": "Current authentication options was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AuthenticationOptions" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$authenticationOptions = $client->authenticationOptions()->load();\n" + } + ] + }, + "put": { + "tags": [ + "Customer Authentication" + ], + "summary": "Change authentication options", + "description": "Change options\n", + "responses": { + "200": { + "description": "Authentication Options were updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationOptions" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$authenticationOptionsForm = new Rebilly\\Entities\\AuthenticationOptions();\n// Regular expression below matches any password with 6+ length that contains alphabet symbols and/or numbers.\n$authenticationOptionsForm->setPasswordPattern('/^[a-zA-Z0-9]{6,}$/');\n\ntry {\n $authenticationOptions = $client->authenticationOptions()->update($authenticationOptionsForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationOptions" + } + } + }, + "description": "Authentication Options resource", + "required": true + } + } + }, + "/authentication-tokens": { + "get": { + "tags": [ + "Customer Authentication" + ], + "summary": "Retrieve a list of auth tokens", + "description": "Retrieve a list of auth tokens\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of auth tokens was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AuthenticationToken" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$authenticationTokens = $client->authenticationTokens()->search([\n 'filter' => 'customerId:testCustomer',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Customer Authentication" + ], + "summary": "Login", + "description": "Login a user (customer)\n", + "responses": { + "201": { + "description": "Login successful", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationToken" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$authenticationForm = new Rebilly\\Entities\\AuthenticationToken();\n$authenticationForm->setUsername('username');\n$authenticationForm->setPassword('test123');\n\ntry {\n $authenticationToken = $client->authenticationTokens()->login($authenticationForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationToken" + } + } + }, + "description": "AuthenticationToken resource", + "required": true + } + } + }, + "/authentication-tokens/{token}": { + "parameters": [ + { + "name": "token", + "in": "path", + "description": "The token identifier string", + "required": true, + "schema": { + "type": "string" + } + } + ], + "get": { + "tags": [ + "Customer Authentication" + ], + "summary": "Verify", + "description": "Verify an authentication token\n", + "responses": { + "200": { + "description": "Authentication Token was verified", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationToken" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$isVerified = $client->authenticationTokens()->verify('token');\n" + } + ] + }, + "delete": { + "tags": [ + "Customer Authentication" + ], + "summary": "Logout a user", + "description": "Logout a user\n", + "responses": { + "204": { + "description": "User was logged out", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$client->authenticationTokens()->logout('token');\n" + } + ] + } + }, + "/bank-accounts": { + "get": { + "tags": [ + "Bank Accounts" + ], + "summary": "Retrieve a list of bank accounts", + "description": "Retrieve a list of Bank Accounts\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of Bank Accounts was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BankAccount" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$bankAccounts = $client->bankAccounts()->search([\n 'filter' => 'customerId:testId',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Bank Accounts" + ], + "summary": "Create a Bank Account", + "description": "Create a Bank Account\n", + "responses": { + "201": { + "description": "Bank Account was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccount" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$bankAccountForm = new Rebilly\\Entities\\BankAccount();\n$bankAccountForm->setCustomerId('customerId');\n$bankAccountForm->setContactId('contactId');\n$bankAccountForm->setRoutingNumber('0123456');\n$bankAccountForm->setAccountNumber('0123456');\n$bankAccountForm->setAccountType('checking');\n\ntry {\n $bankAccount = $client->bankAccounts()->create($bankAccountForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/BankAccount" + } + } + }, + "/bank-accounts/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Bank Accounts" + ], + "summary": "Retrieve a Bank Account", + "description": "Retrieve a Bank Account with specified identifier string\n", + "responses": { + "200": { + "description": "BankAccount was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccount" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$bankAccount = $client->bankAccounts()->load('bankAccountId');\n" + } + ] + }, + "put": { + "tags": [ + "Bank Accounts" + ], + "summary": "Create a BankAccount with predefined ID", + "description": "Create or update a BankAccount with predefined identifier string\n", + "responses": { + "200": { + "description": "BankAccount was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccount" + } + } + } + }, + "201": { + "description": "BankAccount was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccount" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$bankAccountForm = new Rebilly\\Entities\\BankAccount();\n$bankAccountForm->setCustomerId('customerId');\n$bankAccountForm->setContactId('contactId');\n$bankAccountForm->setRoutingNumber('0123456');\n$bankAccountForm->setAccountNumber('0123456');\n$bankAccountForm->setAccountType('checking');\n\ntry {\n $bankAccount = $client->customers()->create($bankAccountForm, 'bankAccountId');\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/BankAccount" + } + } + }, + "/bank-accounts/{id}/deactivation": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "post": { + "tags": [ + "Bank Accounts" + ], + "summary": "Deactivate a Bank Account", + "description": "Deactivate a Bank Account\n", + "responses": { + "201": { + "description": "Deactivated successful", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccount" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$bankAccount = $client->bankAccounts()->deactivate('bankAccountId');\n" + } + ] + } + }, + "/blacklists": { + "get": { + "tags": [ + "Blacklists" + ], + "summary": "Retrieve a list of blacklists", + "description": "Retrieve a list of blacklists\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of Blacklists was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Blacklist" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$blacklists = $client->blacklists()->search([\n 'filter' => 'value:testValue',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Blacklists" + ], + "summary": "Create a blacklist", + "description": "Create a blacklist\n", + "responses": { + "201": { + "description": "Blacklist was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Blacklist" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$blacklistForm = new Rebilly\\Entities\\Blacklist();\n$blacklistForm->setType($blacklistForm::TYPE_EMAIL);\n$blacklistForm->setValue('test@test.com');\n$blacklistForm->setExpiredTime('2025-01-01 05:00:00');\n\ntry {\n $blacklist = $client->blacklists()->create($blacklistForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Blacklist" + } + } + }, + "/blacklists/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Blacklists" + ], + "summary": "Retrieve a blacklist", + "description": "Retrieve a blacklist with specified identifier string\n", + "responses": { + "200": { + "description": "Blacklist was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Blacklist" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$blacklist = $client->blacklists()->load('blacklistId');\n" + } + ] + }, + "put": { + "tags": [ + "Blacklists" + ], + "summary": "Create a blacklist with predefined ID", + "description": "Create a blacklist with predefined identifier string\n", + "responses": { + "201": { + "description": "Blacklist was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Blacklist" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "409": { + "description": "Blacklist exist and cannot be updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$blacklistForm = new Rebilly\\Entities\\Blacklist();\n$blacklistForm->setType($blacklistForm::TYPE_EMAIL);\n$blacklistForm->setValue('test@test.com');\n$blacklistForm->setExpiredTime('2025-01-01 05:00:00');\n\ntry {\n $blacklist = $client->blacklists()->create($blacklistForm, 'blacklistId');\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Blacklist" + } + }, + "delete": { + "tags": [ + "Blacklists" + ], + "summary": "Delete a blacklist", + "description": "Delete a blacklist with predefined identifier string\n", + "responses": { + "204": { + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "description": "Blacklist was deleted" + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$client->blacklists()->delete('blacklistId');\n" + } + ] + } + }, + "/checkout-pages": { + "get": { + "tags": [ + "Checkout Pages" + ], + "summary": "Retrieve a list of checkout pages", + "description": "Retrieve a list of checkout pages\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of checkout pages was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CheckoutPage" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$checkoutPages = $client->checkoutPages()->search([\n 'filter' => 'name:testCheckoutPage',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Checkout Pages" + ], + "summary": "Create a Checkout Page", + "description": "Create a Checkout Page\n", + "responses": { + "201": { + "description": "Checkout Page was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CheckoutPage" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$checkoutPageForm = new Rebilly\\Entities\\CheckoutPage();\n$checkoutPageForm->setPlanId('planId');\n$checkoutPageForm->setWebsiteId('websiteId');\n$checkoutPageForm->setName('TestCheckoutPage');\n$checkoutPageForm->setUriPath('test-checkout-page');\n\ntry {\n $checkoutPage = $client->checkoutPages()->create($checkoutPageForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/CheckoutPage" + } + } + }, + "/checkout-pages/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Checkout Pages" + ], + "summary": "Retrieve a Checkout Page", + "description": "Retrieve a Checkout Page with specified identifier string\n", + "responses": { + "200": { + "description": "Checkout Page was retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CheckoutPage" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$checkoutPage = $client->checkoutPages()->load('checkoutPageId');\n" + } + ] + }, + "put": { + "tags": [ + "Checkout Pages" + ], + "summary": "Create or update a Checkout Page with predefined ID", + "description": "Create or update a Checkout Page with predefined identifier string\n", + "responses": { + "200": { + "description": "Checkout Page was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CheckoutPage" + } + } + } + }, + "201": { + "description": "Checkout Page was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CheckoutPage" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$checkoutPageForm = new Rebilly\\Entities\\CheckoutPage();\n$checkoutPageForm->setPlanId('planId');\n$checkoutPageForm->setWebsiteId('websiteId');\n$checkoutPageForm->setName('TestCheckoutPage');\n$checkoutPageForm->setUriPath('test-checkout-page');\n\ntry {\n $checkoutPage = $client->checkoutPages()->update('checkoutPageId', $checkoutPageForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/CheckoutPage" + } + }, + "delete": { + "tags": [ + "Checkout Pages" + ], + "summary": "Delete a Checkout Page", + "description": "Delete a Checkout Page with predefined identifier string\n", + "responses": { + "204": { + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "description": "Checkout Page was deleted" + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Checkout page cannot be deleted" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$client->checkoutPages()->delete('checkoutPageId');\n" + } + ] + } + }, + "/contacts": { + "get": { + "tags": [ + "Contacts" + ], + "summary": "Retrieve a list of contacts", + "description": "Retrieve a list of contacts\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of Contacts was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Contact" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$contacts = $client->contacts()->search([\n 'filter' => 'firstName:John',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Contacts" + ], + "summary": "Create a contact", + "description": "Create a contact\n", + "responses": { + "201": { + "description": "Contact was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Contact" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$contactForm = new Rebilly\\Entities\\Contact();\n$contactForm->setFirstName('Sherlock');\n$contactForm->setLastName('Holmes');\n$contactForm->setOrganization('TestOrganization');\n\ntry {\n $contact = $client->contacts()->create($contactForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Contact" + } + } + }, + "/contacts/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Contacts" + ], + "summary": "Retrieve a contact", + "description": "Retrieve a contact with specified identifier string\n", + "responses": { + "200": { + "description": "Contact was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Contact" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$contact = $client->contacts()->load('contactId');\n" + } + ] + }, + "put": { + "tags": [ + "Contacts" + ], + "summary": "Create or update a contact with predefined ID", + "description": "Create or update a contact with predefined identifier string\n", + "responses": { + "201": { + "description": "Contact was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Contact" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Contact exists and cannot be updated" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$contactForm = new Rebilly\\Entities\\Contact();\n$contactForm->setFirstName('Sherlock');\n$contactForm->setLastName('Holmes');\n$contactForm->setOrganization('TestOrganization');\n\ntry {\n $contact = $client->contacts()->update('contactId', $contactForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Contact" + } + }, + "delete": { + "tags": [ + "Contacts" + ], + "summary": "Delete a contact", + "description": "Delete a contact with predefined identifier string\n", + "responses": { + "204": { + "description": "Contact was deleted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "402": { + "description": "Contact cannot be deleted" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/coupons-redemptions": { + "get": { + "tags": [ + "Coupons" + ], + "summary": "Retrieve a list of coupon redemptions", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + }, + { + "$ref": "#/components/parameters/collectionFilter" + }, + { + "$ref": "#/components/parameters/collectionQuery" + }, + { + "$ref": "#/components/parameters/collectionCriteria" + }, + { + "$ref": "#/components/parameters/collectionSort" + } + ], + "responses": { + "200": { + "description": "Coupons redemptions were retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CouponRedemption" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$couponRedemptions = $client->couponsRedemptions()->search([\n 'filter' => 'customerId:testCustomer',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Coupons" + ], + "summary": "Redeem a coupon", + "description": "Redeem a coupon\n", + "responses": { + "201": { + "description": "Coupon was redeemed", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CouponRedemption" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$redemptionForm = new Rebilly\\Entities\\Coupons\\Redemption();\n$redemptionForm->setCustomerId('customerId');\n$redemptionForm->setRedemptionCode('redemptionCode');\n\n$restrictionArray = [\n 'type' => Rebilly\\Entities\\Coupons\\Restriction::TYPE_DISCOUNTS_PER_REDEMPTION,\n 'quantity' => 2,\n];\n\n$restrictionForm = new Rebilly\\Entities\\Coupons\\Restriction([\n $restrictionArray,\n]);\n\n$redemptionForm->setAdditionalRestrictions($restrictionForm);\n\ntry {\n $couponRedemption = $client->couponsRedemptions()->redeem($redemptionForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CouponRedemption" + } + } + }, + "description": "Redeem a coupon", + "required": true + } + } + }, + "/coupons-redemptions/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Coupons" + ], + "summary": "Retrieve a coupon redemption with specified identifier string", + "responses": { + "200": { + "description": "Retrieve a coupon redemption with specified identifier string", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CouponRedemption" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$couponRedemption = $client->couponsRedemptions()->load('redemptionCode');\n" + } + ] + } + }, + "/coupons-redemptions/{id}/cancel": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "post": { + "tags": [ + "Coupons" + ], + "summary": "Cancel a coupon redemption", + "responses": { + "201": { + "description": "Cancel a coupon redemption" + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$client->couponsRedemptions()->cancel('id');\n" + } + ] + } + }, + "/coupons": { + "get": { + "tags": [ + "Coupons" + ], + "summary": "Retrieve a list of coupons", + "description": "Retrieve a list of coupons\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + }, + { + "$ref": "#/components/parameters/collectionFilter" + }, + { + "$ref": "#/components/parameters/collectionQuery" + }, + { + "$ref": "#/components/parameters/collectionCriteria" + }, + { + "$ref": "#/components/parameters/collectionSort" + } + ], + "responses": { + "200": { + "description": "A list of coupons was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Coupon" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$coupons = $client->coupons()->search([\n 'filter' => 'status:issued',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Coupons" + ], + "summary": "Create a coupon", + "description": "Create a coupon\n", + "responses": { + "201": { + "description": "Coupon was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Coupon" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$couponForm = new Rebilly\\Entities\\Coupons\\Coupon();\n\n$discountArray = [\n 'currency' => 'USD',\n 'amount' => 1.99,\n];\n\n$discountForm = new \\Rebilly\\Entities\\Coupons\\Discounts\\Fixed($discountArray);\n$couponForm->setDiscount($discountForm);\n// Coupon can be used right now\n$couponForm->setIssuedTime(date('Y-m-d H:i:s'));\n\n$restrictionArray = [\n 'quantity' => 2,\n];\n\n$restrictionForm = new Rebilly\\Entities\\Coupons\\Restrictions\\DiscountsPerRedemption($restrictionArray);\n\n$couponForm->setRestrictions([$restrictionForm]);\n\ntry {\n $coupon = $client->coupons()->create($couponForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Coupon" + } + } + }, + "/coupons/{redemptionCode}": { + "parameters": [ + { + "name": "redemptionCode", + "in": "path", + "description": "The Coupon's redemption code", + "required": true, + "schema": { + "type": "string" + } + } + ], + "get": { + "tags": [ + "Coupons" + ], + "summary": "Retrieve a coupon", + "description": "Retrieve a coupon with specified redemption code string\n", + "responses": { + "200": { + "description": "Coupon was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Coupon" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$coupon = $client->coupons()->load('redemptionCode');\n" + } + ] + }, + "put": { + "tags": [ + "Coupons" + ], + "summary": "Create or update a coupon with predefined redemption code", + "description": "Create or update a coupon with predefined redemption code\n", + "responses": { + "200": { + "description": "Coupon was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Coupon" + } + } + } + }, + "201": { + "description": "Coupon was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Coupon" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Coupon was redeemed already and cannot be changed" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$couponForm = new Rebilly\\Entities\\Coupons\\Coupon();\n\n$discountArray = [\n 'type' => Rebilly\\Entities\\Coupons\\Discount::TYPE_FIXED,\n 'currency' => 'USD',\n 'amount' => 1.99,\n];\n\n$discountForm = new Rebilly\\Entities\\Coupons\\Discount($discountArray);\n$couponForm->setDiscount($discountForm);\n\n$restrictionArray = [\n 'type' => Rebilly\\Entities\\Coupons\\Restriction::TYPE_DISCOUNTS_PER_REDEMPTION,\n 'quantity' => 2,\n];\n\n$restrictionForm = new Rebilly\\Entities\\Coupons\\Restriction([\n $restrictionArray,\n]);\n\n$couponForm->setRestrictions($restrictionForm);\n\ntry {\n $coupon = $client->coupons()->create($couponForm, 'redemptionCode');\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Coupon" + } + } + }, + "/coupons/{redemptionCode}/expiration": { + "parameters": [ + { + "name": "redemptionCode", + "in": "path", + "description": "The Coupon's redemption code", + "required": true, + "schema": { + "type": "string" + } + } + ], + "post": { + "tags": [ + "Coupons" + ], + "summary": "Set a coupon's expiration time.", + "description": "Set a coupon's expiry time with the specified redemption code.\nThe expiredTime of a coupon must be greater than its issuedTime.\nThis cannot be performed on expired coupons.\n", + "responses": { + "201": { + "description": "Coupon expiration was successfully set", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Coupon" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CouponExpiration" + } + } + }, + "description": "Coupon resource" + } + } + }, + "/credential-hashes/emails": { + "post": { + "tags": [ + "Credential Hashes" + ], + "summary": "Create an email credential", + "description": "Create an email credential\n", + "responses": { + "201": { + "description": "Email credential was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SmtpCredential" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SmtpCredential" + } + } + }, + "description": "Email credential resource", + "required": true + } + } + }, + "/credential-hashes/emails/{hash}": { + "parameters": [ + { + "$ref": "#/components/parameters/hash" + } + ], + "get": { + "tags": [ + "Credential Hashes" + ], + "summary": "Retrieve an email credential", + "description": "Retrieve an email credential with specified token identifier string\n", + "responses": { + "200": { + "description": "Email credential was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SmtpCredential" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/credential-hashes/webhooks": { + "post": { + "tags": [ + "Credential Hashes" + ], + "summary": "Create a webhook credential", + "description": "Create a webhook credential\n", + "responses": { + "201": { + "description": "Webhook credential was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookCredential" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookCredential" + } + } + }, + "description": "Credential resource", + "required": true + } + } + }, + "/credential-hashes/webhooks/{hash}": { + "parameters": [ + { + "$ref": "#/components/parameters/hash" + } + ], + "get": { + "tags": [ + "Credential Hashes" + ], + "summary": "Retrieve a webhook credential", + "description": "Retrieve a webhook credential with specified token identifier string\n", + "responses": { + "200": { + "description": "Webhook credential was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookCredential" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/credentials": { + "get": { + "tags": [ + "Customer Authentication" + ], + "summary": "Retrieve a list of credentials", + "description": "Retrieve a list of credentials\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of Credentials was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Credential" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$customerCredentials = $client->customerCredentials()->search([\n 'filter' => 'customerId:testCustomer',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Customer Authentication" + ], + "summary": "Create a credential", + "description": "Create a credential\n", + "responses": { + "201": { + "description": "Credential was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Credential" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$customerCredentialForm = new Rebilly\\Entities\\CustomerCredential();\n$customerCredentialForm->setCustomerId('customerId');\n$customerCredentialForm->setUsername('test');\n$customerCredentialForm->setPassword('1234');\n\ntry {\n $customerCredential = $client->customerCredentials()->create($customerCredentialForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Credential" + } + } + }, + "/credentials/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Customer Authentication" + ], + "summary": "Retrieve a credential", + "description": "Retrieve a credential with specified identifier string\n", + "responses": { + "200": { + "description": "Credential was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Credential" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$customerCredential = $client->customerCredentials()->load('credentialId');\n" + } + ] + }, + "put": { + "tags": [ + "Customer Authentication" + ], + "summary": "Create or update a credential with predefined ID", + "description": "Create or update a credential with predefined identifier string\n", + "responses": { + "200": { + "description": "Credential was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Credential" + } + } + } + }, + "201": { + "description": "Credential was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Credential" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$customerCredentialForm = new Rebilly\\Entities\\CustomerCredential();\n$customerCredentialForm->setCustomerId('customerId');\n$customerCredentialForm->setUsername('test');\n$customerCredentialForm->setPassword('1234');\n\ntry {\n $customerCredential = $client->customerCredentials()->update('credentialId', $customerCredentialForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Credential" + } + }, + "delete": { + "tags": [ + "Customer Authentication" + ], + "summary": "Delete a credential", + "description": "Delete a credential with predefined identifier string\n", + "responses": { + "204": { + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "description": "Credential was deleted" + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$client->customerCredentials()->delete('credentialId');\n" + } + ] + } + }, + "/custom-events": { + "get": { + "tags": [ + "Custom Events" + ], + "summary": "Retrieve a list of custom events", + "description": "Retrieve a list of custom events\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + }, + { + "name": "sort", + "in": "query", + "description": "The collection items sort field and order (prefix with \"-\" for descending sort).", + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "createdTime", + "-createdTime", + "handledTime", + "-handledTime", + "scheduledTime", + "-scheduledTime" + ] + } + } + } + ], + "responses": { + "200": { + "description": "A list of custom events was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomEvent" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + } + }, + "post": { + "tags": [ + "Custom Events" + ], + "summary": "Create a custom event", + "description": "Create a custom event\n", + "responses": { + "202": { + "description": "Custom event was accepted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomEvent" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/CustomEvent" + } + } + }, + "/custom-events/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Custom Events" + ], + "summary": "Retrieve a custom event", + "description": "Retrieve a custom event with predefined identifier string\n", + "responses": { + "200": { + "description": "Custom event was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomEvent" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "put": { + "tags": [ + "Custom Events" + ], + "summary": "Create a custom event with predefined ID", + "description": "Create a custom event with predefined identifier string\n", + "responses": { + "202": { + "description": "Custom event was accepted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomEvent" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "409": { + "$ref": "#/components/responses/Conflict" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/CustomEvent" + } + }, + "delete": { + "tags": [ + "Custom Events" + ], + "summary": "Delete a custom event", + "description": "Delete a custom event with predefined identifier string\n", + "responses": { + "204": { + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "description": "Custom event was deleted" + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + } + } + }, + "/custom-events/{id}/rules": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Custom Events", + "Rules" + ], + "summary": "Retrieve a list of rules for custom event", + "responses": { + "200": { + "description": "Rules were retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuleSet" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "put": { + "tags": [ + "Custom Events", + "Rules" + ], + "summary": "Update the rules for custom event", + "responses": { + "200": { + "description": "Rules were updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuleSet" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/RuleSet" + } + } + }, + "/custom-events/{id}/rules/history": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Custom Events", + "Rules" + ], + "summary": "Retrieve the change history of the set of rules for a custom event", + "description": "Retrieve the change history of the set of rules for the selected custom event.\nThe history is updated each time you change the rules.\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + }, + { + "$ref": "#/components/parameters/collectionFilter" + }, + { + "$ref": "#/components/parameters/collectionQuery" + }, + { + "$ref": "#/components/parameters/collectionSort" + }, + { + "$ref": "#/components/parameters/collectionFields" + }, + { + "$ref": "#/components/parameters/collectionExpand" + } + ], + "responses": { + "200": { + "description": "History was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RuleSetHistoryItem" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/custom-events/{id}/rules/history/{version}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + }, + { + "$ref": "#/components/parameters/rulesVersion" + } + ], + "get": { + "tags": [ + "Custom Events", + "Rules" + ], + "summary": "Retrieve the record from the change history of the set of rules for a custom event", + "description": "Retrieve the record from the change history of the set of rules for the selected custom event.\nA history record is created each time you change the rules.\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionFields" + }, + { + "$ref": "#/components/parameters/collectionExpand" + } + ], + "responses": { + "200": { + "description": "Rules version was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuleSetHistoryItem" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/custom-events/{id}/rules/versions/{version}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + }, + { + "$ref": "#/components/parameters/rulesVersion" + } + ], + "get": { + "tags": [ + "Custom Events", + "Rules" + ], + "summary": "Retrieve the version of the set of rules for a custom event", + "description": "Retrieve the version of the selected set of rules for the selected custom event.\nThe versions are created each time you change the rules.\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionFields" + }, + { + "$ref": "#/components/parameters/collectionExpand" + } + ], + "responses": { + "200": { + "description": "Rules version was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuleSetVersion" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/custom-fields/{resource}": { + "parameters": [ + { + "name": "resource", + "in": "path", + "description": "The resource type string", + "required": true, + "schema": { + "type": "string", + "enum": [ + "customers", + "payment-cards", + "subscriptions", + "transactions", + "websites", + "contacts", + "products" + ] + } + } + ], + "get": { + "tags": [ + "Custom Fields" + ], + "summary": "Retrieve Custom Fields", + "description": "Retrieve a schema of Custom Fields for the given resource type\n", + "responses": { + "200": { + "description": "A schema of Custom Fields was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "description": "The list of custom fields", + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomField" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$customFields = $client->customFields()->search([\n 'filter' => 'type:boolean',\n]);\n" + } + ] + } + }, + "/custom-fields/{resource}/{name}": { + "parameters": [ + { + "name": "resource", + "in": "path", + "description": "The resource type string", + "required": true, + "schema": { + "type": "string", + "enum": [ + "customers", + "payment-cards", + "subscriptions", + "transactions", + "websites", + "contacts", + "products" + ] + } + }, + { + "name": "name", + "in": "path", + "description": "The custom field's identifier string", + "required": true, + "schema": { + "type": "string" + } + } + ], + "get": { + "tags": [ + "Custom Fields" + ], + "summary": "Retrieve a Custom Field", + "description": "Retrieve a schema of the given Custom Field for the given resource type\n", + "responses": { + "200": { + "description": "A schema of the Custom Field was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomField" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$customField = $client->customFields()->load('customers', 'customerId');\n" + } + ] + }, + "put": { + "tags": [ + "Custom Fields" + ], + "summary": "Create or alter a Custom Field", + "description": "Create or alter a schema of the given Custom Field for the given resource type.\n", + "responses": { + "200": { + "description": "The Custom Field was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomField" + } + } + } + }, + "201": { + "description": "The Custom Fields was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomField" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "409": { + "description": "The schema is in use: remove all the associated data in order to remove or alter the schema", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$customFieldForm = new Rebilly\\Entities\\CustomField();\n$customFieldForm->setType($customFieldForm::TYPE_BOOLEAN);\n\ntry {\n $customField = $client->customFields()->update('customers', 'testFieldName', $customFieldForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomField" + } + } + }, + "description": "Custom Fields schema of the given resource type", + "required": true + } + }, + "delete": { + "tags": [ + "Custom Fields" + ], + "summary": "Delete a custom field", + "description": "Delete a custom field by its name\n", + "responses": { + "204": { + "description": "Custom field has been deleted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "The field is in use: remove all the associated data first", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$client->customFields()->delete('customers', 'testFieldName');\n" + } + ] + } + }, + "/customers": { + "get": { + "tags": [ + "Customers" + ], + "summary": "Retrieve a list of customers", + "description": "Retrieve a list of customers\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + }, + { + "$ref": "#/components/parameters/collectionFilter" + }, + { + "$ref": "#/components/parameters/collectionQuery" + }, + { + "$ref": "#/components/parameters/collectionExpand" + }, + { + "$ref": "#/components/parameters/collectionFields" + }, + { + "name": "sort", + "in": "query", + "description": "The collection items sort field and order (prefix with \"-\" for descending sort).", + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "id", + "-id", + "email", + "-email", + "createdTime", + "-createdTime", + "updatedTime", + "-updatedTime" + ] + } + } + }, + { + "name": "Accept", + "in": "header", + "description": "The response media type", + "schema": { + "type": "string", + "enum": [ + "application/json", + "text/csv" + ], + "default": "application/json" + } + } + ], + "responses": { + "200": { + "description": "A list of Customers was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Customer" + } + } + }, + "text/csv": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Customer" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$customers = $client->customers()->search([\n 'filter' => 'firstName:John',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Customers" + ], + "summary": "Create a customer", + "description": "Create a customer\n", + "responses": { + "201": { + "description": "Customer was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Customer" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$customerForm = new Rebilly\\Entities\\Customer();\n$customerForm->setFirstName('Sherlock');\n$customerForm->setLastName('Holmes');\n$customerForm->setEmail('sherlock.holmes@gmail.com');\n\ntry {\n $customer = $client->customers()->create($customerForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Customer" + } + } + }, + "/customers/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Customers" + ], + "summary": "Retrieve a customer", + "description": "Retrieve a customer with specified identifier string\n", + "responses": { + "200": { + "description": "Customer was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Customer" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$customers = $client->customers()->load('myCustomerId');\n" + } + ] + }, + "put": { + "tags": [ + "Customers" + ], + "summary": "Create a customer with predefined ID", + "description": "Create a customer with predefined identifier string\n", + "responses": { + "200": { + "description": "Customer was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Customer" + } + } + } + }, + "201": { + "description": "Customer was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Customer" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$customerForm = new Rebilly\\Entities\\Customer();\n$customerForm->setFirstName('Sherlock');\n$customerForm->setLastName('Holmes');\n$customerForm->setEmail('sherlock.holmes@gmail.com');\n\ntry {\n $customer = $client->customers()->update('myCustomerId', $customerForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Customer" + } + } + }, + "/customers/{id}/lead-source": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Customers" + ], + "summary": "Retrieve a customer's Lead Source", + "description": "Retrieve a Lead Source of given customer\n", + "responses": { + "200": { + "description": "Lead Source was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LeadSource" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$customer = $client->customers()->load('myCustomerId');\n$leadSource = $customer->getLeadSource();\n" + } + ] + }, + "put": { + "tags": [ + "Customers" + ], + "summary": "Create a Lead Source for a customer", + "description": "Create a Lead Source for a customer\n", + "responses": { + "200": { + "description": "Lead Source was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LeadSource" + } + } + } + }, + "201": { + "description": "Lead Source was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LeadSource" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$leadSourceForm = new Rebilly\\Entities\\LeadSource();\n$leadSourceForm->setSource('TestSource');\n$leadSourceForm->setCampaign('TestCampaign');\n\ntry {\n $customer = $client->customers()->updateLeadSource('myCustomerId', $leadSourceForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/LeadSource" + } + }, + "delete": { + "tags": [ + "Customers" + ], + "summary": "Delete a Lead Source for a customer", + "description": "Delete a Lead Source that belongs to a certain customer\n", + "responses": { + "204": { + "description": "Lead Source was deleted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Lead Source cannot be deleted" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$customer = $client->customers()->deleteLeadSource('myCustomerId');\n" + } + ] + } + }, + "/disputes": { + "get": { + "tags": [ + "Disputes" + ], + "summary": "Retrieve a list of disputes", + "description": "Retrieve a list of disputes\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of disputes was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Dispute" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$disputes = $client->disputes()->search([\n 'filter' => 'transactionId:testId',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Disputes" + ], + "summary": "Create a dispute", + "description": "Create a dispute\n", + "responses": { + "201": { + "description": "Dispute was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Dispute" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$disputeForm = new Rebilly\\Entities\\Dispute();\n$disputeForm->setTransactionId('transactionId');\n$disputeForm->setCurrency('USD');\n$disputeForm->setAmount(10);\n$disputeForm->setReasonCode(1000);\n$disputeForm->setType($disputeForm::TYPE_1CB);\n$disputeForm->setStatus($disputeForm::STATUS_RESPONSE_NEEDED);\n$disputeForm->setPostedTime('2025-01-01 05:00:00');\n\ntry {\n $dispute = $client->disputes()->create($disputeForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Dispute" + } + } + }, + "/disputes/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Disputes" + ], + "summary": "Retrieve a dispute", + "description": "Retrieve a dispute with specified identifier string\n", + "responses": { + "200": { + "description": "Dispute was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Dispute" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$dispute = $client->disputes()->load('disputeId');\n" + } + ] + }, + "put": { + "tags": [ + "Disputes" + ], + "summary": "Create or update a Dispute with predefined ID", + "description": "Create or update a Dispute with predefined identifier string\n", + "responses": { + "200": { + "description": "Dispute was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Dispute" + } + } + } + }, + "201": { + "description": "Dispute was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Dispute" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$disputeForm = new Rebilly\\Entities\\Dispute();\n$disputeForm->setTransactionId('transactionId');\n$disputeForm->setCurrency('USD');\n$disputeForm->setAmount(10);\n$disputeForm->setReasonCode(1000);\n$disputeForm->setType($disputeForm::TYPE_1CB);\n$disputeForm->setStatus($disputeForm::STATUS_RESPONSE_NEEDED);\n$disputeForm->setPostedTime('2025-01-01 05:00:00');\n\ntry {\n $dispute = $client->disputes()->update('disputeId', $dispute);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Dispute" + } + } + }, + "/events": { + "get": { + "tags": [ + "Events", + "Rules" + ], + "summary": "Retrieve a list of existing events", + "responses": { + "200": { + "description": "A list of System Events was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SystemEvent" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + } + } + }, + "/events/{eventType}": { + "parameters": [ + { + "$ref": "#/components/parameters/systemEventType" + } + ], + "get": { + "tags": [ + "Events", + "Rules" + ], + "summary": "Retrieve the event information", + "responses": { + "200": { + "description": "Rules were retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SystemEvent" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/events/{eventType}/rules": { + "parameters": [ + { + "$ref": "#/components/parameters/systemEventType" + } + ], + "get": { + "tags": [ + "Events", + "Rules" + ], + "summary": "Retrieve a list of rules for event", + "responses": { + "200": { + "description": "Rules were retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuleSet" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "put": { + "tags": [ + "Events", + "Rules" + ], + "summary": "Update the rules for event", + "responses": { + "200": { + "description": "Rules were updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuleSet" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/RuleSet" + } + } + }, + "/events/{eventType}/rules/history": { + "parameters": [ + { + "$ref": "#/components/parameters/systemEventType" + } + ], + "get": { + "tags": [ + "Events", + "Rules" + ], + "summary": "Retrieve the change history of the set of rules", + "description": "Retrieve the change history of the selected set of rules.\nThe history is updated each time you change the rules.\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + }, + { + "$ref": "#/components/parameters/collectionFilter" + }, + { + "$ref": "#/components/parameters/collectionQuery" + }, + { + "$ref": "#/components/parameters/collectionSort" + }, + { + "$ref": "#/components/parameters/collectionFields" + }, + { + "$ref": "#/components/parameters/collectionExpand" + } + ], + "responses": { + "200": { + "description": "History was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RuleSetHistoryItem" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/events/{eventType}/rules/history/{version}": { + "parameters": [ + { + "$ref": "#/components/parameters/systemEventType" + }, + { + "$ref": "#/components/parameters/rulesVersion" + } + ], + "get": { + "tags": [ + "Events", + "Rules" + ], + "summary": "Retrieve the record from the change history of the set of rules", + "description": "Retrieve the record from the change history of the selected set of rules.\nA history record is created each time you change the rules.\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionFields" + }, + { + "$ref": "#/components/parameters/collectionExpand" + } + ], + "responses": { + "200": { + "description": "History record was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuleSetHistoryItem" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/events/{eventType}/rules/versions/{version}": { + "parameters": [ + { + "$ref": "#/components/parameters/systemEventType" + }, + { + "$ref": "#/components/parameters/rulesVersion" + } + ], + "get": { + "tags": [ + "Events", + "Rules" + ], + "summary": "Retrieve the version of the set of rules", + "description": "Retrieve the version of the selected set of rules.\nThe versions are created each time you change the rules.\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionFields" + }, + { + "$ref": "#/components/parameters/collectionExpand" + } + ], + "responses": { + "200": { + "description": "Rules version was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuleSetVersion" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/files": { + "get": { + "tags": [ + "Files" + ], + "summary": "Retrieve a list of files", + "description": "Retrieve a list of files\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + }, + { + "$ref": "#/components/parameters/collectionFilter" + }, + { + "$ref": "#/components/parameters/collectionQuery" + }, + { + "$ref": "#/components/parameters/collectionExpand" + }, + { + "$ref": "#/components/parameters/collectionFields" + }, + { + "name": "sort", + "in": "query", + "description": "The collection items sort field and order (prefix with \"-\" for descending sort).", + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "id", + "-id", + "name", + "-name", + "extension", + "-extension", + "size", + "-size", + "width", + "-width", + "height", + "-height", + "createdTime", + "-createdTime", + "updatedTime", + "-updatedTime" + ] + } + } + } + ], + "responses": { + "200": { + "description": "A list of Files was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/File" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$files = $client->files()->search([\n 'filter' => 'name:TestFile',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Files" + ], + "summary": "Create a file", + "description": "Create a file\n", + "responses": { + "201": { + "description": "File was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/File" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$fileForm = new Rebilly\\Entities\\File();\n$fileForm->setUrl('http://test.com/somefile.jpg');\n\ntry {\n $file = $client->files()->create($fileForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "url": { + "description": "The file URL", + "type": "string" + } + } + } + } + }, + "description": "Additionally, a file can be sent with a multipart/form-data POST request or the file's raw body can be sent as a request body", + "required": true + } + } + }, + "/files/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Files" + ], + "summary": "Retrieve a File", + "description": "Retrieve a File with specified identifier string\n", + "responses": { + "200": { + "description": "File was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/File" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$file = $client->files()->load('fileId');\n" + } + ] + }, + "put": { + "tags": [ + "Files" + ], + "summary": "Update the File with predefined ID. Note that file can be uploaded with POST only.", + "description": "Update the File with predefined ID\n", + "responses": { + "200": { + "description": "File was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/File" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$fileForm = new Rebilly\\Entities\\File();\n$fileForm->setDescription('This is a test file');\n\ntry {\n $file = $client->files()->update('fileId', $fileForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/File" + } + } + }, + "description": "File resource", + "required": true + } + }, + "delete": { + "tags": [ + "Files" + ], + "summary": "Delete a File", + "description": "Delete the File with predefined identifier string\n", + "responses": { + "204": { + "description": "File was deleted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$client->files()->delete('fileId');\n" + } + ] + } + }, + "/files/{id}/download": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Files" + ], + "summary": "Retrieve a file", + "description": "Retrieve a file\n", + "responses": { + "200": { + "description": "The file was retrieved successfully", + "headers": { + "Content-Length": { + "description": "The number of bytes in the file", + "schema": { + "type": "integer" + } + }, + "Content-Type": { + "description": "The MIME type of the file", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "string", + "readOnly": true + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/files/{id}/download{extension}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + }, + { + "name": "extension", + "in": "path", + "description": "File extension which also indicates the desired file format", + "required": true, + "schema": { + "type": "string", + "enum": [ + ".png", + ".jpg" + ] + } + } + ], + "get": { + "tags": [ + "Files" + ], + "summary": "Used for converting images server-side", + "description": "Used for converting images server-side\n", + "responses": { + "200": { + "description": "The file was retrieved successfully", + "headers": { + "Content-Length": { + "description": "The number of bytes in the file", + "schema": { + "type": "integer" + } + }, + "Content-Type": { + "description": "The MIME type of the file", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "string", + "readOnly": true + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + } + } + }, + "/forgot-password": { + "post": { + "tags": [ + "Users" + ], + "summary": "Sends an email with a link containing a token to reset user password", + "description": "Sends an email with a link containing a token to reset user password\n", + "responses": { + "204": { + "description": "Email sent successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$forgotPasswordForm = new Rebilly\\Entities\\Email();\n$forgotPasswordForm->setEmail('johndoe@test.com');\n\ntry {\n $client->users()->forgotPassword($forgotPasswordForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Email" + } + } + }, + "description": "Email resource", + "required": true + } + } + }, + "/gateway-accounts": { + "get": { + "tags": [ + "Gateway Accounts" + ], + "summary": "Retrieve a list of gateway accounts", + "description": "Retrieve a list of gateway accounts\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of Gateway Accounts was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GatewayAccount" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$gatewayAccounts = $client->$gatewayAccounts()->search([\n 'filter' => 'currency:USD',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Gateway Accounts" + ], + "summary": "Create a Gateway Account", + "description": "Create a Gateway Account\n", + "responses": { + "201": { + "description": "Gateway Account was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GatewayAccount" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$gatewayAccountForm = new Rebilly\\Entities\\GatewayAccount();\n\n$gatewayAccountForm->setGatewayName('A1Gateway');\n$gatewayAccountForm->setAcquirerName('Bank of Rebilly');\n$gatewayAccountForm->setOrganizationId('organizationId');\n$gatewayAccountForm->setMerchantCategoryCode(5734);\n$gatewayAccountForm->setWebsites([\n 'websiteId1',\n 'websiteId2',\n]);\n$gatewayAccountForm->setPaymentCardSchemes([\n Rebilly\\Entities\\PaymentCardScheme::SCHEME_VISA,\n Rebilly\\Entities\\PaymentCardScheme::SCHEME_MASTERCARD,\n]);\n$gatewayAccountForm->setMethod(Rebilly\\Entities\\PaymentMethod::METHOD_CASH);\n\n$gatewayConfig = [\n 'accountId' => 'test',\n 'password' => '123',\n];\n\n$gatewayAccountForm->setGatewayConfig($gatewayConfig);\n\ntry {\n $gatewayAccount = $client->gatewayAccounts()->create($gatewayAccountForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/GatewayAccount" + } + } + }, + "/gateway-accounts/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Gateway Accounts" + ], + "summary": "Retrieve a Gateway Account", + "description": "Retrieve a Gateway Account with specified identifier string\n", + "responses": { + "200": { + "description": "Gateway Account was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GatewayAccount" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$gatewayAccount = $client->gatewayAccounts()->load('gatewayAccountId');\n" + } + ] + }, + "put": { + "tags": [ + "Gateway Accounts" + ], + "summary": "Create or update a Gateway Account with predefined ID", + "description": "Create or update a GatewayAccount with predefined identifier string\n", + "responses": { + "200": { + "description": "Gateway Account was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GatewayAccount" + } + } + } + }, + "201": { + "description": "Gateway Account was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GatewayAccount" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$gatewayAccountForm = new Rebilly\\Entities\\GatewayAccount();\n\n$gatewayAccountForm->setGatewayName('A1Gateway');\n$gatewayAccountForm->setAcquirerName('Bank of Rebilly');\n$gatewayAccountForm->setOrganizationId('organizationId');\n$gatewayAccountForm->setMerchantCategoryCode(5734);\n$gatewayAccountForm->setWebsites([\n 'websiteId1',\n 'websiteId2',\n]);\n$gatewayAccountForm->setPaymentCardSchemes([\n Rebilly\\Entities\\PaymentCardScheme::SCHEME_VISA,\n Rebilly\\Entities\\PaymentCardScheme::SCHEME_MASTERCARD,\n]);\n$gatewayAccountForm->setMethod(Rebilly\\Entities\\PaymentMethod::METHOD_CASH);\n\n$gatewayConfig = [\n 'accountId' => 'test',\n 'password' => '123',\n];\n\n$gatewayAccountForm->setGatewayConfig($gatewayConfig);\n\ntry {\n $gatewayAccount = $client->gatewayAccounts()->update('gatewayAccountId', $gatewayAccountForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/GatewayAccount" + } + }, + "patch": { + "tags": [ + "Gateway Accounts" + ], + "summary": "Update a Gateway Account with predefined ID", + "description": "Update a GatewayAccount with predefined identifier string\n", + "responses": { + "200": { + "description": "Gateway Account was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GatewayAccount" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/GatewayAccount" + } + }, + "delete": { + "tags": [ + "Gateway Accounts" + ], + "summary": "Delete a Gateway Account", + "description": "Delete a Gateway Account with predefined identifier string\n", + "responses": { + "204": { + "description": "Gateway Account was deleted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "try {\n $client->gatewayAccounts()->delete('gatewayAccountId');\n} catch (ServerException $e) {\n echo $e->getMessage();\n}\n" + } + ] + } + }, + "/invoices": { + "get": { + "tags": [ + "Invoices" + ], + "summary": "Retrieve a list of invoices", + "description": "Retrieve a list of invoices\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + }, + { + "name": "Accept", + "in": "header", + "description": "The response media type", + "schema": { + "type": "string", + "enum": [ + "application/json", + "text/csv" + ], + "default": "application/json" + } + } + ], + "responses": { + "200": { + "description": "A list of invoices was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Invoice" + } + } + }, + "text/csv": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Invoice" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$invoices = $client->invoices()->search([\n 'filter' => 'customerId:testCustomerId',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Invoices" + ], + "summary": "Create an invoice", + "description": "Create an invoice\n", + "responses": { + "201": { + "description": "Invoice was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invoice" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$invoiceForm = new Rebilly\\Entities\\Invoice();\n$invoiceForm->setCustomerId('customerId');\n$invoiceForm->setWebsiteId('websiteId');\n$invoiceForm->setCurrency('USD');\n\ntry {\n $invoice = $client->invoices()->create($invoiceForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Invoice" + } + } + }, + "/invoices/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Invoices" + ], + "summary": "Retrieve an invoice", + "description": "Retrieve an invoice with specified identifier string\n", + "parameters": [ + { + "name": "Accept", + "in": "header", + "description": "The response media type", + "schema": { + "type": "string", + "enum": [ + "application/json", + "application/pdf" + ], + "default": "application/json" + } + } + ], + "responses": { + "200": { + "description": "Invoice was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invoice" + } + }, + "application/pdf": { + "schema": { + "$ref": "#/components/schemas/Invoice" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$invoice = $client->invoices()->load('invoiceId');\n" + } + ] + }, + "put": { + "tags": [ + "Invoices" + ], + "summary": "Create or update an invoice with predefined ID", + "description": "Create or update an invoice with predefined identifier string\n", + "responses": { + "200": { + "description": "Invoice was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invoice" + } + } + } + }, + "201": { + "description": "Invoice was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invoice" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$invoiceForm = new Rebilly\\Entities\\Invoice();\n$invoiceForm->setCustomerId('customerId');\n$invoiceForm->setWebsiteId('websiteId');\n$invoiceForm->setCurrency('USD');\n\ntry {\n $invoice = $client->invoices()->update('invoiceId', $invoiceForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Invoice" + } + } + }, + "/invoices/{id}/abandon": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "post": { + "tags": [ + "Invoices" + ], + "summary": "Abandon an invoice", + "description": "Abandon an invoice with specified identifier string\n", + "responses": { + "201": { + "description": "Invoice was abandoned successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invoice" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$invoice = $client->invoices()->abandon('invoiceId');\n" + } + ] + } + }, + "/invoices/{id}/issue": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "post": { + "tags": [ + "Invoices" + ], + "summary": "Issue an invoice", + "description": "Issue an invoice with specified identifier string\n", + "responses": { + "201": { + "description": "Invoice was issued successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invoice" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$invoice = $client->invoices()->issue('invoiceId', '2025-01-01 05:00:00');\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvoiceIssue" + } + } + }, + "description": "InvoiceIssue resource", + "required": true + } + } + }, + "/invoices/{id}/items": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Invoices" + ], + "summary": "Retrieve invoice items", + "description": "Retrieve an invoice items with specified invoice identifier string\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "Invoice items were retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/InvoiceItem" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$invoiceItems = $client->invoiceItems()->search('invoiceId', [\n 'filter' => 'quantity:5',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Invoices" + ], + "summary": "Create an invoice item", + "description": "Create an invoice item\n", + "responses": { + "201": { + "description": "InvoiceItem was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvoiceItem" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$invoiceItemForm = new Rebilly\\Entities\\InvoiceItem();\n$invoiceItemForm->setType($invoiceItemForm::TYPE_DEBIT);\n$invoiceItemForm->setUnitPrice(0.99);\n$invoiceItemForm->setQuantity(5);\n\ntry {\n $invoiceItem = $client->invoiceItems()->create($invoiceItemForm, 'invoiceId');\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvoiceItem" + } + } + }, + "description": "InvoiceItem resource", + "required": true + } + } + }, + "/invoices/{id}/lead-source": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Invoices" + ], + "summary": "Retrieve an invoice's Lead Source", + "description": "Retrieve a Lead Source of given invoice\n", + "responses": { + "200": { + "description": "Lead Source was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LeadSource" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$invoice = $client->invoices()->load('invoiceId');\n$leadSource = $invoice->getLeadSource();\n" + } + ] + }, + "put": { + "tags": [ + "Invoices" + ], + "summary": "Create a Lead Source for an invoice", + "description": "Create a Lead Source for an invoice\n", + "responses": { + "200": { + "description": "Lead Source was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LeadSource" + } + } + } + }, + "201": { + "description": "Lead Source was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LeadSource" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$leadSourceForm = new Rebilly\\Entities\\LeadSource();\n$leadSourceForm->setSource('TestSource');\n$leadSourceForm->setCampaign('TestCampaign');\n\ntry {\n $invoice = $client->invoices()->updateLeadSource('invoiceId', $leadSourceForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/LeadSource" + } + }, + "delete": { + "tags": [ + "Invoices" + ], + "summary": "Delete a Lead Source for an invoice", + "description": "Delete a Lead Source that belongs to a certain invoice\n", + "responses": { + "204": { + "description": "Lead Source was deleted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Lead Source cannot be deleted" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$invoice = $client->invoices()->deleteLeadSource('invoiceId');\n" + } + ] + } + }, + "/invoices/{id}/void": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "post": { + "tags": [ + "Invoices" + ], + "summary": "Void an invoice", + "description": "Void an invoice with specified identifier string\n", + "responses": { + "201": { + "description": "Invoice was voided successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invoice" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$invoice = $client->invoices()->void('invoiceId');\n" + } + ] + } + }, + "/layouts": { + "get": { + "tags": [ + "Layouts" + ], + "summary": "Retrieve a layout list", + "description": "Retrieve a layout list\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "Layout list was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Layout" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$layouts = $client->layouts()->search([\n 'filter' => 'name:TestLayout',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Layouts" + ], + "summary": "Create a layout", + "description": "Create a layout\n", + "responses": { + "201": { + "description": "Layout was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Layout" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$layoutForm = new Rebilly\\Entities\\Layout();\n$layoutItemForm = new Rebilly\\Entities\\LayoutItem();\n\n$layoutItemForm->setPlanId('planId');\n$layoutItemForm->setStarred(false);\n\n$layoutForm->setName('TestLayout');\n$layoutForm->setLayoutItems([\n $layoutItemForm,\n]);\n\ntry {\n $layout = $client->layouts()->create($layoutForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Layout" + } + } + }, + "/layouts/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Layouts" + ], + "summary": "Retrieve a layout", + "description": "Retrieve a layout with specified identifier string\n", + "responses": { + "200": { + "description": "Layout was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Layout" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$layout = $client->layouts()->load('layoutId');\n" + } + ] + }, + "put": { + "tags": [ + "Layouts" + ], + "summary": "Create or update a layout with predefined ID", + "description": "Create or update a layout with predefined identifier string\n", + "responses": { + "200": { + "description": "Layout was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Layout" + } + } + } + }, + "201": { + "description": "Layout was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Layout" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$layoutForm = new Rebilly\\Entities\\Layout();\n$layoutItemForm = new Rebilly\\Entities\\LayoutItem();\n\n$layoutItemForm->setPlanId('planId');\n$layoutItemForm->setStarred(false);\n\n$layoutForm->setName('TestLayout');\n$layoutForm->setLayoutItems([\n $layoutItemForm,\n]);\n\ntry {\n $layout = $client->layouts()->update('layoutId', $layoutForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Layout" + } + }, + "delete": { + "tags": [ + "Layouts" + ], + "summary": "Delete a layout", + "description": "Delete a layout with predefined identifier string\n", + "responses": { + "204": { + "description": "Layout was deleted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$client->layouts()->delete('layoutId');\n" + } + ] + } + }, + "/lists": { + "get": { + "tags": [ + "Lists" + ], + "summary": "Retrieve a collection of Lists (latest version of each List)", + "description": "Retrieve a collection of Lists\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + }, + { + "$ref": "#/components/parameters/collectionFilter" + }, + { + "$ref": "#/components/parameters/collectionCriteria" + }, + { + "$ref": "#/components/parameters/collectionSort" + } + ], + "responses": { + "200": { + "description": "A collection of Lists was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/List" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + } + }, + "post": { + "tags": [ + "Lists" + ], + "summary": "Create a List", + "description": "Create a List\n", + "responses": { + "201": { + "description": "List was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/List" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/List" + } + } + }, + "/lists/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Lists" + ], + "summary": "Retrieve list's latest version", + "description": "Retrieve latest version of List with specified identifier string\n", + "responses": { + "200": { + "description": "List was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/List" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "put": { + "tags": [ + "Lists" + ], + "summary": "Create or update a list with predefined ID", + "description": "Create or update a list with predefined identifier string\n", + "responses": { + "200": { + "description": "List was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/List" + } + } + } + }, + "201": { + "description": "List was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/List" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/List" + } + }, + "delete": { + "tags": [ + "Lists" + ], + "summary": "Delete a list", + "description": "Delete a list with predefined identifier string\n", + "responses": { + "204": { + "description": "List was deleted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "List is used in Rules and cannot be deleted" + } + } + } + }, + "/lists/{id}/{version}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + }, + { + "name": "version", + "in": "path", + "required": true, + "description": "List version", + "schema": { + "type": "integer", + "minimum": 1 + } + } + ], + "get": { + "tags": [ + "Lists" + ], + "summary": "Retrieve List's exact version", + "responses": { + "200": { + "description": "List's exact version was retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/List" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/notes": { + "get": { + "tags": [ + "Notes" + ], + "summary": "Retrieve a list of notes", + "description": "Retrieve a list of notes\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of Notes was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Note" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$notes = $client->notes()->search([\n 'filter' => 'relatedType:customer',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Notes" + ], + "summary": "Create a note", + "description": "Create a note\n", + "responses": { + "201": { + "description": "Note was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Note" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$noteForm = new Rebilly\\Entities\\Note();\n$noteForm->setRelatedId('customerId');\n$noteForm->setRelatedType(Rebilly\\Entities\\ResourceType::TYPE_CUSTOMER);\n$noteForm->setContent('Test Note');\n\ntry {\n $note = $client->notes()->create($noteForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Note" + } + } + }, + "/notes/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Notes" + ], + "summary": "Retrieve a note", + "description": "Retrieve a note with specified identifier string\n", + "responses": { + "200": { + "description": "Note was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Note" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$note = $client->notes()->load('noteId');\n" + } + ] + }, + "put": { + "tags": [ + "Notes" + ], + "summary": "Create or update a note with predefined ID", + "description": "Create or update a note with predefined identifier string\n", + "responses": { + "200": { + "description": "Note was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Note" + } + } + } + }, + "201": { + "description": "Note was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Note" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$noteForm = new Rebilly\\Entities\\Note();\n$noteForm->setRelatedId('customerId');\n$noteForm->setRelatedType(Rebilly\\Entities\\ResourceType::TYPE_CUSTOMER);\n$noteForm->setContent('Test Note');\n\ntry {\n $note = $client->notes()->update('noteId', $noteForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Note" + } + } + }, + "/organizations": { + "get": { + "tags": [ + "Organizations" + ], + "summary": "Retrieve a list of organizations", + "description": "Retrieve a list of organizations\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of organizations was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Organization" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$organizations = $client->organizations()->search([\n 'filter' => 'city:Test',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Organizations" + ], + "summary": "Create a organization", + "description": "Create a organization\n", + "responses": { + "201": { + "description": "Organization was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Organization" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$organizationForm = new Rebilly\\Entities\\Organization();\n$organizationForm->setName('Test Organization');\n$organizationForm->setCountry('US');\n\ntry {\n $organization = $client->organizations()->create($organizationForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Organization" + } + } + }, + "/organizations/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Organizations" + ], + "summary": "Retrieve a organization", + "description": "Retrieve a organization with specified identifier string\n", + "responses": { + "200": { + "description": "Organization was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Organization" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$organization = $client->organizations()->load('organizationId');\n" + } + ] + }, + "put": { + "tags": [ + "Organizations" + ], + "summary": "Create or update a organization with predefined ID", + "description": "Create or update a organization with predefined identifier string\n", + "responses": { + "200": { + "description": "Organization was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Organization" + } + } + } + }, + "201": { + "description": "Organization was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Organization" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$organizationForm = new Rebilly\\Entities\\Organization();\n$organizationForm->setName('Test Organization');\n$organizationForm->setCountry('US');\n\ntry {\n $organization = $client->organizations()->update('organizationId', $organizationForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Organization" + } + }, + "delete": { + "tags": [ + "Organizations" + ], + "summary": "Delete a organization", + "description": "Delete a organization with predefined identifier string\n", + "responses": { + "204": { + "description": "Organization was deleted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Organization has related resources and cannot be deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/password-tokens": { + "get": { + "tags": [ + "Customer Authentication" + ], + "summary": "Retrieve a list of tokens", + "description": "Retrieve a list of tokens\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of Reset Password Tokens was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ResetPasswordToken" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$resetPasswordTokens = $client->resetPasswordTokens()->search([\n 'filter' => 'token:string',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Customer Authentication" + ], + "summary": "Create a Reset Password Token", + "description": "Create a Reset Password Token\n", + "responses": { + "201": { + "description": "Reset Password Token was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResetPasswordToken" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$resetPasswordTokenForm = new Rebilly\\Entities\\ResetPasswordToken();\n$resetPasswordTokenForm->setUserName('test');\n$resetPasswordTokenForm->setPassword('1234');\n\ntry {\n $$resetPasswordToken = $client->resetPasswordTokens()->create($resetPasswordTokenForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResetPasswordToken" + } + } + }, + "description": "ResetPasswordToken resource", + "required": true + } + } + }, + "/password-tokens/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Customer Authentication" + ], + "summary": "Retrieve a Reset Password Token", + "description": "Retrieve a Reset Password Token with specified identifier string\n", + "responses": { + "200": { + "description": "ResetPasswordToken was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResetPasswordToken" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$resetPasswordToken = $client->resetPasswordTokens()->load('tokenId');\n" + } + ] + }, + "delete": { + "tags": [ + "Customer Authentication" + ], + "summary": "Delete a Reset Password Token", + "description": "Delete a Reset Password Token with predefined identifier string\n", + "responses": { + "204": { + "description": "ResetPasswordToken was deleted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "ResetPasswordToken has related resources and cannot be deleted" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "try {\n $client->websites()->delete('websiteId');\n} catch (ServerException $e) {\n echo $e->getMessage();\n}\n" + } + ] + } + }, + "/payment-cards-migrations": { + "get": { + "tags": [ + "Migrate payment cards" + ], + "summary": "Retrieve a list of payment cards ready for migration", + "description": "Retrieve a list of payment cards ready for migration\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of payment cards was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PaymentCard" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$paymentCardMigrations = $client->paymentCardMigrations()->search([\n 'filter' => 'status:active',\n]);\n" + } + ] + } + }, + "/payment-cards-migrations/migrate": { + "post": { + "tags": [ + "Migrate payment cards" + ], + "summary": "Migrate payment cards to another gateway account", + "description": "Migrate payment cards to another gateway account\n", + "responses": { + "201": { + "description": "Migration command was accepted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentCardMigrationResponse" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$paymentCardMigrationForm = new Rebilly\\Entities\\PaymentCardMigrationsRequest();\n$paymentCardMigrationForm->setFromGatewayAccountId('gatewayAccountId');\n$paymentCardMigrationForm->setToGatewayAccountId('newGatewayAccountId');\n\n$paymentCardIds = [\n 'testPaymentCardId',\n 'testPaymentCardId2',\n];\n\n$paymentCardMigrationForm->setPaymentCardIds($paymentCardIds)\n\ntry {\n $paymentCardMigrationResponse = $client->paymentCardMigrations()->migrate($paymentCardMigrationForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentCardMigrationRequest" + } + } + }, + "description": "Payment card migration attributes", + "required": true + } + } + }, + "/payment-cards": { + "get": { + "tags": [ + "Payment Cards" + ], + "summary": "Retrieve a list of Payment Cards", + "description": "Retrieve a list of Payments Cards\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of Payment Card was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PaymentCard" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$paymentCards = $client->paymentCards()->search([\n 'filter' => 'status:active',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Payment Cards" + ], + "summary": "Create a Payment Card", + "description": "Create a Payment Card\n", + "responses": { + "201": { + "description": "Payment Card was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentCard" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$paymentCardForm = new Rebilly\\Entities\\PaymentCard();\n$paymentCardForm->setCustomerId('customerId');\n$paymentCardForm->setPan('4111111111111111');\n$paymentCardForm->setExpYear(2025);\n$paymentCardForm->setExpMonth(8);\n$paymentCardForm->setBillingContactId('contactId');\n\ntry {\n $paymentCard = $client->paymentCards()->create($paymentCardForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentCard" + } + } + }, + "description": "PaymentCard resource", + "required": true + } + } + }, + "/payment-cards/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Payment Cards" + ], + "summary": "Retrieve a Payment Card", + "description": "Retrieve a Payment Card with specified identifier string\n", + "responses": { + "200": { + "description": "PaymentCard was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentCard" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$paymentCard = $client->paymentCards()->load('paymentCardId');\n" + } + ] + }, + "patch": { + "tags": [ + "Payment Cards" + ], + "summary": "Update a payment card's cvv value with predefined ID", + "description": "Update a payment card's cvv value with predefined identifier string\n", + "responses": { + "200": { + "description": "Gateway Account was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentCard" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "cvv": { + "description": "Card's cvv (card verification value).", + "type": "string" + } + } + } + } + }, + "description": "Payment card", + "required": true + } + }, + "put": { + "tags": [ + "Payment Cards" + ], + "summary": "Create a payment card with predefined ID", + "responses": { + "201": { + "description": "Payment card was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentCard" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "409": { + "description": "Payment card already exists and cannot be updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$paymentCardForm = new Rebilly\\Entities\\PaymentCard();\n$paymentCardForm->setCustomerId('customerId');\n$paymentCardForm->setPan('4111111111111111');\n$paymentCardForm->setExpYear(2025);\n$paymentCardForm->setExpMonth(8);\n$paymentCardForm->setBillingContactId('contactId');\n\ntry {\n $paymentCard = $client->paymentCards()->create($paymentCardForm, 'paymentCardId');\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentCard" + } + } + }, + "description": "Payment card", + "required": true + } + } + }, + "/payment-cards/{id}/authorization": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "post": { + "tags": [ + "Payment Cards" + ], + "summary": "Authorize a Payment Card", + "description": "Authorize a Payment Card\n", + "responses": { + "201": { + "description": "Authorization successful", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentCard" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$paymentCardAuthorizationForm = new Rebilly\\Entities\\PaymentCardAuthorization();\n$paymentCardAuthorizationForm->setWebsiteId('websiteId');\n$paymentCardAuthorizationForm->setCurrency('USD');\n$paymentCardAuthorizationForm->setGatewayAccountId('gatewayAccountId');\n\ntry {\n $paymentCard = $client->paymentCards()->authorize('paymentCardId', $paymentCardAuthorizationForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "websiteId", + "currency" + ], + "properties": { + "websiteId": { + "description": "The Website ID", + "type": "string" + }, + "currency": { + "description": "Currency (three letter code)", + "type": "string" + }, + "gatewayAccountId": { + "description": "The Gateway account ID", + "type": "string" + }, + "amount": { + "description": "Amount", + "type": "number", + "format": "double" + } + } + } + } + }, + "description": "Payment Card resource", + "required": true + } + } + }, + "/payment-cards/{id}/deactivation": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "post": { + "tags": [ + "Payment Cards" + ], + "summary": "Deactivate a Payment Card", + "description": "Deactivate a Payment Card\n", + "responses": { + "201": { + "description": "Authorization successful", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentCard" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$client->paymentCards()->deactivate('paymentCardId');\n" + } + ] + } + }, + "/payments": { + "get": { + "tags": [ + "Payments" + ], + "summary": "Retrieve a payment list", + "description": "Retrieve a payment list\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + }, + { + "name": "Accept", + "in": "header", + "description": "The response media type", + "schema": { + "type": "string", + "enum": [ + "application/json", + "text/csv" + ], + "default": "application/json" + } + } + ], + "responses": { + "200": { + "description": "Payment list was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Payment" + } + } + }, + "text/csv": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Payment" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$payments = $client->payments()->search([\n 'filter' => 'currency:USD',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Payments" + ], + "summary": "Create a payment", + "description": "Create a payment\n", + "responses": { + "201": { + "description": "Payment was processed", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Payment" + } + } + } + } + }, + "202": { + "description": "Payment was accepted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Payment" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "409": { + "$ref": "#/components/responses/Conflict" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$paymentForm = new Rebilly\\Entities\\Payment();\n\n$paymentForm->setWebsiteId('websiteId');\n$paymentForm->setCustomerId('customerId');\n$paymentForm->setCurrency('USD');\n$paymentForm->setAmount(1.99);\n\n$data = [\n 'method' => Rebilly\\Entities\\PaymentMethod::METHOD_CASH,\n];\n\n$paymentInstrumentForm = new Rebilly\\Entities\\PaymentMethodInstrument($data);\n\n$paymentForm->setPaymentInstrument($paymentInstrumentForm);\n\ntry {\n $payment = $client->payments()->create($paymentForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Payment" + } + } + }, + "/payments/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Payments" + ], + "summary": "Retrieve a payment", + "description": "Retrieve a payment with specified identifier string\n", + "responses": { + "200": { + "description": "Payment was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Payment" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$payment = $client->payments()->load('paymentId');\n" + } + ] + }, + "put": { + "tags": [ + "Payments" + ], + "summary": "Create a payment with predefined ID", + "description": "Make a payment with predefined identifier string\n", + "responses": { + "201": { + "description": "Payment was processed", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Payment" + } + } + } + } + }, + "202": { + "description": "Payment was accepted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Payment" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$paymentForm = new Rebilly\\Entities\\Payment();\n\n$paymentForm->setWebsiteId('websiteId');\n$paymentForm->setCustomerId('customerId');\n$paymentForm->setCurrency('USD');\n$paymentForm->setAmount(1.99);\n\n$data = [\n 'method' => Rebilly\\Entities\\PaymentMethod::METHOD_CASH,\n];\n\n$paymentInstrumentForm = new Rebilly\\Entities\\PaymentMethodInstrument($data);\n\n$paymentForm->setPaymentInstrument($paymentInstrumentForm);\n\ntry {\n $payment = $client->payments()->update('paymentId', $paymentForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Payment" + } + } + }, + "/paypal-accounts": { + "get": { + "tags": [ + "PayPal Accounts" + ], + "summary": "Retrieve a list of PayPal accounts", + "description": "Retrieve a list of PayPal Accounts\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of PayPal Accounts was retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PayPalAccount" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + } + }, + "post": { + "tags": [ + "PayPal Accounts" + ], + "summary": "Create a PayPal Account", + "description": "Create a PayPal Account\n", + "responses": { + "201": { + "description": "PayPal Account was created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayPalAccount" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayPalAccount" + } + } + }, + "description": "PayPalAccount resource", + "required": true + } + } + }, + "/paypal-accounts/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "PayPal Accounts" + ], + "summary": "Retrieve a PayPal Account", + "description": "Retrieve a PayPal Account with specified identifier string\n", + "responses": { + "200": { + "description": "PayPal Account was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayPalAccount" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "put": { + "tags": [ + "PayPal Accounts" + ], + "summary": "Create a PayPal account with predefined ID", + "responses": { + "201": { + "description": "PayPal Account was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayPalAccount" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "409": { + "description": "PayPal Account exist and cannot be updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayPalAccount" + } + } + }, + "description": "PayPal Account", + "required": true + } + } + }, + "/paypal-accounts/{id}/activation": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "post": { + "tags": [ + "PayPal Accounts" + ], + "summary": "Activate a PayPal Account", + "description": "Activate a PayPal Account\n", + "responses": { + "201": { + "description": "Activate successful", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayPalAccount" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "websiteId", + "currency" + ], + "properties": { + "websiteId": { + "description": "The Website ID", + "type": "string" + }, + "currency": { + "description": "Currency (three letter code)", + "type": "string" + }, + "amount": { + "description": "The amount to authorize", + "type": "number", + "format": "double", + "default": 1 + }, + "redirectURLs": { + "description": "Redirect URLs", + "type": "object" + }, + "gatewayAccountId": { + "description": "The Gateway Account ID which use to send transactions", + "type": "string" + } + } + } + } + }, + "description": "PayPal Account resource", + "required": true + } + } + }, + "/paypal-accounts/{id}/deactivation": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "post": { + "tags": [ + "PayPal Accounts" + ], + "summary": "Deactivate a PayPal Account", + "description": "Deactivate a PayPal Account\n", + "responses": { + "201": { + "description": "Deactivate successful", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayPalAccount" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + } + } + }, + "/plans": { + "get": { + "tags": [ + "Plans" + ], + "summary": "Retrieve a list of plans", + "description": "Retrieve a list of plans\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of Plans was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Plan" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$plans = $client->plans()->search([\n 'filter' => 'name:TestPlan',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Plans" + ], + "summary": "Create a plan", + "description": "Create a plan\n", + "responses": { + "201": { + "description": "Plan was created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Plan" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$planForm = new Rebilly\\Entities\\Plan();\n$planForm->setName('TestPlan');\n$planForm->setCurrency('USD');\n$planForm->setTrialAmount(1);\n$planForm->setTrialPeriodUnit('day');\n$planForm->setTrialPeriodLength(1);\n\ntry {\n $plan = $client->plans()->create($planForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Plan" + } + } + }, + "/plans/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Plans" + ], + "summary": "Retrieve a plan", + "description": "Retrieve a plan with specified identifier string\n", + "responses": { + "200": { + "description": "Plan was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Plan" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$plan = $client->plans()->load('planId');\n" + } + ] + }, + "put": { + "tags": [ + "Plans" + ], + "summary": "Create or update a Plan with predefined ID", + "description": "Create or update a Plan with predefined identifier string\n", + "responses": { + "200": { + "description": "Plan was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Plan" + } + } + } + }, + "201": { + "description": "Plan was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Plan" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$planForm = new Rebilly\\Entities\\Plan();\n$planForm->setName('TestPlan');\n$planForm->setCurrency('USD');\n$planForm->setTrialAmount(1);\n$planForm->setTrialPeriodUnit('day');\n$planForm->setTrialPeriodLength(1);\n\ntry {\n $plan = $client->plans()->update('planId', $planForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Plan" + } + }, + "delete": { + "tags": [ + "Plans" + ], + "summary": "Delete a Plan", + "description": "Delete a Plan with predefined identifier string\n", + "responses": { + "204": { + "description": "Plan was deleted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$client->plans()->delete('planId');\n" + } + ] + } + }, + "/previews/rule-actions/send-email": { + "post": { + "tags": [ + "Rules" + ], + "summary": "Send a test email", + "description": "Send a test email\n", + "security": [ + { + "RebAuth": [] + } + ], + "responses": { + "200": { + "description": "Test email was sent", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SendTestEmail" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SendTestEmail" + } + } + }, + "description": "Test email resource", + "required": true + } + } + }, + "/previews/rule-actions/trigger-webhook": { + "post": { + "tags": [ + "Rules" + ], + "summary": "Trigger a test webhook", + "description": "Trigger a test webhook\n", + "security": [ + { + "RebAuth": [] + } + ], + "responses": { + "200": { + "description": "Test webhook was triggered", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SendPreviewWebhook" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SendPreviewWebhook" + } + } + }, + "description": "Test webhook resource", + "required": true + } + } + }, + "/previews/webhooks": { + "post": { + "tags": [ + "Webhooks" + ], + "summary": "Trigger a test webhook", + "description": "Trigger a test webhook\n", + "responses": { + "204": { + "description": "Test webhook was triggered", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/GlobalWebhook" + } + } + }, + "/products": { + "get": { + "tags": [ + "Products" + ], + "summary": "Retrieve a list of products", + "description": "Retrieve a list of products\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of products was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Product" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + } + }, + "post": { + "tags": [ + "Products" + ], + "summary": "Create a Product", + "description": "Create a Product\n", + "responses": { + "201": { + "description": "Product was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Product" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/Product" + } + } + }, + "/products/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Products" + ], + "summary": "Retrieve a product", + "description": "Retrieve a product with specified identifier string\n", + "responses": { + "200": { + "description": "Product was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Product" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "put": { + "tags": [ + "Products" + ], + "summary": "Create a product with predefined ID", + "description": "Create a product with predefined identifier string\n", + "responses": { + "200": { + "description": "Product was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Product" + } + } + } + }, + "201": { + "description": "Product was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Product" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/Product" + } + }, + "delete": { + "tags": [ + "Products" + ], + "summary": "Delete a product", + "description": "Delete a product with predefined identifier string\n", + "responses": { + "204": { + "description": "Product was deleted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/profile": { + "get": { + "tags": [ + "Profile" + ], + "summary": "Retrieve user's profile", + "description": "Retrieve user's profile\n", + "responses": { + "200": { + "description": "Profile was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Profile" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "put": { + "tags": [ + "Profile" + ], + "summary": "Update user's profile", + "description": "Update user's profile\n", + "responses": { + "200": { + "description": "Profile was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Profile" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Profile" + } + } + }, + "description": "Profile resource", + "required": true + } + } + }, + "/profile/password": { + "post": { + "tags": [ + "Profile" + ], + "summary": "Updates user's password with the specified newPassword", + "description": "Updates user's password with the specified newPassword. And checks if currentPassword matches the actual one.\n", + "responses": { + "201": { + "description": "Password updated successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Profile" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/UpdatePassword" + } + } + }, + "/profile/totp-reset": { + "post": { + "tags": [ + "Profile" + ], + "summary": "Reset (renew) totpSecret", + "description": "Reset (renew) totpSecret\n", + "responses": { + "201": { + "description": "totpSecret reset (renewed) successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Profile" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/queue/custom-events": { + "get": { + "tags": [ + "Custom Events" + ], + "summary": "Retrieve a list of scheduled custom events", + "description": "Retrieve a list of scheduled custom events\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of scheduled custom events was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomEvent" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + } + } + }, + "/queue/custom-events/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Custom Events" + ], + "summary": "Retrieve a scheduled custom event", + "description": "Retrieve a scheduled custom event with predefined identifier string\n", + "responses": { + "200": { + "description": "Scheduled custom event was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomEvent" + } + } + } + }, + "303": { + "description": "Custom event was successfully processed and moved out from queue", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomEvent" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "delete": { + "tags": [ + "Custom Events" + ], + "summary": "Delete a scheduled custom event", + "description": "Delete a scheduled custom event with predefined identifier string\n", + "responses": { + "204": { + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "description": "Scheduled custom event was deleted" + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/queue/payments": { + "get": { + "tags": [ + "Payments" + ], + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "summary": "Retrieve a scheduled payment list", + "description": "Retrieve a scheduled payment list\n", + "responses": { + "200": { + "description": "Successful retrieve the payments list that still waiting to be processed", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Payment" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$payments = $client->payments()->searchInQueue([\n 'filter' => 'currency:USD',\n]);\n" + } + ] + } + }, + "/queue/payments/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Payments" + ], + "summary": "Retrieve a scheduled payment", + "description": "Retrieve a payment with specified identifier string\n", + "responses": { + "200": { + "description": "Successful retrieve the payment that still waiting to be processed", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Payment" + } + } + } + }, + "303": { + "description": "Payment was successfully processed and moved out from queue", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Payment" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$payment = $client->payments()->loadFromQueue('paymentId');\n" + } + ] + }, + "put": { + "tags": [ + "Payments" + ], + "summary": "Update pending payment", + "responses": { + "200": { + "description": "Payment was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Payment" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Payment is already handled and cannot be updated" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/Payment" + } + } + }, + "/sessions": { + "get": { + "tags": [ + "Sessions" + ], + "summary": "Retrieve a list of sessions", + "description": "Retrieve a list of sessions\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of Sessions was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Session" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$sessions = $client->sessions()->search([\n 'filter' => 'userId:testUserId',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Sessions" + ], + "summary": "Create a session", + "description": "Create a session\n", + "responses": { + "201": { + "description": "Session was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$sessionForm = new Rebilly\\Entities\\Session();\n\n$permissions = [\n // Example permission to use GET and POST methods only for certain Customers\n [\n 'resourceName' => Rebilly\\Entities\\ResourceType::TYPE_CUSTOMERS,\n 'methods' => [\n $sessionForm::METHOD_GET,\n $sessionForm::METHOD_POST,\n ],\n 'resourceIds' => [\n 'testCustomerId',\n 'testCustomerId2',\n ],\n ],\n // Example permission to use all methods for Websites resource\n [\n 'resourceName' => Rebilly\\Entities\\ResourceType::TYPE_WEBSITES,\n 'methods' => [\n $sessionForm::METHOD_GET,\n $sessionForm::METHOD_POST,\n $sessionForm::METHOD_PUT,\n $sessionForm::METHOD_HEAD,\n $sessionForm::METHOD_DELETE,\n ],\n ],\n // Example permission to use all methods for all resources\n [],\n];\n\n$sessionForm->setPermissions($permissions);\n\ntry {\n $session = $client->sessions()->create($sessionForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + }, + "description": "Sessions resource", + "required": true + } + } + }, + "/sessions/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Sessions" + ], + "summary": "Retrieve a Session", + "description": "Retrieve a Session with specified identifier string\n", + "responses": { + "200": { + "description": "Session was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$session = $client->sessions()->load('sessionId');\n" + } + ] + }, + "put": { + "tags": [ + "Sessions" + ], + "summary": "Create or update a Session with predefined ID", + "description": "Create or update a Session with predefined identifier string\n", + "responses": { + "200": { + "description": "Session was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + }, + "201": { + "description": "Session was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$sessionForm = new Rebilly\\Entities\\Session();\n\n$permissions = [\n [\n 'resourceName' => Rebilly\\Entities\\ResourceType::TYPE_CUSTOMERS,\n 'methods' => [\n $sessionForm::METHOD_GET,\n $sessionForm::METHOD_POST,\n ],\n 'resourceIds' => [\n 'testCustomerId',\n 'testCustomerId2',\n ],\n ],\n [\n 'resourceName' => Rebilly\\Entities\\ResourceType::TYPE_WEBSITES,\n 'methods' => [\n $sessionForm::METHOD_GET,\n $sessionForm::METHOD_POST,\n ],\n 'resourceIds' => [\n 'testWebsiteId',\n 'testWebsiteId2',\n ],\n ],\n];\n\n$sessionForm->setPermissions($permissions);\n\ntry {\n $session = $client->sessions()->update('sessionId', $sessionForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + }, + "description": "Session resource", + "required": true + } + }, + "delete": { + "tags": [ + "Sessions" + ], + "summary": "Delete a Session", + "description": "Delete a Session with predefined identifier string\n", + "responses": { + "204": { + "description": "Session was deleted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Session has related resources and cannot be deleted" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "try {\n $client->sessions()->delete('sessionId');\n} catch (ServerException $e) {\n echo $e->getMessage();\n}\n" + } + ] + } + }, + "/shipping-zones": { + "get": { + "tags": [ + "Shipping Zones" + ], + "summary": "Retrieve a list of shipping zones", + "description": "Retrieve a list of shipping zones\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of shipping zones was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ShippingZone" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + } + }, + "post": { + "tags": [ + "Shipping Zones" + ], + "summary": "Create a Shipping Zone", + "description": "Create a Shipping Zone\n", + "responses": { + "201": { + "description": "Shipping Zone was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShippingZone" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShippingZone" + } + } + }, + "description": "Shipping Zone resource", + "required": true + } + } + }, + "/shipping-zones/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Shipping Zones" + ], + "summary": "Retrieve a shipping zone", + "description": "Retrieve a shipping zone with specified identifier string\n", + "responses": { + "200": { + "description": "Shipping zone was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShippingZone" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "put": { + "tags": [ + "Shipping Zones" + ], + "summary": "Create a shipping zone with predefined ID", + "description": "Create a shipping zone with predefined identifier string\n", + "responses": { + "200": { + "description": "Shipping zone was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShippingZone" + } + } + } + }, + "201": { + "description": "Shipping zone was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShippingZone" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShippingZone" + } + } + }, + "description": "Shipping zone resource", + "required": true + } + }, + "delete": { + "tags": [ + "Shipping Zones" + ], + "summary": "Delete a shipping zone", + "description": "Delete a shipping zone with predefined identifier string\n", + "responses": { + "204": { + "description": "Shipping zone was deleted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/signin": { + "post": { + "tags": [ + "Users", + "Sessions" + ], + "summary": "Create a session with email and password", + "description": "Create a session with email and password\n", + "security": [], + "responses": { + "201": { + "description": "Session was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$loginForm = new Rebilly\\Entities\\Login();\n$loginForm->setEmail('test@test.com');\n$loginForm->setPassword('1234');\n\ntry {\n $user = $client->users()->signin($loginForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Signin" + } + } + }, + "description": "Signin resource", + "required": true + } + } + }, + "/signup": { + "post": { + "tags": [ + "Users" + ], + "summary": "Creates a new user and sends an email confirmation", + "description": "Creates a new user and sends an email confirmation\n", + "responses": { + "201": { + "description": "User was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$signupForm = new Rebilly\\Entities\\Signup();\n$signupForm->setFirstName('John');\n$signupForm->setLastName('Doe');\n$signupForm->setEmail('johndoe@test.com');\n$signupForm->setBusinessPhone('+123456789');\n$signupForm->setPassword('1234');\n\ntry {\n $client->users()->signup($signupForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Signup" + } + } + }, + "description": "Signup resource", + "required": true + } + } + }, + "/status": { + "get": { + "tags": [ + "Status" + ], + "summary": "Retrieve API current status", + "description": "Retrieve API current status\n", + "security": [], + "responses": { + "200": { + "description": "Status was received", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + } + } + }, + "/subscriptions": { + "get": { + "tags": [ + "Subscriptions" + ], + "summary": "Retrieve a list of subscriptions", + "description": "Retrieve a list of subscriptions\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + }, + { + "name": "Accept", + "in": "header", + "description": "The response media type", + "schema": { + "type": "string", + "enum": [ + "application/json", + "text/csv" + ], + "default": "application/json" + } + } + ], + "responses": { + "200": { + "description": "A list of subscriptions was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Subscription" + } + } + }, + "text/csv": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Subscription" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$subscriptions = $client->subscriptions()->search([\n 'filter' => 'customerId:testCustomerId',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Subscriptions" + ], + "summary": "Create a subscription", + "description": "Create a subscription\n", + "responses": { + "201": { + "description": "Subscription was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Subscription" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$subscriptionForm = new Rebilly\\Entities\\Subscription();\n$subscriptionForm->setCustomerId('customerId');\n$subscriptionForm->setWebsiteId('websiteId');\n$subscriptionForm->setPlanId('planId');\n\ntry {\n $subscription = $client->subscriptions()->create($subscriptionForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Subscription" + } + } + }, + "/subscriptions/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Subscriptions" + ], + "summary": "Retrieve a subscription", + "description": "Retrieve a subscription with specified identifier string\n", + "responses": { + "200": { + "description": "Subscription was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Subscription" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$subscription = $client->subscriptions()->load('subscriptionId');\n" + } + ] + }, + "put": { + "tags": [ + "Subscriptions" + ], + "summary": "Create or update a subscription with predefined ID", + "description": "Create or update a subscription with predefined identifier string\n", + "responses": { + "200": { + "description": "Subscription was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Subscription" + } + } + } + }, + "201": { + "description": "Subscription was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Subscription" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$subscriptionForm = new Rebilly\\Entities\\Subscription();\n$subscriptionForm->setCustomerId('customerId');\n$subscriptionForm->setWebsiteId('websiteId');\n$subscriptionForm->setPlanId('planId');\n\ntry {\n $subscription = $client->subscriptions()->update('subscriptionId', $subscriptionForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Subscription" + } + } + }, + "/subscriptions/{id}/cancel": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "post": { + "tags": [ + "Subscriptions" + ], + "summary": "Cancel a subscription", + "description": "Cancel a subscription\n", + "responses": { + "201": { + "description": "Subscription was switched", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Subscription" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$subscriptionCancelForm = new Rebilly\\Entities\\SubscriptionCancel();\n$subscriptionCancelForm->setPolicy($subscriptionCancelForm::NOW);\n\ntry {\n $subscription = $client->subscriptions()->cancel('subscriptionId', $subscriptionCancelForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SubscriptionCancel" + } + } + }, + "description": "Only policy", + "required": true + } + } + }, + "/subscriptions/{id}/lead-source": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Subscriptions" + ], + "summary": "Retrieve a subscription's Lead Source", + "description": "Retrieve a Lead Source of given subscription\n", + "responses": { + "200": { + "description": "Lead Source was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LeadSource" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$subscription = $client->subscriptions()->load('subscriptionId');\n$leadSource = $subscription->getLeadSource();\n" + } + ] + }, + "put": { + "tags": [ + "Subscriptions" + ], + "summary": "Create a Lead Source for a Subscription", + "description": "Create a Lead Source for a Subscription\n", + "responses": { + "200": { + "description": "Lead Source was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LeadSource" + } + } + } + }, + "201": { + "description": "Lead Source was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LeadSource" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$leadSourceForm = new Rebilly\\Entities\\LeadSource();\n$leadSourceForm->setSource('TestSource');\n$leadSourceForm->setCampaign('TestCampaign');\n\ntry {\n $subscription = $client->subscriptions()->updateLeadSource('subscriptionId', $leadSourceForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/LeadSource" + } + }, + "delete": { + "tags": [ + "Subscriptions" + ], + "summary": "Delete a Lead Source for a Subscription", + "description": "Delete a Lead Source that belongs to a certain Subscription\n", + "responses": { + "204": { + "description": "Lead Source was deleted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Lead Source cannot be deleted" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$subscription = $client->subscriptions()->deleteLeadSource('subscriptionId');\n" + } + ] + } + }, + "/subscriptions/{id}/switch": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "post": { + "tags": [ + "Subscriptions" + ], + "summary": "Switch a subscription", + "description": "Switch a subscription\n", + "responses": { + "201": { + "description": "Subscription was switched", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Subscription" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$subscriptionSwitchForm = new Rebilly\\Entities\\SubscriptionSwitch();\n$subscriptionSwitchForm->setPlanId('newPlanId');\n$subscriptionSwitchForm->setPolicy($subscriptionSwitchForm::NOW);\n\ntry {\n $subscription = $client->subscriptions()->switchTo('subscriptionId', $subscriptionSwitchForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SubscriptionSwitch" + } + } + }, + "description": "SubscriptionSwitch resource", + "required": true + } + } + }, + "/tax-categories": { + "get": { + "tags": [ + "Taxes" + ], + "summary": "Retrieve a list of tax categories", + "description": "Retrieve a list of tax categories\n", + "responses": { + "200": { + "description": "A list of tax categories was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaxCategory" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + } + } + }, + "/tokens": { + "get": { + "tags": [ + "Payment Tokens" + ], + "summary": "Retrieve a list of tokens", + "description": "Retrieve a list of tokens\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of tokens was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PaymentToken" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$paymentCardTokens = $client->paymentCardTokens()->search([\n 'filter' => 'token:string',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Payment Tokens" + ], + "summary": "Create a payment token", + "description": "Create a token\n", + "security": [ + { + "RebAuth": [] + } + ], + "responses": { + "201": { + "description": "Token was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentToken" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$paymentCardTokenForm = new Rebilly\\Entities\\PaymentCardToken();\n$paymentCardTokenForm->setFirstName('John');\n$paymentCardTokenForm->setLastName('Doe');\n$paymentCardTokenForm->setAddress('1313 Main Street');\n$paymentCardTokenForm->setCity('Gotham');\n$paymentCardTokenForm->setPostalCode('12345');\n$paymentCardTokenForm->setRegion('NY');\n$paymentCardTokenForm->setCountry('US');\n\n$paymentInstrumentForm = new Entities\\PaymentInstruments\\PaymentCardPaymentInstrument();\n$paymentInstrumentForm->setPan('4111111111111111');\n$paymentInstrumentForm->setExpYear(2025);\n$paymentInstrumentForm->setExpMonth(8);\n$paymentInstrumentForm->setCvv(123);\n\n$paymentCardTokenForm->setPaymentInstrument($paymentInstrumentForm);\n\ntry {\n $paymentCardToken = $client->paymentCardTokens()->create($paymentCardTokenForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/PaymentToken" + } + } + }, + "/tokens/{token}": { + "parameters": [ + { + "name": "token", + "in": "path", + "description": "The token identifier string", + "required": true, + "schema": { + "type": "string" + } + } + ], + "get": { + "tags": [ + "Payment Tokens" + ], + "summary": "Retrieve a token", + "description": "Retrieve a token with specified identifier string\n", + "security": [ + { + "RebAuth": [] + } + ], + "responses": { + "200": { + "description": "Token was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentToken" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$paymentCardToken = $client->paymentCardTokens()->load('tokenId');\n" + } + ] + } + }, + "/tokens/{token}/expiration": { + "parameters": [ + { + "name": "token", + "in": "path", + "description": "The token identifier string", + "required": true, + "schema": { + "type": "string" + } + } + ], + "post": { + "tags": [ + "Payment Tokens" + ], + "summary": "Expire a token", + "description": "Expire a token\n", + "responses": { + "201": { + "description": "Token expiration successful", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentToken" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "try {\n $paymentCardToken = $client->paymentCardTokens()->expire('tokenId');\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/PaymentToken" + } + } + }, + "/tracking/api": { + "get": { + "tags": [ + "Tracking" + ], + "summary": "Retrieve a list of tracking API logs", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + }, + { + "name": "Accept", + "in": "header", + "description": "The response media type", + "schema": { + "type": "string", + "enum": [ + "application/json", + "text/csv" + ], + "default": "application/json" + } + } + ], + "responses": { + "200": { + "description": "Tracking API logs was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApiTracking" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$apiTrackingLog = $client->apiTracking()->search([\n 'filter' => 'status:200',\n]);\n" + } + ] + } + }, + "/tracking/api/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Tracking" + ], + "summary": "Retrieve a tracking API log with specified identifier string", + "responses": { + "200": { + "description": "Tracking API log was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiTracking" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$apiTrackingLog = $client->apiTracking()->load('apiLogId');\n" + } + ] + } + }, + "/tracking/lists": { + "get": { + "tags": [ + "Tracking" + ], + "summary": "Retrieve Lists changes history", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "Lists changes history was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/List" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + } + } + }, + "/tracking/subscriptions": { + "get": { + "tags": [ + "Tracking" + ], + "summary": "Retrieve a list of tracking subscription logs", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "Tracking subscription logs was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SubscriptionTracking" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + } + } + }, + "/tracking/subscriptions/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Tracking" + ], + "summary": "Retrieve a tracking subscription log with specified identifier string", + "responses": { + "200": { + "description": "Tracking subscription log was retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SubscriptionTracking" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/tracking/webhooks": { + "get": { + "tags": [ + "Tracking" + ], + "summary": "Retrieve a list of tracking webhook notifications", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + }, + { + "$ref": "#/components/parameters/collectionSort" + }, + { + "$ref": "#/components/parameters/collectionFilter" + }, + { + "$ref": "#/components/parameters/collectionCriteria" + } + ], + "responses": { + "200": { + "description": "Tracking webhook notifications was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebhookTracking" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + } + } + }, + "/tracking/webhooks/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Tracking" + ], + "summary": "Retrieve a tracking webhook notification with specified identifier string", + "responses": { + "200": { + "description": "Tracking webhook notification was retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookTracking" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/tracking/website-webhooks": { + "get": { + "tags": [ + "Tracking" + ], + "summary": "Retrieve a list of tracking webhook notifications", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "Tracking webhook notifications was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebsiteWebhookTracking" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + } + } + }, + "/tracking/website-webhooks/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Tracking" + ], + "summary": "Retrieve a tracking webhook notification with specified identifier string", + "responses": { + "200": { + "description": "Tracking webhook notification was retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebsiteWebhookTracking" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/transactions": { + "get": { + "tags": [ + "Transactions" + ], + "summary": "Retrieve a list of transactions", + "description": "Retrieve a list of transactions\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + }, + { + "$ref": "#/components/parameters/collectionFilter" + }, + { + "$ref": "#/components/parameters/collectionQuery" + }, + { + "$ref": "#/components/parameters/collectionCriteria" + }, + { + "$ref": "#/components/parameters/collectionSort" + }, + { + "name": "Accept", + "in": "header", + "description": "The response media type", + "schema": { + "type": "string", + "enum": [ + "application/json", + "text/csv" + ], + "default": "application/json" + } + } + ], + "responses": { + "200": { + "description": "A list of transactions was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Transaction" + } + } + }, + "text/csv": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Transaction" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$transactions = $client->transactions()->search([\n 'filter' => 'result:approved',\n]);\n" + } + ] + } + }, + "/transactions/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Transactions" + ], + "summary": "Retrieve a Transaction", + "description": "Retrieve a Transaction with specified identifier string\n", + "responses": { + "200": { + "description": "Transaction was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Transaction" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$transaction = $client->transactions()->load('transactionId');\n" + } + ] + } + }, + "/transactions/{id}/cancel": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "post": { + "tags": [ + "Transactions" + ], + "summary": "Cancel a pending or suspended transaction", + "description": "Cancel a scheduled transaction. Once handled a transaction cannot be canceled", + "responses": { + "201": { + "description": "Successful cancel the payment", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Transaction" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "try {\n $payment = $client->transactions()->cancel('transactionId');\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ] + } + }, + "/transactions/{id}/gateway-logs": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Transactions" + ], + "summary": "Retrieve a Transaction Gateway Logs", + "description": "Retrieve Gateway communication Logs for Transaction with specified identifier string", + "responses": { + "200": { + "description": "Logs were retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionGatewayLog" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/transactions/{id}/lead-source": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Transactions" + ], + "summary": "Retrieve a transaction's Lead Source", + "description": "Retrieve a Lead Source of given transaction\n", + "responses": { + "200": { + "description": "Lead Source was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LeadSource" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$transaction = $client->transactions()->load('transactionId');\n$leadSource = $transaction->getLeadSource();\n" + } + ] + }, + "put": { + "tags": [ + "Transactions" + ], + "summary": "Create a Lead Source for a transaction", + "description": "Create a Lead Source for a transaction\n", + "responses": { + "200": { + "description": "Lead Source was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LeadSource" + } + } + } + }, + "201": { + "description": "Lead Source was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LeadSource" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$leadSourceForm = new Rebilly\\Entities\\LeadSource();\n$leadSourceForm->setSource('TestSource');\n$leadSourceForm->setCampaign('TestCampaign');\n\ntry {\n $transaction = $client->transactions()->updateLeadSource('transactionId', $leadSourceForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/LeadSource" + } + }, + "delete": { + "tags": [ + "Transactions" + ], + "summary": "Delete a Lead Source for a transaction", + "description": "Delete a Lead Source that belongs to a certain transaction\n", + "responses": { + "204": { + "description": "Lead Source was deleted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Lead Source cannot be deleted" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$payment = $client->transactions()->deleteLeadSource('transactionId');\n" + } + ] + } + }, + "/transactions/{id}/refund": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "post": { + "tags": [ + "Transactions" + ], + "summary": "Refund a Transaction", + "description": "Refund a Transaction with specified identifier string.\nNote that the refund will be in the same currency as the original transaction.\n", + "responses": { + "201": { + "description": "Transaction was refunded successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Transaction" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$transaction = $client->transactions()->refund('transactionId', 1.99);\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRefund" + } + } + }, + "description": "Transaction resource", + "required": true + } + } + }, + "/users": { + "get": { + "tags": [ + "Users" + ], + "summary": "Retrieve a list of users", + "description": "Retrieve a list of users\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of users was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$users = $client->users()->search([\n 'filter' => 'firstName:John',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Users" + ], + "summary": "Create an user", + "description": "Create an user\n", + "responses": { + "201": { + "description": "User was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$userForm = new Rebilly\\Entities\\User();\n$userForm->setFirstName('John');\n$userForm->setLastName('Doe');\n$userForm->setEmail('johndoe@test.com');\n\ntry {\n $user = $client->users()->create($userForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/User" + } + } + }, + "/users/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Users" + ], + "summary": "Retrieve user", + "description": "Retrieve user with specified identifier string\n", + "responses": { + "200": { + "description": "User was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$user = $client->users()->load('userId');\n" + } + ] + }, + "put": { + "tags": [ + "Users" + ], + "summary": "Create or update user with predefined ID", + "description": "Create or update user with predefined identifier string\n", + "responses": { + "200": { + "description": "User was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "201": { + "description": "User was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$userForm = new Rebilly\\Entities\\User();\n$userForm->setFirstName('John');\n$userForm->setLastName('Doe');\n$userForm->setEmail('johndoe@test.com');\n\ntry {\n $user = $client->users()->update('userId', $userForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/User" + } + }, + "delete": { + "tags": [ + "Users" + ], + "summary": "Delete user", + "description": "Delete user with predefined identifier string\n", + "responses": { + "204": { + "description": "User was deleted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "try {\n $client->users()->delete('userId');\n} catch (ServerException $e) {\n echo $e->getMessage();\n}\n" + } + ] + } + }, + "/users/{id}/password": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "post": { + "tags": [ + "Users" + ], + "summary": "Updates user's password with the specified newPassword", + "description": "Updates user's password with the specified newPassword. And checks if currentPassword matches the actual one.\n", + "responses": { + "201": { + "description": "Password updated successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "$ref": "#/components/responses/InvalidDataError" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$updatePasswordForm = new Rebilly\\Entities\\UpdatePassword();\n$updatePasswordForm->setCurrentPassword('1234');\n$updatePasswordForm->setNewPassword('5678');\n\ntry {\n $user = $client->users()->updatePassword('userId', $updatePasswordForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/UpdatePassword" + } + } + }, + "/users/{id}/totp-reset": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "post": { + "tags": [ + "Users" + ], + "summary": "Reset (renew) totpSecret", + "description": "Reset (renew) totpSecret\n", + "responses": { + "201": { + "description": "totpSecret reset (renewed) successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$user = $client->users()->resetTotp('userId');\n" + } + ] + } + }, + "/users/reset-password/{token}": { + "parameters": [ + { + "name": "token", + "in": "path", + "description": "The token string", + "required": true, + "schema": { + "type": "string" + } + } + ], + "post": { + "tags": [ + "Users" + ], + "summary": "Reset user password", + "description": "Reset user password\n", + "responses": { + "201": { + "description": "Password was reseted successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$resetPasswordForm = new Rebilly\\Entities\\ResetPassword();\n$resetPasswordForm->setNewPassword('1234');\n\ntry {\n $user = $client->users()->resetPassword('userId', 'token', $resetPasswordForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResetPassword" + } + } + }, + "description": "ResetPassword resource", + "required": true + } + } + }, + "/webhooks": { + "get": { + "tags": [ + "Webhooks" + ], + "summary": "Retrieve a list of webhooks", + "description": "Retrieve a list of webhooks\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + } + ], + "responses": { + "200": { + "description": "A list of Webhooks was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GlobalWebhook" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + } + }, + "post": { + "tags": [ + "Webhooks" + ], + "summary": "Create a webhook", + "description": "Create a webhook\n", + "responses": { + "201": { + "description": "Webhook was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GlobalWebhook" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/GlobalWebhook" + } + } + }, + "/webhooks/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Webhooks" + ], + "summary": "Retrieve a webhook", + "description": "Retrieve a webhook with specified identifier string\n", + "responses": { + "200": { + "description": "Webhook was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GlobalWebhook" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "put": { + "tags": [ + "Webhooks" + ], + "summary": "Create or update a webhook with predefined ID", + "description": "Create or update a webhook with predefined identifier string\n", + "responses": { + "200": { + "description": "Webhook was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GlobalWebhook" + } + } + } + }, + "201": { + "description": "Webhook was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GlobalWebhook" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/GlobalWebhook" + } + } + }, + "/websites": { + "get": { + "tags": [ + "Websites" + ], + "summary": "Retrieve a list of websites", + "description": "Retrieve a list of websites\n", + "parameters": [ + { + "$ref": "#/components/parameters/collectionLimit" + }, + { + "$ref": "#/components/parameters/collectionOffset" + }, + { + "name": "Accept", + "in": "header", + "description": "The response media type", + "schema": { + "type": "string", + "enum": [ + "application/json", + "text/csv" + ], + "default": "application/json" + } + } + ], + "responses": { + "200": { + "description": "A list of Websites was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + }, + "Pagination-Total": { + "description": "Total items count", + "schema": { + "type": "integer" + } + }, + "Pagination-Limit": { + "description": "Items per page limit", + "schema": { + "type": "integer" + } + }, + "Pagination-Offset": { + "description": "Pagination offset", + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Website" + } + } + }, + "text/csv": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Website" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$websites = $client->websites()->search([\n 'filter' => 'name:TestWebsite',\n]);\n" + } + ] + }, + "post": { + "tags": [ + "Websites" + ], + "summary": "Create a website", + "description": "Create a website\n", + "responses": { + "201": { + "description": "Website was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Website" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$websiteForm = new Rebilly\\Entities\\Website();\n$websiteForm->setName('TestWebsite');\n$websiteForm->setUrl('http://testwebsite.com');\n$websiteForm->setServicePhone('+0123456789');\n$websiteForm->setServiceEmail('test@testwebsite.com');\n\ntry {\n $website = $client->websites()->create($websiteForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Website" + } + } + }, + "/websites/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Websites" + ], + "summary": "Retrieve a website", + "description": "Retrieve a website with specified identifier string\n", + "responses": { + "200": { + "description": "Website was retrieved successfully", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Website" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$website = $client->websites()->load('websiteId');\n" + } + ] + }, + "put": { + "tags": [ + "Websites" + ], + "summary": "Create or update a website with predefined ID", + "description": "Create or update a website with predefined identifier string\n", + "responses": { + "200": { + "description": "Website was updated", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Website" + } + } + } + }, + "201": { + "description": "Website was created", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Website" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$websiteForm = new Rebilly\\Entities\\Website();\n$websiteForm->setName('TestWebsite');\n$websiteForm->setUrl('http://testwebsite.com');\n$websiteForm->setServicePhone('+0123456789');\n$websiteForm->setServiceEmail('test@testwebsite.com');\n\ntry {\n $website = $client->websites()->update('websiteId', $websiteForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Website" + } + }, + "delete": { + "tags": [ + "Websites" + ], + "summary": "Delete a website", + "description": "Delete a website with predefined identifier string\n", + "responses": { + "204": { + "description": "Website was deleted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "description": "Website has related resources and cannot be deleted" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "try {\n $client->websites()->delete('websiteId');\n} catch (ServerException $e) {\n echo $e->getMessage();\n}\n" + } + ] + } + }, + "/websites/{id}/webhook": { + "parameters": [ + { + "$ref": "#/components/parameters/resourceId" + } + ], + "get": { + "tags": [ + "Websites" + ], + "summary": "Retrieve a webhook for website", + "description": "Retrieve a webhook for website with specified identifier string\n", + "responses": { + "200": { + "description": "Webhook was retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebsiteWebhook" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$websiteWebhook = $client->websiteWebhook()->load('websiteId');\n" + } + ] + }, + "put": { + "tags": [ + "Websites" + ], + "summary": "Create or update a webhook for website with predefined ID", + "description": "Create or update a webhook for website with predefined identifier string\n", + "responses": { + "200": { + "description": "Webhook was updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebsiteWebhook" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$websiteWebhookForm = new Rebilly\\Entities\\WebsiteWebhook();\n$websiteWebhookForm->setWebHookUrl('http://testwebsite.com/webhook');\n$websiteWebhookForm->setWebHookUsername('test');\n$websiteWebhookForm->setWebHookPassword('1234');\n\ntry {\n $website = $client->websiteWebhook()->update('websiteId', $websiteWebhookForm);\n} catch (UnprocessableEntityException $e) {\n echo $e->getMessage();\n}\n" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebsiteWebhook" + } + } + }, + "description": "Webhook resource", + "required": true + } + }, + "delete": { + "tags": [ + "Websites" + ], + "summary": "Delete a webhook", + "description": "Delete a webhook that belongs to a website with predefined ID\n", + "responses": { + "204": { + "description": "Webhook was deleted", + "headers": { + "Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Remaining": { + "description": "The number of remaining requests in the current period", + "schema": { + "type": "integer" + } + }, + "Rate-Limit-Reset": { + "description": "The date in format defined by [RFC 822](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)\nwhen the current period will reset\n", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/AccessForbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "x-code-samples": [ + { + "lang": "PHP", + "source": "$client->websiteWebhook()->delete('websiteId');\n" + } + ] + } + } + }, + "components": { + "schemas": { + "ApiKey": { + "type": "object", + "description": "API secret Key.", + "properties": { + "id": { + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "description": { + "description": "API key description", + "type": "string" + }, + "datetimeFormat": { + "description": "Date time format", + "type": "string", + "default": "iso8601", + "enum": [ + "mysql", + "iso8601" + ] + }, + "apiUser": { + "description": "API user name", + "type": "string", + "readOnly": true + }, + "secretKey": { + "description": "API secret key's value", + "type": "string", + "readOnly": true + }, + "createdTime": { + "description": "The API key created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "Attachment": { + "type": "object", + "required": [ + "fileId", + "relatedId", + "relatedType" + ], + "properties": { + "id": { + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "fileId": { + "description": "Linked File object id", + "type": "string" + }, + "relatedType": { + "description": "Linked object type", + "type": "string", + "enum": [ + "customer", + "dispute", + "invoice", + "note", + "payment", + "plan", + "product", + "subscription", + "transaction" + ] + }, + "relatedId": { + "description": "Linked object Id", + "type": "string" + }, + "name": { + "description": "The Original Attachment name", + "type": "string" + }, + "description": { + "description": "The Attachment description", + "type": "string" + }, + "createdTime": { + "description": "Creation date/time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "Latest update date/time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 3, + "maxItems": 3, + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/SelfLink" + }, + { + "$ref": "#/components/schemas/FileLink" + }, + { + "$ref": "#/components/schemas/AttachmentResourceLink" + } + ] + } + } + } + }, + "AuthenticationOptions": { + "type": "object", + "properties": { + "passwordPattern": { + "description": "Allowed password pattern", + "type": "string" + }, + "credentialTtl": { + "description": "The default lifetime of the credential in seconds", + "type": "integer" + }, + "authTokenTtl": { + "description": "The default lifetime of the auth-token in seconds", + "type": "integer" + }, + "resetTokenTtl": { + "description": "The default lifetime of the reset-token in seconds", + "type": "integer" + } + } + }, + "AuthenticationToken": { + "type": "object", + "required": [ + "username", + "password" + ], + "properties": { + "token": { + "description": "The token identifier string", + "type": "string", + "readOnly": true + }, + "username": { + "description": "The token's username", + "type": "string" + }, + "password": { + "description": "The token's password (write-only)", + "type": "string", + "format": "password" + }, + "credentialId": { + "description": "The credential's ID", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "customerId": { + "description": "The token's customer ID", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "expiredTime": { + "description": "Token's expired time", + "type": "string", + "format": "date-time" + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "BankAccount": { + "type": "object", + "properties": { + "id": { + "description": "The bank account identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "customerId": { + "description": "The Customer's ID.", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "bankName": { + "description": "Bank's name.", + "type": "string" + }, + "routingNumber": { + "description": "Bank's Routing Number. Required if bank account is not created from Token. This field is write-only", + "type": "string" + }, + "accountNumber": { + "description": "Bank's Account Number. Required if bank account is not created from Token. This field is write-only", + "type": "string" + }, + "accountType": { + "description": "Banks's Account type. Required if bank account is not created from Token", + "type": "string" + }, + "token": { + "description": "Bank Account Token. Use without any other fields", + "type": "string" + }, + "address": { + "description": "The Address. Required if bank account is not created from Token", + "allOf": [ + { + "$ref": "#/components/schemas/ContactObject" + } + ] + }, + "status": { + "description": "Bank Account status", + "type": "string", + "enum": [ + "active", + "deactivated" + ] + }, + "createdTime": { + "description": "Bank Account created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "Bank Account updated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "customFields": { + "$ref": "#/components/schemas/ResourceCustomFields" + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 3, + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/SelfLink" + }, + { + "$ref": "#/components/schemas/CustomerLink" + }, + { + "$ref": "#/components/schemas/ContactLink" + } + ] + } + } + } + }, + "Blacklist": { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "id": { + "description": "The blacklist identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "type": { + "description": "The blacklist type", + "type": "string", + "enum": [ + "payment-card-id", + "customer-id", + "email", + "ip-address", + "country", + "fingerprint", + "bin" + ] + }, + "value": { + "description": "The blacklist value", + "type": "string" + }, + "expiredTime": { + "description": "The blacklist expired time", + "type": "string", + "format": "date-time" + }, + "createdTime": { + "description": "The blacklist created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "The blacklist updated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "CheckoutPage": { + "type": "object", + "required": [ + "name", + "planId", + "websiteId", + "uriPath" + ], + "properties": { + "id": { + "description": "Checkout page identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "planId": { + "description": "Checkout page plan ID", + "type": "string", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "websiteId": { + "description": "Checkout page website ID", + "type": "string", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "uriPath": { + "description": "Your own custom uri path for this Checkout Page. It will be appended to checkout url https://checkout.rebilly.com/website/", + "type": "string" + }, + "name": { + "description": "Checkout page name", + "type": "string" + }, + "isActive": { + "description": "If checkout page active", + "type": "boolean" + }, + "redirectUrl": { + "description": "Checkout page url", + "type": "string" + }, + "redirectTimeout": { + "description": "Checkout page redirect timeout", + "type": "integer" + }, + "allowCustomCustomerId": { + "description": "If to enable your own customer ID in requests", + "type": "boolean" + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "none": { + "allOf": [ + { + "$ref": "#/components/schemas/AmountAdjustment" + }, + { + "$ref": "#/components/schemas/SmtpAuthorization" + }, + { + "$ref": "#/components/schemas/WebhookAuthorization" + } + ] + }, + "Contact": { + "type": "object", + "properties": { + "id": { + "description": "The contact identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "customerId": { + "description": "The contact customer ID", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "firstName": { + "description": "The contact first name", + "type": "string" + }, + "lastName": { + "description": "The contact last name", + "type": "string" + }, + "organization": { + "description": "The contact organization", + "type": "string" + }, + "address": { + "description": "The contact street address", + "type": "string", + "maxLength": 60 + }, + "address2": { + "description": "The contact street address (second line)", + "type": "string", + "maxLength": 60 + }, + "city": { + "description": "The contact city", + "type": "string", + "maxLength": 45 + }, + "region": { + "description": "The contact region (state)", + "type": "string", + "maxLength": 45 + }, + "country": { + "description": "The contact country ISO Alpha-2 code", + "type": "string", + "pattern": "^[A-Z]{2}$" + }, + "postalCode": { + "description": "The contact postal code", + "type": "string", + "maxLength": 10 + }, + "phoneNumbers": { + "$ref": "#/components/schemas/ContactPhoneNumbers" + }, + "emails": { + "$ref": "#/components/schemas/ContactEmails" + }, + "isOutdated": { + "description": "Is contact outdated", + "type": "boolean", + "readOnly": true + }, + "createdTime": { + "description": "The contact created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "The contact updated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "customFields": { + "$ref": "#/components/schemas/ResourceCustomFields" + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 2, + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/SelfLink" + }, + { + "$ref": "#/components/schemas/CustomerLink" + } + ] + } + } + } + }, + "ContactEmails": { + "description": "The contact emails", + "type": "array", + "items": { + "type": "object", + "required": [ + "label", + "value" + ], + "properties": { + "label": { + "description": "The email label", + "type": "string" + }, + "value": { + "description": "The email value", + "type": "string" + }, + "primary": { + "description": "True if email is primary", + "type": "boolean" + } + } + } + }, + "ContactObject": { + "type": "object", + "properties": { + "firstName": { + "description": "The contact first name", + "type": "string" + }, + "lastName": { + "description": "The contact last name", + "type": "string" + }, + "organization": { + "description": "The contact organization", + "type": "string" + }, + "address": { + "description": "The contact street address", + "type": "string", + "maxLength": 60 + }, + "address2": { + "description": "The contact street address (second line)", + "type": "string", + "maxLength": 60 + }, + "city": { + "description": "The contact city", + "type": "string", + "maxLength": 45 + }, + "region": { + "description": "The contact region (state)", + "type": "string", + "maxLength": 45 + }, + "country": { + "description": "The contact country ISO Alpha-2 code", + "type": "string", + "pattern": "^[A-Z]{2}$" + }, + "postalCode": { + "description": "The contact postal code", + "type": "string", + "maxLength": 10 + }, + "phoneNumbers": { + "$ref": "#/components/schemas/ContactPhoneNumbers" + }, + "emails": { + "$ref": "#/components/schemas/ContactEmails" + } + } + }, + "ContactPhoneNumbers": { + "description": "The contact phone numbers", + "type": "array", + "items": { + "type": "object", + "required": [ + "label", + "value" + ], + "properties": { + "label": { + "description": "The phone label", + "type": "string" + }, + "value": { + "description": "The phone value", + "type": "string" + }, + "primary": { + "description": "True if phone is primary", + "type": "boolean" + } + } + } + }, + "Coupon": { + "type": "object", + "description": "Coupons and Discounts", + "required": [ + "discount", + "issuedTime" + ], + "properties": { + "redemptionCode": { + "description": "Coupon's redemption code", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "discount": { + "$ref": "#/components/schemas/Discount" + }, + "restrictions": { + "description": "Coupon restrictions", + "type": "array", + "items": { + "$ref": "#/components/schemas/CouponRestriction" + } + }, + "redemptionsCount": { + "type": "integer", + "description": "Coupon's redemptions count", + "minimum": 0, + "readOnly": true + }, + "status": { + "type": "string", + "description": "If coupon enabled", + "readOnly": true, + "enum": [ + "issued", + "expired" + ] + }, + "description": { + "type": "string", + "description": "Your coupon description. When it is not empty this is used for invoice discount item description,\notherwise the item's description uses coupon's redemptionCode like 'Coupon \"redemptionCode\"'\n" + }, + "issuedTime": { + "description": "Coupon's issued time (start time)", + "type": "string", + "format": "date-time" + }, + "expiredTime": { + "description": "Coupon's expire time (end time)", + "type": "string", + "format": "date-time" + }, + "createdTime": { + "description": "Coupon created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "Coupon updated time.", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "CouponExpiration": { + "type": "object", + "required": [ + "expiredTime" + ], + "properties": { + "expiredTime": { + "description": "The coupon's expiry time, must be greater than the issued time. Null or empty string will immediately expire the coupon.", + "type": "string", + "format": "date-time" + } + } + }, + "CouponRedemption": { + "type": "object", + "description": "Coupons redemption log", + "properties": { + "id": { + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "redemptionCode": { + "description": "Coupon's redemption code", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "customerId": { + "description": "Customer's ID", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "additionalRestrictions": { + "description": "Additional restrictions for coupon's redemptions", + "type": "array", + "items": { + "$ref": "#/components/schemas/RedemptionRestriction" + } + }, + "redeemedTime": { + "description": "Coupon redeem time", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "canceledTime": { + "description": "Coupon redemption canceled time", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "CouponRestriction": { + "description": "Coupon restrictions", + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/GenericRestriction" + }, + { + "type": "object", + "properties": { + "type": { + "description": "Coupon's restriction type", + "type": "string", + "enum": [ + "discounts-per-redemption", + "redemptions-per-customer", + "restrict-to-invoices", + "restrict-to-plans", + "restrict-to-subscriptions", + "minimum-order-amount", + "total-redemptions" + ] + } + } + } + ] + }, + "Discount": { + "type": "object", + "discriminator": { + "propertyName": "type" + }, + "properties": { + "type": { + "description": "Discount type", + "type": "string", + "enum": [ + "fixed", + "percent" + ] + } + } + }, + "discounts-per-redemption": { + "description": "discounts-per-redemption restrictions", + "allOf": [ + { + "$ref": "#/components/schemas/GenericRestriction" + }, + { + "type": "object", + "required": [ + "quantity" + ], + "properties": { + "quantity": { + "type": "integer", + "description": "Restriction quantity" + } + } + } + ] + }, + "fixed": { + "description": "Coupon fixed amount discount", + "allOf": [ + { + "$ref": "#/components/schemas/Discount" + }, + { + "type": "object", + "required": [ + "amount", + "currency" + ], + "properties": { + "amount": { + "description": "Discount amount", + "type": "number", + "format": "double" + }, + "currency": { + "description": "Discount currency", + "type": "string" + } + } + } + ] + }, + "GenericRestriction": { + "description": "All restriction", + "type": "object", + "discriminator": { + "propertyName": "type" + }, + "properties": { + "type": { + "description": "Restriction type", + "type": "string" + } + } + }, + "InvoiceDiscount": { + "type": "object", + "readOnly": true, + "properties": { + "redemptionCode": { + "description": "Coupon's redemption code", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "redemptionId": { + "description": "Redemption ID", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "amount": { + "description": "Total amount that was discounted by this Coupon", + "type": "number", + "format": "double" + }, + "description": { + "type": "string", + "description": "Discount description" + } + } + }, + "minimum-order-amount": { + "description": "minimum-order-amount restrictions", + "allOf": [ + { + "$ref": "#/components/schemas/GenericRestriction" + }, + { + "type": "object", + "required": [ + "amount", + "currency" + ], + "properties": { + "amount": { + "type": "integer", + "description": "Minimum order quantity" + }, + "currency": { + "type": "string", + "description": "Minimum order currency" + } + } + } + ] + }, + "percent": { + "description": "Coupon percent discount", + "allOf": [ + { + "$ref": "#/components/schemas/Discount" + }, + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "description": "Discount percent", + "type": "number", + "format": "double" + } + } + } + ] + }, + "RedemptionRestriction": { + "description": "Redemption restrictions", + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/GenericRestriction" + }, + { + "type": "object", + "properties": { + "type": { + "description": "Redemption's additional restriction type", + "type": "string", + "enum": [ + "discounts-per-redemption", + "restrict-to-invoices", + "restrict-to-plans", + "restrict-to-subscriptions", + "minimum-order-amount" + ] + } + } + } + ] + }, + "redemptions-per-customer": { + "description": "Quantity per Customer restrictions", + "allOf": [ + { + "$ref": "#/components/schemas/GenericRestriction" + }, + { + "type": "object", + "required": [ + "quantity" + ], + "properties": { + "quantity": { + "type": "integer", + "description": "Restriction value" + } + } + } + ] + }, + "restrict-to-invoices": { + "description": "restrict-to-invoices restrictions", + "allOf": [ + { + "$ref": "#/components/schemas/GenericRestriction" + }, + { + "type": "object", + "required": [ + "invoiceIds" + ], + "properties": { + "invoiceIds": { + "type": "array", + "description": "Invoice IDs coupon can be applied to", + "items": { + "type": "string" + } + } + } + } + ] + }, + "restrict-to-plans": { + "description": "restrict-to-plans restrictions", + "allOf": [ + { + "$ref": "#/components/schemas/GenericRestriction" + }, + { + "type": "object", + "required": [ + "planIds" + ], + "properties": { + "planIds": { + "type": "array", + "description": "Plan IDs coupon can be applied to", + "items": { + "type": "string" + } + } + } + } + ] + }, + "restrict-to-subscriptions": { + "description": "restrict-to-subscriptions restrictions", + "allOf": [ + { + "$ref": "#/components/schemas/GenericRestriction" + }, + { + "type": "object", + "required": [ + "subscriptionIds" + ], + "properties": { + "subscriptionIds": { + "type": "array", + "description": "Subscription IDs coupon can be applied to", + "items": { + "type": "string" + } + } + } + } + ] + }, + "total-redemptions": { + "description": "total-redemptions restrictions", + "allOf": [ + { + "$ref": "#/components/schemas/GenericRestriction" + }, + { + "type": "object", + "required": [ + "quantity" + ], + "properties": { + "quantity": { + "type": "integer", + "description": "Total redemptions quantity" + } + } + } + ] + }, + "Credential": { + "type": "object", + "required": [ + "username", + "password", + "customerId" + ], + "properties": { + "id": { + "description": "The credential identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "username": { + "description": "Credential's username", + "type": "string" + }, + "password": { + "description": "The credential's password", + "type": "string", + "format": "password" + }, + "customerId": { + "description": "The credential's customer ID", + "type": "string" + }, + "expiredTime": { + "description": "The credential's expired time", + "type": "string", + "format": "date-time" + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 2, + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/SelfLink" + }, + { + "$ref": "#/components/schemas/CustomerLink" + } + ] + } + } + } + }, + "Condition": { + "type": "object", + "discriminator": { + "propertyName": "op" + }, + "properties": { + "op": { + "type": "string", + "description": "The condition operation", + "enum": [ + "and", + "or", + "not", + "between", + "equals", + "in", + "gt", + "gte", + "lt", + "lte" + ] + } + }, + "required": [ + "op" + ] + }, + "and": { + "type": "object", + "description": "Logical AND", + "allOf": [ + { + "$ref": "#/components/schemas/Condition" + }, + { + "$ref": "#/components/schemas/logical" + } + ] + }, + "between": { + "type": "object", + "description": "Between condition", + "allOf": [ + { + "$ref": "#/components/schemas/Condition" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "min": { + "type": "string" + }, + "max": { + "type": "string" + } + }, + "required": [ + "path", + "min", + "max" + ] + } + ] + }, + "compare": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "path", + "value" + ] + }, + "equals": { + "type": "object", + "description": "Equals condition", + "allOf": [ + { + "$ref": "#/components/schemas/Condition" + }, + { + "$ref": "#/components/schemas/compare" + } + ] + }, + "gt": { + "type": "object", + "description": "Greater than condition", + "allOf": [ + { + "$ref": "#/components/schemas/Condition" + }, + { + "$ref": "#/components/schemas/compare" + } + ] + }, + "gte": { + "type": "object", + "description": "Greater than or equals condition", + "allOf": [ + { + "$ref": "#/components/schemas/Condition" + }, + { + "$ref": "#/components/schemas/compare" + } + ] + }, + "in": { + "type": "object", + "description": "In condition", + "allOf": [ + { + "$ref": "#/components/schemas/Condition" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "values": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "path", + "values" + ] + } + ] + }, + "logical-not": { + "type": "object", + "properties": { + "condition": { + "$ref": "#/components/schemas/Condition" + } + }, + "required": [ + "condition" + ], + "example": { + "operation": "not", + "condition": { + "operation": "equals", + "path": "/name", + "value": "John Dou" + } + } + }, + "logical": { + "type": "object", + "properties": { + "conditions": { + "type": "array", + "minItems": 2, + "items": { + "$ref": "#/components/schemas/Condition" + } + } + }, + "required": [ + "conditions" + ] + }, + "lt": { + "type": "object", + "description": "Less than condition", + "allOf": [ + { + "$ref": "#/components/schemas/Condition" + }, + { + "$ref": "#/components/schemas/compare" + } + ] + }, + "lte": { + "type": "object", + "description": "Less than or equals condition", + "allOf": [ + { + "$ref": "#/components/schemas/Condition" + }, + { + "$ref": "#/components/schemas/compare" + } + ] + }, + "not": { + "type": "object", + "description": "Logical NOT", + "allOf": [ + { + "$ref": "#/components/schemas/Condition" + }, + { + "$ref": "#/components/schemas/logical-not" + } + ] + }, + "or": { + "type": "object", + "description": "Logical OR", + "allOf": [ + { + "$ref": "#/components/schemas/Condition" + }, + { + "$ref": "#/components/schemas/logical" + } + ] + }, + "Customer": { + "type": "object", + "properties": { + "id": { + "description": "The customer identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "defaultPaymentInstrument": { + "$ref": "#/components/schemas/PaymentInstrument" + }, + "createdTime": { + "description": "The customer created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "The customer updated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "customFields": { + "$ref": "#/components/schemas/ResourceCustomFields" + }, + "primaryAddress": { + "$ref": "#/components/schemas/ContactObject" + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 3, + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/SelfLink" + }, + { + "$ref": "#/components/schemas/NotesLink" + }, + { + "$ref": "#/components/schemas/DefaultPaymentInstrumentLink" + }, + { + "$ref": "#/components/schemas/LeadSourceLink" + } + ] + } + } + } + }, + "CustomEvent": { + "type": "object", + "properties": { + "id": { + "description": "The custom event identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "eventType": { + "type": "string", + "description": "The system event type", + "enum": [ + "subscription-ended", + "subscription-trial-ended", + "subscription-renewed", + "payment-card-expired", + "invoice-past-due", + "invoice-issued", + "invoice-voided", + "invoice-paid", + "invoice-abandoned" + ] + }, + "title": { + "type": "string", + "description": "The custom event title" + }, + "description": { + "type": "string", + "description": "The custom event description" + }, + "chronology": { + "type": "string", + "description": "The emitting time of the custom event relatively to the system event", + "enum": [ + "before", + "after" + ] + }, + "scheduleInstruction": { + "$ref": "#/components/schemas/CustomEventScheduleInstruction" + }, + "createdTime": { + "$ref": "#/components/schemas/ServerTimestamp" + }, + "rulesCount": { + "type": "integer", + "readOnly": true + } + }, + "required": [ + "eventType", + "title", + "chronology", + "scheduleInstruction" + ] + }, + "CustomField": { + "description": "A separate Custom Field schema", + "type": "object", + "required": [ + "name", + "type" + ], + "properties": { + "name": { + "description": "The name of the custom field", + "type": "string" + }, + "type": { + "description": "Type value | Description\n------------- | -------------\narray | An array of strings up to 255 characters, maximum size is 1000 elements\nboolean | true or false\ndate | String of format \"full-date\" (YYYY-MM-DD) from RFC-3339 (full-date)\ndatetime | String of format \"date-time\" (YYYY-MM-DDTHH:MM:SSZ) from RFC-3339 (date-time)\ninteger | Cardinal value of -2^31..2^31-1\nnumber | Float value. It can take cardinal values also which are interpreted as float\nstring | Regular string up to 255 characters\nmonetary | A map of 3-letters currency code and amount, e.g. {\"currency\": \"EUR\", \"amount\": 25.30}\n", + "type": "string", + "enum": [ + "array", + "boolean", + "datetime", + "integer", + "number", + "string", + "monetary" + ] + }, + "description": { + "description": "The custom field description", + "type": "string" + }, + "isUsed": { + "description": "A flag to represent that the custom field is used on a record of the resource.", + "type": "boolean" + }, + "additionalSchema": { + "description": "Additional parameters which can be added according to type:\nParameter Name | Types | Description\n-------------- | ------------- | -------------\nallowedValues | string, array | List of allowed values\n" + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "Dispute": { + "type": "object", + "required": [ + "currency", + "transactionId", + "postedTime", + "type", + "status", + "reasonCode" + ], + "properties": { + "id": { + "description": "The dispute identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "customerId": { + "description": "The dispute's customer ID", + "type": "string", + "readOnly": true + }, + "transactionId": { + "description": "The dispute's transaction ID", + "type": "string" + }, + "currency": { + "description": "The dispute currency ISO Alpha code", + "type": "string" + }, + "amount": { + "description": "The dispute amount", + "type": "number", + "format": "double" + }, + "acquirerReferenceNumber": { + "description": "The dispute's acquirer reference number", + "type": "string" + }, + "reasonCode": { + "description": "The dispute's reason code", + "type": "string", + "enum": [ + "1000", + "12", + "2", + "30", + "31", + "35", + "37", + "40", + "41", + "42", + "46", + "47", + "49", + "50", + "53", + "54", + "55", + "57", + "59", + "60", + "62", + "7", + "70", + "71", + "72", + "73", + "74", + "75", + "76", + "77", + "79", + "8", + "80", + "81", + "82", + "83", + "85", + "86", + "93", + "00", + "63", + "A01", + "A02", + "A08", + "F10", + "F14", + "F22", + "F24", + "F29", + "C02", + "C04", + "C05", + "C08", + "C14", + "C18", + "C28", + "C31", + "C32", + "M10", + "M49", + "P01", + "P03", + "P04", + "P05", + "P07", + "P08", + "P22", + "P23", + "R03", + "R13", + "M01", + "FR1", + "FR4", + "FR6", + "AL", + "AP", + "AW", + "CA", + "CD", + "CR", + "DA", + "DP", + "DP1", + "EX", + "IC", + "IN", + "IS", + "LP", + "N", + "NA", + "NC", + "P", + "RG", + "RM", + "RN1", + "RN2", + "SV", + "TF", + "TNM", + "UA01", + "UA02", + "UA32", + "UA99", + "UA03", + "UA10", + "UA11", + "UA12", + "UA18", + "UA20", + "UA21", + "UA22", + "UA23", + "UA28", + "UA30", + "UA31", + "UA38", + "duplicate", + "fraudulent", + "subscription_canceled", + "product_unacceptable", + "product_not_received", + "unrecognized", + "credit_not_processed", + "customer_initiated", + "incorrect_account_details", + "insufficient_funds", + "bank_cannot_process", + "debit_not_authorized", + "general" + ] + }, + "category": { + "description": "The dispute's category", + "type": "string", + "readOnly": true, + "enum": [ + "fraud", + "unrecognized", + "product-not-received", + "product-unacceptable", + "product-not-refunded", + "duplicate", + "subscription-canceled", + "uncategorized" + ] + }, + "type": { + "description": "The dispute's type", + "type": "string", + "enum": [ + "information-request", + "first-chargeback", + "second-chargeback", + "arbitration" + ] + }, + "status": { + "description": "The dispute's status", + "type": "string", + "enum": [ + "response-needed", + "under-review", + "forfeited", + "won", + "lost", + "unknown" + ] + }, + "postedTime": { + "description": "Dispute posted time", + "type": "string", + "format": "date-time" + }, + "deadlineTime": { + "description": "Dispute deadline time", + "type": "string", + "format": "date-time" + }, + "rawResponse": { + "description": "Dispute raw response from gateway", + "type": "string", + "readOnly": true + }, + "resolvedTime": { + "description": "Dispute resolved time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "createdTime": { + "description": "Dispute created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "Dispute updated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "DisputeEvidence": { + "type": "object", + "properties": { + "policy": { + "description": "The id of a file upload with the policy materials (may include the cancellation policy, refund policy, and terms of use).", + "type": "string" + }, + "accessLogs": { + "description": "The id of a file upload with the access logs showing the customer activity.", + "type": "string" + }, + "customerCommunication": { + "description": "The id of a file upload showing communication with the customer (for example emails).", + "type": "string" + }, + "customerSignature": { + "description": "The id of a file upload showing the signed contract or signed delivery receipt.", + "type": "string" + }, + "deliveryProof": { + "description": "The id of a file upload showing the proof of delivery.", + "type": "string" + }, + "explanation": { + "description": "An explanation relevant to the category of dispute.", + "type": "string" + }, + "additionalFile": { + "description": "Any additional evidence as a file upload id.", + "type": "string" + } + } + }, + "Email": { + "type": "object", + "required": [ + "email" + ], + "properties": { + "email": { + "description": "Email", + "type": "string", + "format": "email" + } + } + }, + "SmtpAuthorization": { + "type": "object", + "discriminator": { + "propertyName": "type" + }, + "properties": { + "type": { + "type": "string", + "enum": [ + "none", + "plain", + "login", + "cram-md5" + ], + "default": "none" + } + } + }, + "cram-md5": { + "allOf": [ + { + "$ref": "#/components/schemas/SmtpAuthorization" + }, + { + "$ref": "#/components/schemas/UserPasswordAuthorization" + } + ] + }, + "login": { + "allOf": [ + { + "$ref": "#/components/schemas/SmtpAuthorization" + }, + { + "$ref": "#/components/schemas/UserPasswordAuthorization" + } + ] + }, + "plain": { + "allOf": [ + { + "$ref": "#/components/schemas/SmtpAuthorization" + }, + { + "$ref": "#/components/schemas/UserPasswordAuthorization" + } + ] + }, + "SmtpCredential": { + "type": "object", + "description": "SMTP Credential", + "required": [ + "host" + ], + "properties": { + "hash": { + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "host": { + "type": "string", + "description": "The host name" + }, + "port": { + "type": "integer", + "description": "The port value", + "minimum": 1, + "maximum": 65535, + "default": 25 + }, + "encryption": { + "type": "string", + "description": "The encryption value", + "enum": [ + "none", + "tls", + "ssl" + ], + "default": "none" + }, + "auth": { + "$ref": "#/components/schemas/SmtpAuthorization" + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "UserPasswordAuthorization": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string", + "format": "password" + } + }, + "required": [ + "username", + "password" + ] + }, + "Error": { + "type": "object", + "properties": { + "status": { + "type": "integer", + "minimum": 100, + "maximum": 600 + }, + "error": { + "type": "string" + } + } + }, + "SystemEvent": { + "type": "object", + "description": "The application event", + "readOnly": true, + "properties": { + "eventType": { + "$ref": "#/components/schemas/EventType" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "rulesCount": { + "type": "integer", + "readOnly": true + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "EventType": { + "type": "string", + "description": "Rebilly event type", + "readOnly": true, + "enum": [ + "dispute-created", + "gateway-account-requested", + "transaction-processed", + "subscription-canceled", + "subscription-created", + "subscription-renewed", + "payment-card-expired", + "payment-declined", + "transaction-process-requested", + "risk-score-changed" + ] + }, + "File": { + "type": "object", + "properties": { + "id": { + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "name": { + "description": "Original File name", + "type": "string" + }, + "extension": { + "description": "The File extension", + "type": "string" + }, + "description": { + "description": "The File description", + "type": "string" + }, + "url": { + "description": "Write-only. If defined on POST, this would be used as a file source.", + "type": "string" + }, + "tags": { + "description": "The tags list", + "type": "array", + "items": { + "type": "string" + } + }, + "mime": { + "description": "The mime type", + "type": "string", + "readOnly": true, + "enum": [ + "image/png", + "image/jpeg", + "image/gif", + "application/pdf", + "audio/mpeg" + ] + }, + "size": { + "description": "The File size in bytes", + "type": "integer", + "readOnly": true + }, + "width": { + "description": "Image width, applicable to images only", + "type": "integer", + "readOnly": true + }, + "height": { + "description": "Image height, applicable to images only", + "type": "integer", + "readOnly": true + }, + "sha1": { + "description": "Hash sum of the file", + "type": "string", + "readOnly": true + }, + "createdTime": { + "description": "The upload date/time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "The latest update date/time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 2, + "maxItems": 2, + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/SelfLink" + }, + { + "$ref": "#/components/schemas/PermalinkLink" + } + ] + } + } + } + }, + "GatewayAccount": { + "type": "object", + "required": [ + "gatewayName", + "acquirerName", + "merchantCategoryCode", + "websites", + "acceptedCurrencies", + "organizationId" + ], + "discriminator": { + "propertyName": "gatewayName" + }, + "properties": { + "id": { + "description": "The gateway identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "status": { + "description": "The gateway account's status", + "readOnly": true, + "type": "string", + "enum": [ + "active", + "inactive", + "pending" + ] + }, + "gatewayName": { + "$ref": "#/components/schemas/GatewayName" + }, + "acquirerName": { + "$ref": "#/components/schemas/AcquirerName" + }, + "merchantCategoryCode": { + "description": "The gateway account's merchant category code", + "type": "integer", + "minimum": 742, + "maximum": 9950 + }, + "dccMarkup": { + "description": "Dynamic currency conversion markup in basis points", + "type": "integer", + "minimum": 1, + "maximum": 10000 + }, + "descriptor": { + "description": "The gateway account's descriptor", + "type": "string" + }, + "cityField": { + "description": "The gateway account's city field (also known as line 2 descriptor)", + "type": "string" + }, + "organizationId": { + "description": "Organization ID", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "websites": { + "description": "Websites IDs", + "type": "array", + "items": { + "description": "Website ID", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + } + }, + "excludedDccQuoteCurrencies": { + "description": "Excluded Dynamic Currency Conversion Quote Currencies", + "type": "array", + "items": { + "type": "string" + } + }, + "monthlyLimit": { + "description": "Monthly Limit", + "type": "integer", + "format": "double", + "minimum": 0 + }, + "threeDSecure": { + "description": "True, if Gateway Account allows 3DSecure", + "type": "boolean" + }, + "dynamicDescriptor": { + "description": "True, if Gateway Account allows dynamic descriptor", + "type": "boolean" + }, + "acceptedCurrencies": { + "description": "Accepted currencies (array of the currency three letter code)", + "type": "array", + "items": { + "type": "string" + } + }, + "method": { + "$ref": "#/components/schemas/Method" + }, + "paymentCardSchemes": { + "description": "Accepted payment card brands", + "type": "array", + "items": { + "type": "string", + "enum": [ + "Visa", + "MasterCard", + "American Express", + "Discover", + "Maestro", + "Solo", + "Electron", + "JCB", + "Voyager", + "Diners Club", + "Switch", + "Laser", + "China Unionpay" + ] + } + }, + "downtimeStart": { + "description": "Gateway account downtime start", + "type": "string", + "format": "date-time" + }, + "downtimeEnd": { + "description": "Gateway account downtime end", + "type": "string", + "format": "date-time" + }, + "createdTime": { + "description": "Gateway Account created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "Gateway Account updated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 2, + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/SelfLink" + }, + { + "$ref": "#/components/schemas/OnBoardingUrlLink" + } + ] + } + } + } + }, + "A1Gateway": { + "description": "A1Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "description": "A1Gateway credentials object", + "properties": { + "accountId": { + "type": "string", + "description": "A1Gateway account ID" + }, + "password": { + "type": "string", + "description": "A1Gateway password", + "format": "password" + } + }, + "required": [ + "accountId", + "password" + ] + }, + "mpi": { + "$ref": "#/components/schemas/A1GatewayMpis" + } + } + } + ] + }, + "AmexVPC": { + "description": "AmexVPC config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "description": "AmexVPC credentials object", + "properties": { + "url": { + "type": "string", + "description": "Virtual Payment Client URL" + }, + "merchantId": { + "type": "string", + "description": "Merchant ID" + }, + "accessCode": { + "type": "string", + "description": "Access Code", + "format": "password" + }, + "user": { + "type": "string", + "description": "User (used for refund, void and capture)" + }, + "password": { + "type": "string", + "description": "Password (used for refund, void and capture)", + "format": "password" + } + }, + "required": [ + "url", + "merchantId", + "accessCode", + "user", + "password" + ] + } + } + } + ] + }, + "AuthorizeNet": { + "description": "AuthorizeNet Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "apiLoginId": { + "type": "string", + "description": "AuthorizeNet Gateway api login ID" + }, + "transactionKey": { + "description": "AuthorizeNet Gateway Transaction Key", + "type": "string", + "format": "password" + } + }, + "required": [ + "apiLoginId", + "transactionKey" + ] + } + } + } + ] + }, + "Beanstream": { + "description": "Beanstream Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "merchantId": { + "type": "string", + "description": "Beanstream Gateway merchant ID" + }, + "apiPasscode": { + "type": "string", + "description": "Beanstream Gateway API Passcode", + "format": "password" + } + }, + "required": [ + "merchantId", + "apiPasscode" + ] + } + } + } + ] + }, + "BraintreePayments": { + "description": "BraintreePayments Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "publicKey": { + "type": "string", + "description": "BraintreePayments Public Key" + }, + "privateKey": { + "type": "string", + "description": "BraintreePayments Private Key", + "format": "password" + }, + "merchantId": { + "type": "string", + "description": "BraintreePayments merchant ID", + "format": "password" + }, + "merchantAccountId": { + "type": "string", + "description": "BraintreePayments merchant account ID", + "format": "password" + } + }, + "required": [ + "publicKey", + "privateKey", + "merchantId", + "merchantAccountId" + ] + } + } + } + ] + }, + "Cashflows": { + "description": "Cashflows Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "authId": { + "type": "string", + "description": "Cashflows Gateway auth ID" + }, + "authPassword": { + "type": "string", + "description": "Cashflows Gateway auth password", + "format": "password" + } + }, + "required": [ + "authPassword", + "authId" + ] + } + } + } + ] + }, + "Cayan": { + "description": "Cayan Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "merchantSiteId": { + "type": "string", + "description": "Cayan Gateway merchant site ID" + }, + "merchantName": { + "type": "string", + "description": "Cayan Gateway merchant name" + }, + "merchantKey": { + "type": "string", + "description": "Cayan Gateway merchant key", + "format": "password" + } + }, + "required": [ + "merchantSiteId", + "merchantName", + "merchantKey" + ] + } + } + } + ] + }, + "Chase": { + "description": "Chase Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "username": { + "type": "string", + "description": "Chase Gateway Net Connect username" + }, + "password": { + "type": "string", + "description": "Chase Gateway Net Connect password", + "format": "password" + }, + "coNumber": { + "type": "string", + "description": "Chase Gateway CO Number used for delimited file reports" + }, + "divisionId": { + "type": "string", + "description": "Chase Gateway division ID" + }, + "partialAuth": { + "type": "boolean", + "description": "Support for Partial Auths", + "default": false + } + }, + "required": [ + "username", + "password", + "coNumber", + "divisionId", + "partialAuth" + ] + } + } + } + ] + }, + "ChinaUnionPay": { + "description": "China Union Pay Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "accountId": { + "type": "string", + "description": "China Union Pay Gateway account ID" + }, + "partyId": { + "type": "string", + "description": "China Union Pay Gateway party ID" + }, + "goods": { + "type": "string", + "description": "China Union Pay Gateway goods" + }, + "md5key": { + "type": "string", + "description": "China Union Pay Gateway md5key", + "format": "password" + }, + "mobilePay": { + "type": "string", + "description": "China Union Pay Gateway mobile pay param" + } + }, + "required": [ + "accountId", + "partyId", + "goods", + "md5key", + "mobilePay" + ] + } + } + } + ] + }, + "Credorax": { + "description": "Credorax Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "merchantId": { + "type": "string", + "description": "Credorax Gateway merchant ID" + }, + "merchantMd5Signature": { + "type": "string", + "description": "Credorax Gateway md5 signature", + "format": "password" + } + }, + "required": [ + "merchantId", + "merchantMd5Signature" + ] + } + } + } + ] + }, + "DataCash": { + "description": "DataCash Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "client": { + "type": "string", + "description": "DataCash Gateway client" + }, + "password": { + "type": "string", + "description": "DataCash Gateway password", + "format": "password" + }, + "policy": { + "type": "integer", + "description": "Policy", + "minimum": 0, + "maximum": 7, + "default": 2 + }, + "delay": { + "type": "integer", + "description": "Auto Capture delay (in hours)", + "minimum": 0, + "default": 0 + } + }, + "required": [ + "client", + "password", + "policy", + "delay" + ] + }, + "mpi": { + "$ref": "#/components/schemas/DataCashMpis" + } + } + } + ] + }, + "Dengi": { + "description": "Dengi Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "projectId": { + "type": "string", + "description": "Dengi Gateway project ID" + }, + "publicKey": { + "type": "string", + "description": "Dengi Gateway public key", + "format": "password" + }, + "refundKey": { + "type": "string", + "description": "Dengi Gateway refund key", + "format": "password" + } + }, + "required": [ + "projectId", + "publicKey", + "refundKey" + ] + } + } + } + ] + }, + "eMerchantPay": { + "description": "eMerchantPay Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "clientId": { + "type": "string", + "description": "eMerchantPay Gateway client ID" + }, + "apiKey": { + "type": "string", + "description": "eMerchantPay Gateway api key", + "format": "password" + } + }, + "required": [ + "clientId", + "apiKey" + ] + }, + "mpi": { + "$ref": "#/components/schemas/eMerchantPayMpis" + } + } + } + ] + }, + "Flexepin": { + "description": "Flexepin Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "apiKey": { + "type": "string", + "description": "Flexepin API Key" + }, + "apiSecret": { + "type": "string", + "description": "Flexepin API Secret", + "format": "password" + } + }, + "required": [ + "apiKey", + "apiSecret" + ] + } + } + } + ] + }, + "Forte": { + "description": "Forte Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "accountId": { + "type": "string", + "description": "Forte Gateway account ID" + }, + "locationId": { + "type": "string", + "description": "Forte Gateway location ID" + }, + "apiAccessId": { + "type": "string", + "description": "Forte Gateway api access ID", + "format": "password" + }, + "apiSecretKey": { + "type": "string", + "description": "Forte Gateway api secret key", + "format": "password" + } + }, + "required": [ + "accountId", + "locationId", + "apiAccessId", + "apiSecretKey" + ] + } + } + } + ] + }, + "FundSend": { + "description": "FundSend Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "clientId": { + "type": "string", + "description": "FundSend Gateway client ID" + }, + "secretWord": { + "type": "string", + "description": "FundSend Gateway secret word", + "format": "password" + } + }, + "required": [ + "clientId", + "secretWord" + ] + } + } + } + ] + }, + "GET": { + "description": "GET Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "accountId": { + "type": "string", + "description": "GET Gateway account ID" + } + }, + "required": [ + "accountId" + ] + }, + "mpi": { + "$ref": "#/components/schemas/GETMpis" + } + } + } + ] + }, + "GlobalCollect": { + "description": "GlobalCollect Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "merchantId": { + "type": "string", + "description": "GlobalCollect Gateway merchant ID" + }, + "apiKeyId": { + "type": "string", + "description": "GlobalCollect Gateway api key ID" + }, + "apiSecretKey": { + "type": "string", + "description": "GlobalCollect Gateway api secret key", + "format": "password" + }, + "skipFraudService": { + "type": "boolean", + "description": "GlobalCollect skip fraud service" + } + }, + "required": [ + "merchantId", + "apiKeyId", + "apiSecretKey" + ] + }, + "mpi": { + "$ref": "#/components/schemas/GlobalCollectMpis" + } + } + } + ] + }, + "GlobalOne": { + "description": "GlobalOne Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "terminalId": { + "type": "string", + "description": "GlobalOne Gateway terminal ID" + }, + "sharedSecret": { + "type": "string", + "description": "GlobalOne Gateway shared secret", + "format": "password" + } + }, + "required": [ + "terminalId", + "sharedSecret" + ] + } + } + } + ] + }, + "Gpaysafe": { + "description": "Gpaysafe Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "apiKey": { + "type": "string", + "description": "Gpaysafe apiKey" + } + }, + "required": [ + "apiKey" + ] + } + } + } + ] + }, + "iCheque": { + "description": "iCheque Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "clientId": { + "type": "string", + "description": "iCheque Gateway client ID" + }, + "secretWord": { + "type": "string", + "description": "iCheque Gateway secret word", + "format": "password" + } + }, + "required": [ + "clientId", + "secretWord" + ] + } + } + } + ] + }, + "Ilixium": { + "description": "Ilixium Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "merchantId": { + "type": "string", + "description": "Ilixium Gateway merchant ID" + }, + "accountId": { + "type": "string", + "description": "Ilixium Gateway account ID" + }, + "digestPassword": { + "type": "string", + "description": "Ilixium Gateway digest password", + "format": "password" + } + }, + "required": [ + "merchantId", + "accountId", + "digestPassword" + ] + }, + "mpi": { + "$ref": "#/components/schemas/IlixiumMpis" + } + } + } + ] + }, + "Jeton": { + "description": "Jeton Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "apiKey": { + "type": "string", + "description": "Jeton apiKey" + } + }, + "required": [ + "apiKey" + ] + } + } + } + ] + }, + "JetPay": { + "description": "JetPay Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "TerminalID": { + "type": "string", + "description": "JetPay Gateway terminal ID" + } + }, + "required": [ + "TerminalID" + ] + } + } + } + ] + }, + "Moneris": { + "description": "Moneris Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "apiToken": { + "type": "string", + "description": "Moneris Gateway api token", + "format": "password" + }, + "storeId": { + "type": "string", + "description": "Moneris Gateway store ID" + } + }, + "required": [ + "storeId", + "apiToken" + ] + } + } + } + ] + }, + "NMI": { + "description": "NMI Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "username": { + "type": "string", + "description": "NMI Gateway api token" + }, + "password": { + "type": "string", + "description": "NMI Gateway store ID", + "format": "password" + } + }, + "required": [ + "username", + "password" + ] + }, + "mpi": { + "$ref": "#/components/schemas/NMIMpis" + } + } + } + ] + }, + "OchaPay": { + "description": "OchaPay Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "apiUsername": { + "type": "string", + "description": "OchaPay Gateway username" + }, + "apiPassword": { + "type": "string", + "description": "OchaPay Gateway api password", + "format": "password" + }, + "secretWord": { + "type": "string", + "description": "OchaPay Gateway secret word", + "format": "password" + } + }, + "required": [ + "apiUsername", + "apiPassword", + "secretWord" + ] + } + } + } + ] + }, + "Optimal": { + "description": "Optimal Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "storeId": { + "type": "string", + "description": "Optimal Gateway store ID" + }, + "storePwd": { + "type": "string", + "description": "Optimal Gateway store password", + "format": "password" + }, + "accountNum": { + "type": "string", + "description": "Optimal Gateway account number" + } + }, + "required": [ + "storeId", + "storePwd", + "accountNum" + ] + }, + "mpi": { + "$ref": "#/components/schemas/OptimalMpis" + } + } + } + ] + }, + "PandaGateway": { + "description": "Panda Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "merchantCode": { + "type": "string", + "description": "Panda Gateway merchant code" + }, + "apiCode": { + "type": "string", + "description": "Panda Gateway api code" + }, + "signKey": { + "type": "string", + "description": "Panda Gateway sign key", + "format": "password" + } + }, + "required": [ + "merchantCode", + "apiCode", + "signKey" + ] + } + } + } + ] + }, + "Payeezy": { + "description": "Payeezy Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "merchantId": { + "type": "string", + "description": "Payeezy Merchant ID" + }, + "merchantToken": { + "type": "string", + "description": "Merchant Token", + "format": "password" + }, + "apiKey": { + "type": "string", + "description": "API Key" + }, + "apiSecret": { + "type": "string", + "description": "API Secret", + "format": "password" + } + }, + "required": [ + "merchantId", + "merchantToken", + "apiKey", + "apiSecret" + ] + } + } + } + ] + }, + "Payflow": { + "description": "Payflow config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "description": "Payflow credentials object", + "properties": { + "user": { + "type": "string", + "description": "If you set up one or more additional users on the account, this value is the ID of the user authorized to process transactions. If, however, you have not set up additional users, USER has the same value as VENDOR" + }, + "vendor": { + "type": "string", + "description": "Your merchant login ID created when you registered for the account." + }, + "password": { + "type": "string", + "description": "The password you defined while registering for the account.", + "format": "password" + } + }, + "required": [ + "user", + "vendor", + "password" + ] + } + } + } + ] + }, + "PayPal": { + "description": "PayPal Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "merchantIdInPayPal": { + "type": "string", + "description": "PayPal Gateway merchant id" + }, + "redirectUrl": { + "type": "string", + "description": "PayPal Gateway redirect url", + "format": "url" + } + }, + "required": [ + "merchantIdInPayPal", + "redirectUrl" + ] + } + } + } + ] + }, + "Payr": { + "description": "Payr Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "clientId": { + "type": "string", + "description": "Payr Gateway client ID" + }, + "secretWord": { + "type": "string", + "description": "Payr Gateway secret word", + "format": "password" + } + }, + "required": [ + "clientId", + "secretWord" + ] + } + } + } + ] + }, + "Payvision": { + "description": "Payvision Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "memberId": { + "type": "string", + "description": "Payvision Gateway member id" + }, + "memberGuid": { + "type": "string", + "description": "Payvision Gateway member guid", + "format": "password" + }, + "avs": { + "type": "boolean", + "description": "Payvision Gateway avs" + }, + "delay": { + "type": "integer", + "description": "Payvision Gateway delay" + } + }, + "required": [ + "memberId", + "memberGuid", + "avs", + "delay" + ] + }, + "mpi": { + "$ref": "#/components/schemas/PayvisionMpis" + } + } + } + ] + }, + "Plugnpay": { + "description": "Plugnpay Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "publisher-name": { + "type": "string", + "description": "Plugnpay Gateway member id" + }, + "publisher-password": { + "type": "string", + "description": "Plugnpay Gateway avs", + "format": "password" + } + }, + "required": [ + "publisher-name", + "publisher-password" + ] + } + } + } + ] + }, + "Realex": { + "description": "Realex Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "merchantId": { + "type": "string", + "description": "Realex Gateway merchant id" + }, + "secretKey": { + "type": "string", + "description": "Realex Gateway secret key", + "format": "password" + }, + "rebatePassword": { + "type": "string", + "description": "Realex Gateway rebate password", + "format": "password" + }, + "account": { + "type": "string", + "description": "Realex Gateway account" + } + }, + "required": [ + "merchantId", + "secretKey", + "rebatePassword", + "account" + ] + } + } + } + ] + }, + "RealTime": { + "description": "RealTime Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "clientId": { + "type": "string", + "description": "RealTime Gateway client ID" + }, + "secretWord": { + "type": "string", + "description": "RealTime Gateway secret word", + "format": "password" + } + }, + "required": [ + "clientId", + "secretWord" + ] + } + } + } + ] + }, + "RebillyProcessor": { + "description": "RebillyProcessor Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "properties": { + "mpi": { + "$ref": "#/components/schemas/RebillyProcessorMpis" + } + } + } + ] + }, + "Redsys": { + "description": "Redsys Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "merchantCode": { + "type": "string", + "description": "Redsys Gateway merchant code", + "format": "password" + }, + "secretCode": { + "type": "string", + "description": "Redsys Gateway secret code", + "format": "password" + } + }, + "required": [ + "merchantCode", + "secretCode" + ] + } + } + } + ] + }, + "RPN": { + "description": "RPN Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "mid": { + "type": "string", + "description": "RPN MID" + }, + "key": { + "type": "string", + "description": "RPN Key", + "format": "password" + } + }, + "required": [ + "mid", + "key" + ] + } + } + } + ] + }, + "Sagepay": { + "description": "Sagepay Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "M_ID": { + "type": "string", + "description": "Sagepay Gateway merchant ID" + }, + "M_KEY": { + "type": "string", + "description": "Sagepay Gateway merchant key", + "format": "password" + } + }, + "required": [ + "M_ID", + "M_KEY" + ] + } + } + } + ] + }, + "SMSVoucher": { + "description": "SMSVoucher Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "AppId": { + "type": "string", + "description": "SMSVoucher AppId" + } + }, + "required": [ + "AppId" + ] + } + } + } + ] + }, + "Stripe": { + "description": "Stripe Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "redirectUrl": { + "type": "string", + "description": "Stripe Gateway redirect url", + "format": "url" + } + }, + "required": [ + "redirectUrl" + ] + } + } + } + ] + }, + "UPayCard": { + "description": "UPayCard Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "receiver_account": { + "type": "string", + "description": "merchant receiver account" + }, + "key": { + "type": "string", + "description": "merchant key", + "format": "password" + }, + "secret": { + "type": "string", + "description": "merchant secret", + "format": "password" + } + }, + "required": [ + "receiver_account", + "key", + "secret" + ] + } + } + } + ] + }, + "USAePay": { + "description": "USAePay Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "sourceKey": { + "type": "string", + "description": "USAePay Gateway source key", + "format": "password" + }, + "pin": { + "type": "string", + "description": "USAePay Gateway pin", + "format": "password" + } + }, + "required": [ + "sourceKey", + "pin" + ] + } + } + } + ] + }, + "VantivLitle": { + "description": "VantivLitle Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "username": { + "type": "string", + "description": "VantivLitle Gateway username" + }, + "password": { + "type": "string", + "description": "VantivLitle Gateway password", + "format": "password" + }, + "merchantId": { + "type": "string", + "description": "VantivLitle Gateway merchant ID" + } + }, + "required": [ + "username", + "password", + "merchantId" + ] + }, + "mpi": { + "$ref": "#/components/schemas/VantivLitleMpis" + } + } + } + ] + }, + "vegaaH": { + "description": "vegaaH Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "terminalId": { + "type": "string", + "description": "vegaaH Gateway terminal ID" + }, + "password": { + "type": "string", + "description": "vegaaH Gateway password", + "format": "password" + } + }, + "required": [ + "terminalId", + "password" + ] + } + } + } + ] + }, + "Walpay": { + "description": "Walpay Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "merchantName": { + "type": "string", + "description": "Walpay Gateway merchant name" + }, + "merchantPin": { + "type": "string", + "description": "Walpay Gateway merchant pin", + "format": "password" + } + }, + "required": [ + "merchantName", + "merchantPin" + ] + }, + "mpi": { + "$ref": "#/components/schemas/WalpayMpis" + } + } + } + ] + }, + "Wirecard": { + "description": "Wirecard Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "merchantUsername": { + "type": "string", + "description": "Wirecard Gateway merchant username" + }, + "merchantPassword": { + "type": "string", + "description": "Wirecard Gateway merchant password", + "format": "password" + }, + "businessSignature": { + "type": "string", + "description": "Wirecard Gateway merchant business case signature", + "format": "password" + }, + "delay": { + "type": "integer", + "description": "Wirecard Gateway delay" + } + }, + "required": [ + "merchantUsername", + "merchantPassword", + "businessSignature", + "delay" + ] + }, + "mpi": { + "$ref": "#/components/schemas/WirecardMpis" + } + } + } + ] + }, + "Worldpay": { + "description": "Worldpay Gateway config", + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccount" + }, + { + "type": "object", + "required": [ + "gatewayConfig" + ], + "properties": { + "gatewayConfig": { + "type": "object", + "properties": { + "merchantCode": { + "type": "string", + "description": "Worldpay Gateway merchant code", + "format": "password" + }, + "merchantPassword": { + "type": "string", + "description": "Worldpay Gateway merchant password", + "format": "password" + } + }, + "required": [ + "merchantCode", + "merchantPassword" + ] + }, + "mpi": { + "$ref": "#/components/schemas/WorldpayMpis" + } + } + } + ] + }, + "AcquirerName": { + "description": "Acquirer name", + "type": "string", + "enum": [ + "Alipay", + "AIB", + "B+S", + "Bank of America", + "Bank of Moscow", + "Bank of Rebilly", + "Bank One", + "Beanstream", + "BMO Harris Bank", + "Borgun", + "BraintreePayments", + "Catalunya Caixa", + "Chase", + "ChinaUnionPay", + "CIM", + "Credorax", + "Elavon", + "EMS", + "Fifth Third Bank", + "First Data Buypass", + "First Data Nashville", + "First Data North", + "First Data Omaha", + "Flexepin", + "Forte", + "FundSend", + "GlobalCollect", + "Global East", + "Gpaysafe", + "Heartland", + "HSBC", + "iCheque", + "Ilixium", + "Jeton", + "Masapay", + "Merrick", + "Mission Valley Bank", + "Moneris", + "NATWEST", + "NMI", + "OchaPay", + "Other", + "Panda Bank", + "PayPal", + "Payr", + "Payvision", + "Peoples Trust Company", + "Privatbank", + "RBC", + "RBS WorldPay", + "RealTime", + "RebillyProcessor", + "SMSVoucher", + "State Bank of Mauritius", + "Stripe", + "TBI", + "TrustPay", + "TSYS", + "UPayCard", + "Vantiv", + "VoicePay", + "WeChat Pay", + "Wells Fargo", + "Wing Hang Bank", + "Wirecard", + "WorldPay" + ] + }, + "GatewayName": { + "description": "The gateway name", + "type": "string", + "enum": [ + "A1Gateway", + "AmexVPC", + "AuthorizeNet", + "Beanstream", + "BraintreePayments", + "Cashflows", + "Cayan", + "Chase", + "ChinaUnionPay", + "Credorax", + "DataCash", + "Dengi", + "eMerchantPay", + "Flexepin", + "FundSend", + "Forte", + "GET", + "GlobalCollect", + "GlobalOne", + "Gpaysafe", + "iCheque", + "Ilixium", + "JetPay", + "Jeton", + "Moneris", + "NMI", + "OchaPay", + "Optimal", + "PandaGateway", + "Payeezy", + "Payflow", + "PayPal", + "Payr", + "Payvision", + "Plugnpay", + "Realex", + "RealTime", + "RebillyProcessor", + "Redsys", + "RPN", + "Sagepay", + "SMSVoucher", + "Stripe", + "UPayCard", + "USAePay", + "VantivLitle", + "vegaaH", + "Walpay", + "Wirecard", + "Worldpay" + ] + }, + "MpiName": { + "description": "The Merchant plug-in Name", + "type": "string", + "enum": [ + "PayvisionMpi", + "WirecardMpi", + "IlixiumMpi", + "DataCashMpi", + "OptimalMpi", + "GlobalCollectMpi", + "CardinalCommerce", + "Other" + ] + }, + "GlobalWebhook": { + "type": "object", + "required": [ + "method", + "url", + "credentialHash" + ], + "properties": { + "id": { + "description": "The webhook identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "eventsFilter": { + "description": "An array of System event type", + "type": "array", + "default": [], + "items": { + "$ref": "#/components/schemas/GlobalWebhookEventType" + } + }, + "status": { + "$ref": "#/components/schemas/OnOff" + }, + "method": { + "type": "string", + "enum": [ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE" + ] + }, + "url": { + "description": "URL that will be triggered when the given event occurs.", + "type": "string", + "format": "uri" + }, + "headers": { + "type": "object", + "description": "Map of elements with header name - header value association" + }, + "credentialHash": { + "type": "string", + "description": "Hash from Credentials which is used for authentication by the given URL" + }, + "createdTime": { + "description": "List created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "List updated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "Links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "GlobalWebhookEventType": { + "type": "string", + "description": "Rebilly webhooks event type", + "enum": [ + "gateway-account-requested", + "subscription-trial-ended", + "subscription-activated", + "subscription-canceled", + "subscription-renewed", + "transaction-processed", + "payment-card-expired", + "payment-declined", + "invoice-modified", + "invoice-created", + "dispute-created", + "suspended-payment-completed" + ] + }, + "InvalidError": { + "allOf": [ + { + "$ref": "#/components/schemas/Error" + }, + { + "type": "object", + "properties": { + "details": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + ] + }, + "Invoice": { + "type": "object", + "required": [ + "customerId", + "websiteId", + "currency" + ], + "properties": { + "id": { + "description": "The invoice identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "customerId": { + "description": "The customer's ID", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "websiteId": { + "description": "The website's ID", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "organizationId": { + "description": "The organization's ID", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "subscriptionId": { + "description": "The related Subscription's ID if available, otherwise null", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "currency": { + "description": "The currency three letter code", + "type": "string" + }, + "amount": { + "description": "The Invoice's amount", + "type": "number", + "format": "double", + "readOnly": true + }, + "shippingAmount": { + "description": "The Invoice's shipping amount", + "type": "number", + "format": "double", + "readOnly": true + }, + "billingAddress": { + "description": "Invoice's billing address", + "allOf": [ + { + "$ref": "#/components/schemas/ContactObject" + } + ] + }, + "deliveryAddress": { + "description": "Invoice's delivery address", + "allOf": [ + { + "$ref": "#/components/schemas/ContactObject" + } + ] + }, + "notes": { + "description": "Notes for the customer which will display on the invoice", + "type": "string" + }, + "items": { + "type": "array", + "description": "Invoice items array", + "readOnly": true, + "items": { + "type": "string" + } + }, + "taxes": { + "type": "array", + "description": "Taxes applied to this invoice", + "readOnly": true, + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/InvoiceTax" + } + ] + } + }, + "discounts": { + "type": "array", + "description": "Discounts applied", + "readOnly": true, + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/InvoiceDiscount" + } + ] + } + }, + "status": { + "type": "string", + "description": "Invoice status.", + "readOnly": true, + "enum": [ + "draft", + "issued", + "past-due", + "paid", + "abandoned", + "voided" + ] + }, + "delinquentCollectionPeriod": { + "type": "integer", + "description": "Delinquent Collection Period - difference between paidTime and dueTime in days.", + "readOnly": true + }, + "collectionPeriod": { + "type": "integer", + "description": "Collection Period - difference between paidTime and issuedTime in days.", + "readOnly": true + }, + "abandonedTime": { + "description": "Invoice abandoned time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "voidedTime": { + "description": "Invoice voided time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "paidTime": { + "description": "Invoice paid time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "dueTime": { + "description": "Invoice due time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "issuedTime": { + "description": "Invoice issued time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "createdTime": { + "description": "Invoice created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 6, + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/SelfLink" + }, + { + "$ref": "#/components/schemas/CustomerLink" + }, + { + "$ref": "#/components/schemas/WebsiteLink" + }, + { + "$ref": "#/components/schemas/BillingContactLink" + }, + { + "$ref": "#/components/schemas/DeliveryContactLink" + }, + { + "$ref": "#/components/schemas/OrganizationLink" + }, + { + "$ref": "#/components/schemas/LeadSourceLink" + } + ] + } + } + } + }, + "InvoiceIssue": { + "type": "object", + "properties": { + "issuedTime": { + "type": "string", + "format": "date-time" + } + } + }, + "InvoiceItem": { + "type": "object", + "required": [ + "type", + "unitPrice" + ], + "properties": { + "id": { + "description": "The website identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "type": { + "description": "Invoice item's type", + "type": "string", + "enum": [ + "debit", + "credit" + ] + }, + "unitPrice": { + "description": "Invoice item's price", + "type": "number", + "format": "double" + }, + "quantity": { + "description": "Invoice item's quantity", + "type": "integer" + }, + "productId": { + "description": "The product's ID", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "description": { + "description": "Invoice item's description", + "type": "string" + }, + "discountAmount": { + "description": "Invoice item discount amount", + "type": "number", + "format": "double", + "readOnly": true + }, + "periodStartTime": { + "description": "Start time", + "type": "string", + "format": "date-time" + }, + "periodEndTime": { + "description": "End time", + "type": "string", + "format": "date-time" + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 2, + "items": { + "x-tuple": true, + "anyOf": [ + { + "$ref": "#/components/schemas/SelfLink" + }, + { + "$ref": "#/components/schemas/SubscriptionLink" + } + ] + } + } + } + }, + "InvoiceTax": { + "type": "object", + "readOnly": true, + "properties": { + "amount": { + "description": "Tax amount", + "type": "number", + "format": "double" + }, + "description": { + "type": "string", + "description": "Tax description" + } + } + }, + "Layout": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "id": { + "description": "The layout identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "name": { + "description": "The name of the layout string", + "type": "string" + }, + "items": { + "description": "The array of layout items (planId and starred)", + "type": "array", + "items": { + "$ref": "#/components/schemas/LayoutItem" + } + }, + "createdTime": { + "description": "Layout created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "customFields": { + "$ref": "#/components/schemas/ResourceCustomFields" + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 2, + "maxItems": 2, + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/SelfLink" + }, + { + "$ref": "#/components/schemas/ItemsLink" + } + ] + } + } + } + }, + "LayoutItem": { + "type": "object", + "required": [ + "planId" + ], + "properties": { + "planId": { + "description": "The plan identifier string", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "starred": { + "description": "Boolean if the plan should be starred (special callout presentation)", + "type": "boolean" + }, + "order": { + "description": "Item's order in Layout", + "type": "integer", + "readOnly": true + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "LeadSource": { + "type": "object", + "properties": { + "id": { + "description": "The lead source identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "medium": { + "description": "Lead Source's medium (eg search, display)", + "type": "string" + }, + "source": { + "description": "Lead Source's source (eg google, yahoo)", + "type": "string" + }, + "campaign": { + "description": "Lead Source's campaign (eg go-big-123)", + "type": "string" + }, + "term": { + "description": "Lead Source's term (eg salt shakers)", + "type": "string" + }, + "content": { + "description": "Lead Source's content (eg smiley faces)", + "type": "string" + }, + "affiliate": { + "description": "Lead Source's affiliate (eg 123, Bob Smith)", + "type": "string" + }, + "subAffiliate": { + "description": "Lead Source's sub-affiliate also called a sub-id or click id in some circles (eg 123456)", + "type": "string" + }, + "salesAgent": { + "description": "Lead Source's sales agent (eg James Bond)", + "type": "string" + }, + "clickId": { + "description": "Lead Source's click id (may come from an ad server)", + "type": "string" + }, + "path": { + "description": "Lead Source's path uri (eg www.example.com/some/landing/path)", + "type": "string" + }, + "ipAddress": { + "description": "Customer's IP Address", + "type": "string" + }, + "currency": { + "description": "Currency (three letter ISO 4217 alpha code) (eg USD, EUR)", + "type": "string" + }, + "amount": { + "description": "The amount that the lead cost", + "type": "number", + "format": "double" + }, + "createdTime": { + "description": "LeadSource created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "LeadSource updated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 2, + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/SelfLink" + }, + { + "$ref": "#/components/schemas/CustomerLink" + } + ] + } + } + } + }, + "ApprovalUrlLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "approvalUrl" + ] + } + }, + "required": [ + "rel" + ] + }, + "AttachmentResourceLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "customer", + "dispute", + "invoice", + "note", + "payment", + "plan", + "product", + "subscription", + "transaction" + ] + } + }, + "required": [ + "rel" + ] + }, + "BankAccountLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "bankAccount" + ] + } + }, + "required": [ + "rel" + ] + }, + "BillingContactLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "billingContact" + ] + } + }, + "required": [ + "rel" + ] + }, + "CancelUrlLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "cancelUrl" + ] + } + }, + "required": [ + "rel" + ] + }, + "ContactLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "contact" + ] + } + }, + "required": [ + "rel" + ] + }, + "CustomerLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "customer" + ] + } + }, + "required": [ + "rel" + ] + }, + "DefaultPaymentInstrumentLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "defaultPaymentInstrument" + ] + } + }, + "required": [ + "rel" + ] + }, + "DeliveryContactLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "deliveryContact" + ] + } + }, + "required": [ + "rel" + ] + }, + "DisputeLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "dispute" + ] + } + }, + "required": [ + "rel" + ] + }, + "FileLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "file" + ] + } + }, + "required": [ + "rel" + ] + }, + "GatewayAccountLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "gatewayAccount" + ] + } + }, + "required": [ + "rel" + ] + }, + "ItemsLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "items" + ] + } + }, + "required": [ + "rel" + ] + }, + "LeadSourceLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "leadSource" + ] + } + }, + "required": [ + "rel" + ] + }, + "NewLink": { + "type": "object", + "properties": { + "href": { + "description": "The link URL", + "type": "string" + } + }, + "required": [ + "href" + ] + }, + "NotesLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "notes" + ] + } + }, + "required": [ + "rel" + ] + }, + "OnBoardingUrlLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "onBoardingUrl" + ] + } + }, + "required": [ + "rel" + ] + }, + "OrganizationLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "organization" + ] + } + }, + "required": [ + "rel" + ] + }, + "ParentTransactionLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "parentTransaction" + ] + } + }, + "required": [ + "rel" + ] + }, + "PaymentCardLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "paymentCard" + ] + } + }, + "required": [ + "rel" + ] + }, + "PaymentLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "payment" + ] + } + }, + "required": [ + "rel" + ] + }, + "PermalinkLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "permalink" + ] + } + }, + "required": [ + "rel" + ] + }, + "PlanLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "plan" + ] + } + }, + "required": [ + "rel" + ] + }, + "RefundUrlLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "refundUrl" + ] + } + }, + "required": [ + "rel" + ] + }, + "RetriedTransactionLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "retriedTransaction" + ] + } + }, + "required": [ + "rel" + ] + }, + "RuleSetVersionLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "rules" + ] + } + }, + "required": [ + "rel" + ] + }, + "SelfLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "self" + ] + } + }, + "required": [ + "rel" + ] + }, + "SubscriptionLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "subscription" + ] + } + }, + "required": [ + "rel" + ] + }, + "TransactionLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "transaction" + ] + } + }, + "required": [ + "rel" + ] + }, + "WebsiteLink": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/NewLink" + } + ], + "properties": { + "rel": { + "description": "The link type", + "type": "string", + "enum": [ + "website" + ] + } + }, + "required": [ + "rel" + ] + }, + "List": { + "type": "object", + "required": [ + "name", + "values" + ], + "properties": { + "id": { + "description": "List ID", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "version": { + "description": "List version", + "type": "integer", + "readOnly": true, + "minimum": 1 + }, + "name": { + "description": "List name", + "type": "string" + }, + "values": { + "description": "List values", + "type": "array", + "items": { + "type": "string" + } + }, + "createdTime": { + "description": "List created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "List updated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "Links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "Method": { + "type": "string", + "description": "The payment method", + "enum": [ + "ach", + "cash", + "payment-card", + "paypal", + "Alipay", + "China UnionPay", + "Flexepin", + "Gpaysafe", + "Jeton", + "OchaPay", + "SMSVoucher", + "UPayCard", + "WeChat Pay" + ] + }, + "A1GatewayMpis": { + "description": "A1Gateway Mpis", + "discriminator": { + "propertyName": "name" + }, + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "allOf": [ + { + "$ref": "#/components/schemas/MpiName" + } + ], + "enum": [ + "Other" + ] + } + } + }, + "DataCashMpi": { + "description": "DataCash Integrated", + "allOf": [ + { + "$ref": "#/components/schemas/DataCashMpis" + } + ] + }, + "DataCashMpis": { + "description": "DataCash Mpis", + "discriminator": { + "propertyName": "name" + }, + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "allOf": [ + { + "$ref": "#/components/schemas/MpiName" + } + ], + "enum": [ + "DataCashMpi" + ] + } + } + }, + "eMerchantPayMpis": { + "description": "eMerchantPay Mpis", + "discriminator": { + "propertyName": "name" + }, + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "allOf": [ + { + "$ref": "#/components/schemas/MpiName" + } + ], + "enum": [ + "Other" + ] + } + } + }, + "GETMpis": { + "description": "GET Mpis", + "discriminator": { + "propertyName": "name" + }, + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "allOf": [ + { + "$ref": "#/components/schemas/MpiName" + } + ], + "enum": [ + "Other" + ] + } + } + }, + "GlobalCollectMpi": { + "description": "GlobalCollect Integrated", + "allOf": [ + { + "$ref": "#/components/schemas/GlobalCollectMpis" + } + ] + }, + "GlobalCollectMpis": { + "description": "GlobalCollect Mpis", + "discriminator": { + "propertyName": "name" + }, + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "allOf": [ + { + "$ref": "#/components/schemas/MpiName" + } + ], + "enum": [ + "GlobalCollectMpi" + ] + } + } + }, + "IlixiumMpi": { + "description": "Ilixium Integrated", + "allOf": [ + { + "$ref": "#/components/schemas/IlixiumMpis" + } + ] + }, + "IlixiumMpis": { + "description": "IlixiumMpis Mpis", + "discriminator": { + "propertyName": "name" + }, + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "allOf": [ + { + "$ref": "#/components/schemas/MpiName" + } + ], + "enum": [ + "IlixiumMpi" + ] + } + } + }, + "NMIMpis": { + "description": "NMI Mpis", + "discriminator": { + "propertyName": "name" + }, + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "allOf": [ + { + "$ref": "#/components/schemas/MpiName" + } + ], + "enum": [ + "Other" + ] + } + } + }, + "OptimalMpi": { + "description": "Optimal Integrated", + "allOf": [ + { + "$ref": "#/components/schemas/OptimalMpis" + } + ] + }, + "OptimalMpis": { + "description": "Optimal Mpis", + "discriminator": { + "propertyName": "name" + }, + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "allOf": [ + { + "$ref": "#/components/schemas/MpiName" + } + ], + "enum": [ + "OptimalMpi" + ] + } + } + }, + "Other": { + "description": "Other", + "allOf": [ + { + "$ref": "#/components/schemas/A1GatewayMpis" + }, + { + "$ref": "#/components/schemas/eMerchantPayMpis" + }, + { + "$ref": "#/components/schemas/GETMpis" + }, + { + "$ref": "#/components/schemas/NMIMpis" + }, + { + "$ref": "#/components/schemas/PayvisionMpis" + }, + { + "$ref": "#/components/schemas/VantivLitleMpis" + }, + { + "$ref": "#/components/schemas/WalpayMpis" + }, + { + "$ref": "#/components/schemas/WorldpayMpis" + } + ] + }, + "CardinalCommerce": { + "description": "CardinalCommerce Mpi Credentials", + "allOf": [ + { + "$ref": "#/components/schemas/PayvisionMpis" + }, + { + "type": "object", + "required": [ + "merchantId", + "processorId", + "transactionPwd" + ], + "properties": { + "merchantId": { + "type": "string", + "description": "Cardinal MerchantId" + }, + "processorId": { + "type": "string", + "description": "Cardinal ProcessorId" + }, + "transactionPwd": { + "type": "string", + "format": "password", + "description": "Cardinal TransactionPwd" + } + } + } + ] + }, + "PayvisionMpi": { + "description": "Payvision Integrated", + "allOf": [ + { + "$ref": "#/components/schemas/PayvisionMpis" + } + ] + }, + "PayvisionMpis": { + "description": "Payvision Mpis", + "discriminator": { + "propertyName": "name" + }, + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "allOf": [ + { + "$ref": "#/components/schemas/MpiName" + } + ], + "enum": [ + "PayvisionMpi", + "CardinalCommerce", + "Other" + ] + } + } + }, + "RebillyProcessorMpi": { + "description": "RebillyProcessor Integrated", + "allOf": [ + { + "$ref": "#/components/schemas/RebillyProcessorMpis" + } + ] + }, + "RebillyProcessorMpis": { + "description": "RebillyProcessorMpis Mpis", + "discriminator": { + "propertyName": "name" + }, + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "allOf": [ + { + "$ref": "#/components/schemas/MpiName" + } + ], + "enum": [ + "RebillySandboxMpi" + ] + } + } + }, + "VantivLitleMpis": { + "description": "VantivLitle Mpis", + "discriminator": { + "propertyName": "name" + }, + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "allOf": [ + { + "$ref": "#/components/schemas/MpiName" + } + ], + "enum": [ + "Other" + ] + } + } + }, + "WalpayMpis": { + "description": "WalpayMpis Mpis", + "discriminator": { + "propertyName": "name" + }, + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "allOf": [ + { + "$ref": "#/components/schemas/MpiName" + } + ], + "enum": [ + "Other" + ] + } + } + }, + "WirecardMpi": { + "description": "Wirecard Integrated", + "allOf": [ + { + "$ref": "#/components/schemas/WirecardMpis" + } + ] + }, + "WirecardMpis": { + "description": "Wirecard Mpis", + "discriminator": { + "propertyName": "name" + }, + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "allOf": [ + { + "$ref": "#/components/schemas/MpiName" + } + ], + "enum": [ + "WirecardMpi" + ] + } + } + }, + "WorldpayMpis": { + "description": "Worldpay Mpis", + "discriminator": { + "propertyName": "name" + }, + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "allOf": [ + { + "$ref": "#/components/schemas/MpiName" + } + ], + "enum": [ + "Other" + ] + } + } + }, + "Note": { + "type": "object", + "required": [ + "content", + "relatedType", + "relatedId" + ], + "properties": { + "id": { + "description": "The note identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "createdBy": { + "description": "The note's creator", + "type": "string", + "readOnly": true + }, + "content": { + "description": "The note's name", + "type": "string" + }, + "archived": { + "description": "Is the note archived (excluded from List method)", + "type": "boolean" + }, + "relatedType": { + "description": "The note's related resource type", + "type": "string", + "enum": [ + "customer", + "payment-card", + "payment-gateway", + "subscription", + "transaction" + ] + }, + "relatedId": { + "description": "The note's related resource ID", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "createdTime": { + "description": "Note created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "Note updated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "archivedTime": { + "description": "Note archived time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "OnOff": { + "type": "string", + "default": "active", + "enum": [ + "active", + "inactive" + ] + }, + "Organization": { + "type": "object", + "required": [ + "name", + "country" + ], + "properties": { + "id": { + "description": "The organization identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "createdTime": { + "$ref": "#/components/schemas/ServerTimestamp" + }, + "updatedTime": { + "description": "The organization updated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "name": { + "description": "The organization name", + "type": "string", + "maxLength": 60 + }, + "address": { + "description": "The organization street address", + "type": "string", + "maxLength": 60 + }, + "address2": { + "description": "The organization street address", + "type": "string", + "maxLength": 60 + }, + "city": { + "description": "The organization city", + "type": "string", + "maxLength": 45 + }, + "region": { + "description": "The organization region (state)", + "type": "string", + "maxLength": 45 + }, + "country": { + "description": "The organization country ISO Alpha-2 code", + "type": "string", + "pattern": "^[A-Z]{2}$" + }, + "postalCode": { + "description": "The organization postal code", + "type": "string", + "maxLength": 10 + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "Payment": { + "type": "object", + "required": [ + "websiteId", + "customerId", + "currency", + "amount" + ], + "properties": { + "id": { + "description": "The payment identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "websiteId": { + "description": "The website identifier string", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "customerId": { + "description": "The customer identifier string", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "currency": { + "description": "The payment currency ISO Alpha code", + "type": "string" + }, + "amount": { + "description": "The payment amount", + "type": "integer", + "format": "double" + }, + "scheduledTime": { + "description": "The time the payment is scheduled for collection", + "type": "string", + "format": "date-time" + }, + "invoiceIds": { + "description": "The array of invoice identifiers", + "type": "array", + "items": { + "$ref": "#/components/schemas/ResourceId" + } + }, + "description": { + "description": "The payment description", + "type": "string", + "maxLength": 255 + }, + "retryInstruction": { + "$ref": "#/components/schemas/PaymentRetry" + }, + "retryNumber": { + "readOnly": true, + "description": "The position in the sequence of retries", + "type": "integer" + }, + "retriedPaymentId": { + "readOnly": true, + "description": "The retried payment ID", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "customFields": { + "$ref": "#/components/schemas/ResourceCustomFields" + }, + "paymentInstrument": { + "$ref": "#/components/schemas/PaymentInstrument" + }, + "billingAddress": { + "description": "Billing Address", + "allOf": [ + { + "$ref": "#/components/schemas/ContactObject" + } + ] + }, + "createdBy": { + "description": "The process that created this payment", + "type": "string" + }, + "updatedBy": { + "description": "The process that updated this payment", + "type": "string" + }, + "status": { + "description": "Payment status", + "type": "string", + "readOnly": true, + "enum": [ + "scheduled", + "queued", + "in-progress", + "incomplete", + "waiting-gateway", + "suspended", + "completed" + ] + }, + "result": { + "description": "Payment result", + "type": "string", + "readOnly": true, + "enum": [ + "approved", + "declined", + "canceled", + "unknown" + ] + }, + "riskMetadata": { + "description": "Risk metadata", + "allOf": [ + { + "$ref": "#/components/schemas/RiskMetadata" + } + ] + }, + "createdTime": { + "description": "Payment created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "Payment updated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 4, + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/SelfLink" + }, + { + "$ref": "#/components/schemas/WebsiteLink" + }, + { + "$ref": "#/components/schemas/TransactionLink" + }, + { + "$ref": "#/components/schemas/ApprovalUrlLink" + }, + { + "$ref": "#/components/schemas/LeadSourceLink" + } + ] + } + } + } + }, + "PaymentCard": { + "type": "object", + "properties": { + "id": { + "description": "The card identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "customerId": { + "description": "The Customer's ID. Required if card is creating not from Token", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "pan": { + "description": "The card PAN (Primary Account Number). Required if card is creating not from Token", + "type": "string" + }, + "bin": { + "description": "The card's bin (the PAN's first 6 digits)", + "type": "string", + "format": "bin", + "readOnly": true + }, + "last4": { + "description": "The PAN's last 4 digits", + "type": "string", + "readOnly": true + }, + "expYear": { + "description": "Card's expiry year. Required if card is creating not from Token", + "type": "integer" + }, + "expMonth": { + "description": "Card's expiry month. Required if card is creating not from Token", + "type": "integer" + }, + "cvv": { + "description": "Card's cvv (card verification value). Required if card is creating not from Token", + "type": "string" + }, + "billingAddress": { + "description": "The Billing Address. Required if card is creating not from Token", + "allOf": [ + { + "$ref": "#/components/schemas/ContactObject" + } + ] + }, + "token": { + "description": "PaymentCardToken. Use without any other fields", + "type": "string" + }, + "safeHash": { + "description": "The card's hash. Based on bin and last 4 digits of the PAN", + "type": "string", + "readOnly": true + }, + "status": { + "description": "Payment Card status", + "type": "string", + "readOnly": true, + "enum": [ + "active", + "expired", + "inactive", + "deactivated", + "pending" + ] + }, + "brand": { + "description": "Payment Card brand", + "type": "string", + "readOnly": true, + "enum": [ + "Visa", + "MasterCard", + "American Express", + "Discover", + "Maestro", + "Solo", + "Electron", + "JCB", + "Voyager", + "Diners Club", + "Switch", + "Laser", + "China UnionPay" + ] + }, + "bankCountry": { + "description": "Payment Card bank country", + "type": "string", + "readOnly": true + }, + "bankName": { + "description": "Payment Card bank name", + "type": "string", + "readOnly": true + }, + "createdTime": { + "description": "Card created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "Card updated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "customFields": { + "$ref": "#/components/schemas/ResourceCustomFields" + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 3, + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/SelfLink" + }, + { + "$ref": "#/components/schemas/CustomerLink" + }, + { + "$ref": "#/components/schemas/BillingContactLink" + } + ] + } + } + } + }, + "PaymentCardMigrationRequest": { + "type": "object", + "required": [ + "fromGatewayAccountId", + "toGatewayAccountId", + "paymentCardIds" + ], + "properties": { + "fromGatewayAccountId": { + "description": "An ID of Gateway Account cards should be migrated from", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "toGatewayAccountId": { + "description": "An ID of Gateway Account cards should be migrated to", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "paymentCardIds": { + "type": "array", + "minItems": 1, + "items": { + "description": "An array of payment card IDs", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + } + } + } + }, + "PaymentCardMigrationResponse": { + "type": "object", + "properties": { + "migratedCards": { + "type": "integer", + "description": "Amount of cards that were successfully migrated" + } + } + }, + "PaymentInstrument": { + "type": "object", + "required": [ + "method" + ], + "discriminator": { + "propertyName": "method" + }, + "properties": { + "method": { + "$ref": "#/components/schemas/Method" + } + } + }, + "ach": { + "description": "ACH payment instrument object", + "allOf": [ + { + "$ref": "#/components/schemas/PaymentInstrument" + }, + { + "type": "object", + "required": [ + "bankAccountId" + ], + "properties": { + "bankAccountId": { + "description": "The bank account identifier string", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "gatewayAccountId": { + "description": "The payment gateway identifier string", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + } + } + } + ] + }, + "cash": { + "description": "Cash payment instrument object", + "allOf": [ + { + "$ref": "#/components/schemas/PaymentInstrument" + }, + { + "type": "object", + "properties": { + "receivedBy": { + "description": "The receiver's name", + "type": "string" + } + } + } + ] + }, + "payment-card": { + "description": "Payment card payment instrument object", + "allOf": [ + { + "$ref": "#/components/schemas/PaymentInstrument" + }, + { + "type": "object", + "required": [ + "paymentCardId" + ], + "properties": { + "paymentCardId": { + "description": "The payment card identifier string", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "gatewayAccountId": { + "description": "The payment gateway identifier string", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + } + } + } + ] + }, + "paypal": { + "description": "PayPal payment instrument object", + "allOf": [ + { + "$ref": "#/components/schemas/PaymentInstrument" + }, + { + "type": "object", + "required": [ + "payPalAccountId" + ], + "properties": { + "payPalAccountId": { + "description": "The PayPal account identifier string", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "gatewayAccountId": { + "description": "The payment gateway identifier string", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + } + } + } + ] + }, + "AmountAdjustment": { + "type": "object", + "discriminator": { + "propertyName": "method" + }, + "properties": { + "method": { + "type": "string", + "enum": [ + "none", + "partial", + "discount" + ] + } + }, + "required": [ + "method" + ] + }, + "PaymentRetry": { + "type": "object", + "properties": { + "attempts": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "scheduleInstruction": { + "$ref": "#/components/schemas/CommonScheduleInstruction" + }, + "paymentInstruction": { + "$ref": "#/components/schemas/AmountAdjustment" + } + }, + "required": [ + "scheduleInstruction", + "paymentInstruction" + ] + } + }, + "afterAttemptPolicy": { + "description": "The policy on the attempt finishes", + "type": "string", + "enum": [ + "none", + "change-subscription-renewal-time" + ] + }, + "afterRetryEndPolicy": { + "description": "The policy on the retry ends", + "type": "string", + "enum": [ + "none", + "cancel-subscription" + ] + } + }, + "required": [ + "attempts", + "afterAttemptPolicy", + "afterRetryEndPolicy" + ] + }, + "discount": { + "allOf": [ + { + "$ref": "#/components/schemas/AmountAdjustment" + }, + { + "type": "object", + "properties": { + "value": { + "description": "The payment amount discount", + "type": "number", + "format": "float" + }, + "type": { + "description": "The payment amount discount type", + "type": "string", + "enum": [ + "percent", + "fixed" + ] + } + }, + "required": [ + "value", + "type" + ] + } + ] + }, + "partial": { + "allOf": [ + { + "$ref": "#/components/schemas/AmountAdjustment" + }, + { + "type": "object", + "properties": { + "value": { + "description": "The payment amount", + "type": "number", + "format": "float" + }, + "type": { + "description": "The payment amount type", + "type": "string", + "enum": [ + "percent", + "fixed" + ] + } + }, + "required": [ + "value", + "type" + ] + } + ] + }, + "PaymentToken": { + "type": "object", + "required": [ + "method", + "paymentInstrument" + ], + "properties": { + "id": { + "description": "The token identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "method": { + "$ref": "#/components/schemas/Method" + }, + "paymentInstrument": { + "$ref": "#/components/schemas/PaymentTokenInstrument" + }, + "fingerprint": { + "description": "Device fingerprint hash", + "type": "string" + }, + "billingAddress": { + "description": "The Address. Required if bank account is not created from Token", + "allOf": [ + { + "$ref": "#/components/schemas/ContactObject" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "PaymentTokenInstrument": { + "type": "object", + "description": "Payment Token instrument object", + "properties": { + "pan": { + "description": "The card PAN (Primary Account Number)", + "type": "string" + }, + "expMonth": { + "description": "Card's expiry month", + "type": "integer" + }, + "expYear": { + "description": "Card's expiry year", + "type": "integer" + }, + "cvv": { + "description": "The CVV/CVC of the payment card", + "type": "string" + }, + "routingNumber": { + "description": "Routing Number", + "type": "integer" + }, + "accountNumber": { + "description": "AccountNumber", + "type": "integer" + }, + "accountType": { + "description": "Account Type", + "type": "string" + }, + "bankName": { + "description": "Bank name", + "type": "string" + } + } + }, + "PayPalAccount": { + "type": "object", + "properties": { + "id": { + "description": "The PayPal identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "customerId": { + "description": "The Customer's ID.", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "billingAddress": { + "description": "The Customer's Billing Address.", + "allOf": [ + { + "$ref": "#/components/schemas/ContactObject" + } + ] + }, + "username": { + "description": "PayPal username.", + "type": "string" + }, + "status": { + "description": "PayPal Account status", + "type": "string", + "readOnly": true, + "enum": [ + "inactive", + "active", + "deactivated" + ] + }, + "createdTime": { + "description": "PayPal Account created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "PayPal Account updated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "customFields": { + "$ref": "#/components/schemas/ResourceCustomFields" + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 4, + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/SelfLink" + }, + { + "$ref": "#/components/schemas/CustomerLink" + }, + { + "$ref": "#/components/schemas/ContactLink" + }, + { + "$ref": "#/components/schemas/ApprovalUrlLink" + } + ] + } + } + }, + "required": [ + "customerId", + "billingAddress" + ] + }, + "Plan": { + "type": "object", + "required": [ + "name", + "currency" + ], + "properties": { + "id": { + "description": "The website identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "name": { + "description": "The plan name", + "type": "string" + }, + "currency": { + "description": "Currency (three letter ISO 4217 code)", + "type": "string" + }, + "currencySign": { + "description": "Currency sign", + "type": "string" + }, + "isActive": { + "description": "If the plan is not active, customers cannot subscribe to the plan (default to true)", + "type": "boolean" + }, + "description": { + "description": "The plan description", + "type": "string" + }, + "richDescription": { + "description": "The plan rich description - supports HTML", + "type": "string" + }, + "recurringAmount": { + "description": "The amount that recurs according to the schedule", + "type": "number", + "format": "double" + }, + "recurringPeriodUnit": { + "description": "The unit of time", + "type": "string", + "enum": [ + "day", + "week", + "month", + "year" + ] + }, + "recurringPeriodLength": { + "description": "The length of time (used with the recurringPeriodUnit)", + "type": "integer" + }, + "trialAmount": { + "description": "The amount of a trial - 0 is a valid value (for free)", + "type": "number", + "format": "double" + }, + "trialPeriodUnit": { + "description": "The unit of time", + "type": "string", + "enum": [ + "day", + "week", + "month", + "year" + ] + }, + "trialPeriodLength": { + "description": "The length of time (used with the trialPeriodUnit)", + "type": "integer" + }, + "setupAmount": { + "description": "The amount of a trial - 0 is a valid value (for free)", + "type": "number", + "format": "double" + }, + "expiredTime": { + "description": "Time when the plan is not longer valid", + "type": "string", + "format": "date-time" + }, + "contractTermUnit": { + "description": "The unit of time", + "type": "string", + "enum": [ + "day", + "week", + "month", + "year" + ] + }, + "contractTermLength": { + "description": "The length that corresponds with the contractTermUnit", + "type": "integer" + }, + "recurringPeriodLimit": { + "description": "The number of times a subscription will rebill until the contract is over", + "type": "integer" + }, + "minQuantity": { + "description": "Minimum quantity per order, defaults to 1", + "type": "integer" + }, + "maxQuantity": { + "description": "Maximum quantity per order (NULL if no maximum)", + "type": "integer" + }, + "createdTime": { + "description": "Plan created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "Plan updated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "PriceBasedShippingRate": { + "type": "object", + "required": [ + "name", + "currency", + "price" + ], + "properties": { + "name": { + "description": "The shipping rate name", + "type": "string", + "maxLength": 255 + }, + "minOrderSubtotal": { + "description": "Minimum order subtotal for which this shipping rate is applicable, defaults to 0.00", + "type": "number", + "format": "double", + "default": 0 + }, + "maxOrderSubtotal": { + "description": "Maximum order subtotal for which this shipping rate is applicable (NULL if no maximum)", + "type": "number", + "format": "double" + }, + "price": { + "description": "The shipping price - 0 is a valid value (for free)", + "type": "number", + "format": "double" + }, + "currency": { + "description": "Currency (three letter ISO 4217 code)", + "type": "string" + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "Product": { + "type": "object", + "required": [ + "name" + ], + "description": "Your product includes digital goods, services, and physical goods.\nProducts appear on invoice line items. If you set a tax category identifier,\ntaxes will be calculated upon invoice generation.\n", + "example": "{\n \"id\": \"stringid\",\n \"name\": \"Widget\",\n \"description\": \"Fantastic widget\",\n \"taxCategoryId\": null,\n \"requiresShipping\": true,\n \"accountingCode\": \"100\",\n \"customFields\": [],\n \"createdTime\": \"2015-08-27 13:45:12\",\n \"updatedTime\": \"2015-08-27 13:45:12\",\n \"_links\": [\n {\n \"rel\": \"self\",\n \"href\": \"https://api.rebilly.com/v2.1/products/stringid\"\n }\n ]\n}\n", + "properties": { + "id": { + "description": "The product identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "name": { + "description": "The product name", + "type": "string", + "maxLength": 255 + }, + "description": { + "description": "The product description", + "type": "string", + "maxLength": 512 + }, + "taxCategoryId": { + "description": "The product's tax category identifier string", + "type": "string", + "enum": [ + 99999, + 20010, + 40030, + 51020, + 51010, + 31000, + 30070 + ] + }, + "requiresShipping": { + "description": "If the product requires shipping, shipping calculations will be applied", + "type": "boolean" + }, + "accountingCode": { + "description": "The product accounting code", + "type": "string" + }, + "customFields": { + "$ref": "#/components/schemas/ResourceCustomFields" + }, + "createdTime": { + "description": "The product created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "The product updated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "Profile": { + "type": "object", + "properties": { + "id": { + "description": "The user identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "email": { + "description": "The user email", + "readOnly": true, + "type": "string", + "format": "email", + "maxLength": 100 + }, + "firstName": { + "description": "User's first name", + "readOnly": true, + "type": "string" + }, + "lastName": { + "description": "User's last name", + "readOnly": true, + "type": "string" + }, + "businessPhone": { + "description": "The user business phone number", + "readOnly": true, + "type": "string" + }, + "mobilePhone": { + "description": "The user mobile phone number", + "readOnly": true, + "type": "string" + }, + "availableCurrencies": { + "type": "array", + "description": "An array of reporting currencies enabled for the merchant", + "readOnly": true, + "items": { + "type": "string" + } + }, + "reportingCurrency": { + "description": "The user's ISO Alpha-3 code used for reports", + "type": "string" + }, + "totpRequired": { + "description": "The user setting of two-factor authentification", + "readOnly": true, + "type": "boolean" + }, + "totpSecret": { + "description": "The user TOTP key for authentification app (if TOTP enabled)", + "readOnly": true, + "type": "string" + }, + "totpUrl": { + "description": "The user link to QR-code for TOTP authentification app (if TOTP enabled)", + "readOnly": true, + "type": "string", + "format": "url" + }, + "country": { + "description": "The user country setting - two letter code", + "readOnly": true, + "type": "string" + }, + "preferences": { + "description": "User preferences like timezone, language and many more. This is an object with custom properties.", + "type": "object" + } + } + }, + "ResetPassword": { + "type": "object", + "required": [ + "newPassword" + ], + "properties": { + "newPassword": { + "description": "New password", + "type": "string", + "format": "password" + } + } + }, + "ResetPasswordToken": { + "type": "object", + "required": [ + "username", + "password" + ], + "properties": { + "token": { + "description": "The token's identifier string", + "type": "string", + "readOnly": true + }, + "username": { + "description": "The token's username", + "type": "string" + }, + "password": { + "description": "Token's password (only for POST)", + "type": "string", + "format": "password" + }, + "credential": { + "description": "Token's credential ID", + "type": "string" + }, + "expiredTime": { + "description": "Password expired time", + "type": "string", + "format": "date-time" + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "ResourceCustomFields": { + "description": "\"Custom Fields list as a map `{\"custom field name\": \"custom field value\", ...}\"`.\nThe format must follow the saved format (see Custom Fields section for the formats).\n", + "type": "object" + }, + "ResourceId": { + "type": "string", + "description": "The resource ID. Defaults to UUID v4", + "maxLength": 50, + "example": "4f6cf35x-2c4y-483z-a0a9-158621f77a21" + }, + "RiskMetadata": { + "type": "object", + "properties": { + "ipAddress": { + "description": "The customer's IP", + "type": "string", + "format": "ipv4" + }, + "isProxy": { + "description": "True if customer's ip address is related to proxy", + "type": "boolean", + "readOnly": true + }, + "isVpn": { + "description": "True if customer's ip address is related to VPN", + "type": "boolean", + "readOnly": true + }, + "isTor": { + "description": "True if customer's ip address is related to TOR", + "type": "boolean", + "readOnly": true + }, + "isHosting": { + "description": "True if customer's ip address is related to hosting", + "type": "boolean", + "readOnly": true + }, + "vpnServiceName": { + "description": "VPN service name, if available", + "type": "string", + "readOnly": true + }, + "isp": { + "description": "Internet Service Provider name, if available", + "type": "string", + "readOnly": true + }, + "country": { + "description": "Country ISO Alpha-2 code for specified ipAddress", + "maxLength": 2, + "type": "string", + "readOnly": true, + "example": "US" + }, + "city": { + "description": "City for specified ipAddress", + "type": "string", + "readOnly": true, + "example": "New York" + }, + "latitude": { + "description": "Latitude for specified ipAddress", + "type": "number", + "format": "double", + "readOnly": true + }, + "longitude": { + "description": "Longitude for specified ipAddress", + "type": "number", + "format": "double", + "readOnly": true + }, + "postalCode": { + "description": "Postal code for specified ipAddress", + "type": "string", + "maxLength": 10, + "readOnly": true + }, + "timeZone": { + "description": "Time zone for specified ipAddress", + "type": "string", + "readOnly": true, + "example": "America/New_York" + }, + "accuracyRadius": { + "description": "Accuracy radius for specified ipAddress (kilometers)", + "type": "integer", + "readOnly": true + }, + "fingerprint": { + "description": "The fingerprint", + "type": "string" + }, + "httpHeaders": { + "description": "HTTP headers", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "example": { + "User-Agent": "Mozilla/5.0", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + } + } + } + }, + "add-risk-score": { + "allOf": [ + { + "$ref": "#/components/schemas/RuleAction" + }, + { + "type": "object", + "description": "Add risk score", + "properties": { + "score": { + "type": "integer", + "default": 0 + } + } + } + ] + }, + "blacklist": { + "description": "Add customer data to blacklist", + "allOf": [ + { + "$ref": "#/components/schemas/RuleAction" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "customer-id", + "email", + "fingerprint", + "ip-address", + "payment-card-id" + ] + }, + "ttl": { + "type": "integer", + "description": "Blacklist TTL. Defaults to zero, meaning blacklist record won't expire ever.", + "default": 0 + } + }, + "required": [ + "type" + ] + } + ] + }, + "cancel-scheduled-payments": { + "description": "", + "allOf": [ + { + "$ref": "#/components/schemas/RuleAction" + } + ] + }, + "guess-payment-card-expiration": { + "description": "", + "allOf": [ + { + "$ref": "#/components/schemas/RuleAction" + } + ] + }, + "pick-gateway-account": { + "description": "", + "allOf": [ + { + "$ref": "#/components/schemas/RuleAction" + }, + { + "type": "object", + "properties": { + "pickInstruction": { + "$ref": "#/components/schemas/GatewayAccountPickInstruction" + } + }, + "required": [ + "pickInstruction" + ] + } + ] + }, + "schedule-payment-retry": { + "description": "", + "allOf": [ + { + "$ref": "#/components/schemas/RuleAction" + }, + { + "$ref": "#/components/schemas/PaymentRetry" + } + ] + }, + "schedule-payment": { + "description": "", + "allOf": [ + { + "$ref": "#/components/schemas/RuleAction" + }, + { + "type": "object", + "description": "The calculation instruction of scheduled time for payment", + "properties": { + "scheduleInstruction": { + "$ref": "#/components/schemas/CommonScheduleInstruction" + }, + "amountPolicy": { + "type": "string", + "enum": [ + "balance-outstanding", + "invoice-total" + ] + } + }, + "required": [ + "scheduleInstruction", + "amountPolicy" + ] + } + ] + }, + "send-email": { + "description": "", + "allOf": [ + { + "$ref": "#/components/schemas/RuleAction" + }, + { + "$ref": "#/components/schemas/EmailNotification" + } + ] + }, + "stop-subscriptions": { + "description": "Stop active subscriptions", + "allOf": [ + { + "$ref": "#/components/schemas/RuleAction" + } + ] + }, + "trigger-webhook": { + "description": "", + "allOf": [ + { + "$ref": "#/components/schemas/RuleAction" + }, + { + "$ref": "#/components/schemas/Webhook" + } + ] + }, + "SendPreviewWebhook": { + "description": "Trigger a test webhook", + "allOf": [ + { + "$ref": "#/components/schemas/Webhook" + } + ] + }, + "SendTestEmail": { + "description": "Send a test email", + "allOf": [ + { + "$ref": "#/components/schemas/EmailNotification" + } + ] + }, + "EmailNotification": { + "type": "object", + "properties": { + "credentialHash": { + "type": "string", + "description": "SMTP Credential identifier string." + }, + "sender": { + "type": "string", + "description": "The sender address. The template palceholders are allowed." + }, + "recipients": { + "type": "array", + "description": "The recipients addresses. The template palceholders are allowed.", + "minItems": 1, + "items": { + "type": "string" + } + }, + "cc": { + "type": "array", + "description": "The recipients addresses. The template palceholders are allowed.", + "items": { + "type": "string" + } + }, + "bcc": { + "type": "array", + "description": "The hidden recipients addresses. The template palceholders are allowed.", + "items": { + "type": "string" + } + }, + "subject": { + "type": "string", + "description": "The message subject. The template palceholders are allowed." + }, + "bodyText": { + "type": "string", + "description": "Leave empty to use text from \"bodyHtml\" without tags.\nThe template palceholders are allowed.\n" + }, + "bodyHtml": { + "type": "string", + "description": "Leave empty to recieve \"text/plain\" email.\nThe template palceholders are allowed.\n" + } + }, + "required": [ + "credentialHash", + "sender", + "recipients", + "subject", + "bodyText", + "bodyHtml" + ] + }, + "GatewayAccountPickInstruction": { + "type": "object", + "discriminator": { + "propertyName": "method" + }, + "properties": { + "method": { + "type": "string", + "enum": [ + "gateway-account-weights", + "gateway-acquirer-weights" + ] + } + }, + "required": [ + "method" + ] + }, + "gateway-account-weights": { + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccountPickInstruction" + }, + { + "type": "object", + "properties": { + "weightedList": { + "type": "array", + "uniqueItems": true, + "minimum": 0, + "items": { + "type": "object", + "properties": { + "gatewayAccountId": { + "$ref": "#/components/schemas/ResourceId" + }, + "weight": { + "type": "integer" + } + }, + "required": [ + "gatewayAccountId", + "weight" + ] + }, + "example": "[\n {\"gatewayAccountId\": \"my_gateway_account_1\", \"weight\": 80},\n {\"gatewayAccountId\": \"my_gateway_account_2\", \"weight\": 20}\n]\n" + } + }, + "required": [ + "weightedList" + ] + } + ] + }, + "gateway-acquirer-weights": { + "allOf": [ + { + "$ref": "#/components/schemas/GatewayAccountPickInstruction" + }, + { + "type": "object", + "properties": { + "weightedList": { + "type": "array", + "uniqueItems": true, + "minimum": 0, + "items": { + "type": "object", + "properties": { + "gatewayName": { + "$ref": "#/components/schemas/GatewayName" + }, + "acquirerName": { + "$ref": "#/components/schemas/AcquirerName" + }, + "weight": { + "type": "integer" + } + }, + "required": [ + "gatewayName", + "acquirerName", + "weight" + ] + }, + "example": "[\n {\"gatewayName\": \"RebillyProcessor\", \"acquirerName\": \"AIB\", \"weight\": 80},\n {\"gatewayName\": \"RebillyProcessor\", \"acquirerName\": \"B+S\", \"weight\": 20}\n" + } + }, + "required": [ + "weightedList" + ] + } + ] + }, + "Rule": { + "type": "object", + "description": "The rule", + "properties": { + "name": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/OnOff" + }, + "final": { + "description": "Whether rule is final, meaning stop further matching rules if this is matched", + "type": "boolean", + "default": true + }, + "criteria": { + "$ref": "#/components/schemas/Condition" + }, + "actions": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/components/schemas/RuleAction" + } + } + }, + "required": [ + "name", + "actions" + ] + }, + "RuleAction": { + "type": "object", + "discriminator": { + "propertyName": "name" + }, + "properties": { + "name": { + "type": "string", + "description": "The action name", + "enum": [ + "blacklist", + "cancel-scheduled-payments", + "guess-payment-card-expiration", + "pick-gateway-account", + "schedule-payment-retry", + "schedule-payment", + "send-email", + "trigger-webhook", + "stop-subscriptions", + "add-risk-score" + ] + }, + "status": { + "$ref": "#/components/schemas/OnOff" + } + }, + "required": [ + "name" + ] + }, + "RuleSet": { + "type": "object", + "description": "Set of rules for particular event", + "properties": { + "version": { + "type": "integer", + "readOnly": true + }, + "rules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Rule" + } + }, + "updatedTime": { + "$ref": "#/components/schemas/ServerTimestamp" + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + }, + "required": [ + "rules" + ] + }, + "RuleSetHistoryItem": { + "type": "object", + "description": "Version of rules", + "readOnly": true, + "properties": { + "version": { + "type": "integer" + }, + "createdTime": { + "$ref": "#/components/schemas/ServerTimestamp" + }, + "_links": { + "type": "array", + "description": "The links related to a resource", + "readOnly": true, + "minItems": 2, + "maxItems": 2, + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/SelfLink" + }, + { + "$ref": "#/components/schemas/RuleSetVersionLink" + } + ] + } + } + } + }, + "RuleSetVersion": { + "type": "object", + "description": "Version of rules", + "readOnly": true, + "properties": { + "version": { + "type": "integer" + }, + "rules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Rule" + } + }, + "createdTime": { + "$ref": "#/components/schemas/ServerTimestamp" + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "Webhook": { + "type": "object", + "properties": { + "method": { + "type": "string", + "enum": [ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE" + ] + }, + "url": { + "type": "string", + "format": "uri" + }, + "query": { + "type": "object", + "description": "The URI parameters", + "additionalProperties": { + "type": "string", + "example": { + "param1": "value1", + "param2": "value2" + } + } + }, + "body": { + "type": "string" + }, + "credentialHash": { + "type": "string", + "description": "Webhook Credential identifier string." + }, + "headers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebhookHeader" + } + } + }, + "required": [ + "method", + "url" + ] + }, + "WebhookHeader": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/OnOff" + }, + "value": { + "type": "string", + "example": { + "Header1": "value1", + "Header2": "value2;value3" + } + } + }, + "required": [ + "name", + "value" + ] + }, + "CommonScheduleInstruction": { + "type": "object", + "description": "The calculation instruction of scheduled time", + "discriminator": { + "propertyName": "method" + }, + "properties": { + "method": { + "type": "string", + "enum": [ + "auto", + "immediately", + "date-interval", + "day-of-month", + "day-of-week" + ] + } + }, + "required": [ + "method" + ] + }, + "CustomEventScheduleInstruction": { + "type": "object", + "description": "The calculation instruction of scheduled time", + "discriminator": { + "propertyName": "method" + }, + "properties": { + "method": { + "type": "string", + "enum": [ + "date-interval", + "day-of-month", + "day-of-week" + ] + } + }, + "required": [ + "method" + ] + }, + "DayOfWeek": { + "type": "string", + "enum": [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday" + ] + }, + "auto": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonScheduleInstruction" + } + ] + }, + "date-interval": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonScheduleInstruction" + }, + { + "$ref": "#/components/schemas/CustomEventScheduleInstruction" + }, + { + "type": "object", + "properties": { + "duration": { + "type": "integer", + "description": "The number of the units", + "minimum": 1 + }, + "unit": { + "$ref": "#/components/schemas/TimeUnit" + } + }, + "required": [ + "duration", + "unit" + ] + } + ] + }, + "day-of-month": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonScheduleInstruction" + }, + { + "$ref": "#/components/schemas/CustomEventScheduleInstruction" + }, + { + "type": "object", + "properties": { + "day": { + "type": "integer", + "minimum": 1, + "maximum": 31, + "description": "The day of the month when event will be scheduled.\nBe aware if the month has less days,\nthe last day of the month will be selected.\n" + }, + "time": { + "$ref": "#/components/schemas/Time" + } + }, + "required": [ + "day" + ] + } + ] + }, + "day-of-week": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonScheduleInstruction" + }, + { + "$ref": "#/components/schemas/CustomEventScheduleInstruction" + }, + { + "type": "object", + "properties": { + "day": { + "$ref": "#/components/schemas/DayOfWeek" + }, + "week": { + "type": "string", + "default": "next", + "enum": [ + "next", + "first-in-month", + "last-in-month" + ] + }, + "time": { + "$ref": "#/components/schemas/Time" + } + }, + "required": [ + "day" + ] + } + ] + }, + "immediately": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonScheduleInstruction" + } + ] + }, + "Time": { + "type": "string", + "format": "date-time", + "pattern": "^([0-9]|0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](Z(\\+|\\-)([0-9]|0[0-9]|1[0-9]|2[0-3]):[0-5][0-9])?$" + }, + "TimeUnit": { + "type": "string", + "enum": [ + "second", + "seconds", + "minute", + "minutes", + "hour", + "hours", + "day", + "days", + "month", + "months", + "year", + "years" + ] + }, + "ServerTimestamp": { + "type": "string", + "description": "Read-only timestamp, automatically assigned on back-end.", + "format": "date-time", + "readOnly": true + }, + "Session": { + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "id": { + "description": "The session identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "token": { + "description": "The session's token used for authentication", + "type": "string" + }, + "permissions": { + "description": "The session's permissions. See the format in example", + "allOf": [ + { + "$ref": "#/components/schemas/UserPermissions" + } + ] + }, + "userId": { + "description": "The user identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "createdTime": { + "description": "Session created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "Session updated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "expiredTime": { + "description": "Session expired time. Defaults to one hour", + "type": "string", + "format": "date-time" + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "ShippingZone": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "id": { + "description": "The shipping zone identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "name": { + "description": "The shipping zone name", + "type": "string", + "maxLength": 255 + }, + "countries": { + "description": "Countries covered by the shipping zone. A country can only belong to one shipping zone (no overlapping).\nThis property can be empty or null to create a default shipping zone for countries that were not specified in other zones.\n", + "type": "array", + "items": { + "description": "Country ISO Alpha-2 code", + "type": "string", + "pattern": "^[A-Z]{2}$" + } + }, + "rates": { + "description": "Price-based shipping rate instructions", + "type": "array", + "items": { + "description": "Price based shipping rate instruction", + "allOf": [ + { + "$ref": "#/components/schemas/PriceBasedShippingRate" + } + ] + } + }, + "isDefault": { + "description": "Is this Shipping Zone default", + "readOnly": true + }, + "createdTime": { + "description": "The shipping zone created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "The shipping zone updated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "Signin": { + "type": "object", + "required": [ + "email", + "password" + ], + "properties": { + "email": { + "description": "Email", + "type": "string" + }, + "password": { + "description": "Password", + "type": "string", + "format": "password" + }, + "expiredTime": { + "description": "Session expired time. Defaults to one hour", + "type": "string", + "format": "date-time" + } + } + }, + "Signup": { + "type": "object", + "required": [ + "email", + "company", + "firstName", + "lastName", + "businessPhone", + "password", + "website" + ], + "properties": { + "email": { + "description": "The user email", + "type": "string", + "format": "email", + "maxLength": 100 + }, + "company": { + "description": "The user's company name", + "type": "string" + }, + "firstName": { + "description": "The user first name", + "type": "string" + }, + "lastName": { + "description": "The user last name", + "type": "string" + }, + "businessPhone": { + "description": "The user business phone number", + "type": "string" + }, + "password": { + "description": "The user password", + "type": "string", + "format": "password" + }, + "website": { + "description": "The user's website address", + "type": "string" + }, + "currencies": { + "description": "An array of currencies codes", + "type": "array", + "default": [ + "USD" + ], + "items": { + "description": "3 letters ISO 4217 currency code", + "type": "string" + } + }, + "merchantCategoryCode": { + "description": "Merchant category code. Defaults to \"Computer Software Stores\"", + "type": "integer", + "default": 5734 + } + } + }, + "Status": { + "type": "object", + "properties": { + "status": { + "description": "The API status. If everything is ok - value is 'ok'", + "type": "string", + "readOnly": true, + "enum": [ + "ok" + ] + }, + "time": { + "description": "Current time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + } + } + }, + "Subscription": { + "type": "object", + "required": [ + "customerId", + "planId", + "websiteId" + ], + "properties": { + "id": { + "description": "The Subscription identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "customerId": { + "description": "Unique id for each customer", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "planId": { + "description": "Unique id for each plan", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "websiteId": { + "description": "Unique id for each website", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "initialInvoiceId": { + "description": "Unique id for the initial invoice", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "deliveryAddress": { + "description": "Delivery address", + "allOf": [ + { + "$ref": "#/components/schemas/ContactObject" + } + ] + }, + "billingAddress": { + "description": "Billing address", + "allOf": [ + { + "$ref": "#/components/schemas/ContactObject" + } + ] + }, + "status": { + "description": "Subscription status", + "type": "string", + "readOnly": true + }, + "quantity": { + "description": "Quantity for each subscription. Default value to 1", + "type": "integer" + }, + "autopay": { + "description": "Autopay determines if a payment attempt will be automatic", + "type": "boolean", + "default": true + }, + "inTrial": { + "description": "True if the subscription is currently in a trial period", + "type": "boolean", + "readOnly": true + }, + "rebillNumber": { + "description": "The current period number", + "type": "integer", + "readOnly": true + }, + "canceledBy": { + "description": "Canceled by", + "type": "string", + "readOnly": true, + "enum": [ + "merchant", + "customer", + "rebilly" + ] + }, + "cancelCategory": { + "description": "Cancel category", + "type": "string", + "readOnly": true, + "enum": [ + "billing-failure", + "did-not-use", + "did-not-want", + "missing-features", + "bugs-or-problems", + "do-not-remember", + "risk-warning", + "contract-expired", + "too-expensive", + "never-started", + "switched-plan", + "other" + ] + }, + "cancelDescription": { + "description": "Cancel reason description in free form", + "type": "string", + "readOnly": true, + "maxLength": 255 + }, + "riskMetadata": { + "description": "Risk metadata", + "allOf": [ + { + "$ref": "#/components/schemas/RiskMetadata" + } + ] + }, + "startTime": { + "description": "Subscription start time", + "type": "string", + "format": "date-time" + }, + "activationTime": { + "description": "Subscription activation time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "endTime": { + "description": "Subscription end time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "renewalTime": { + "description": "Subscription renewal time", + "type": "string", + "format": "date-time" + }, + "canceledTime": { + "description": "Subscription canceled time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "createdTime": { + "description": "Subscription created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "Subscription updated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "customFields": { + "$ref": "#/components/schemas/ResourceCustomFields" + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 6, + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/SelfLink" + }, + { + "$ref": "#/components/schemas/CustomerLink" + }, + { + "$ref": "#/components/schemas/PlanLink" + }, + { + "$ref": "#/components/schemas/WebsiteLink" + }, + { + "$ref": "#/components/schemas/BillingContactLink" + }, + { + "$ref": "#/components/schemas/DeliveryContactLink" + }, + { + "$ref": "#/components/schemas/LeadSourceLink" + } + ] + } + } + } + }, + "SubscriptionCancel": { + "type": "object", + "required": [ + "policy", + "canceledBy", + "cancelCategory" + ], + "properties": { + "policy": { + "description": "Cancel policy", + "type": "string", + "enum": [ + "at-next-renewal", + "now-with-prorata-credit", + "now" + ] + }, + "canceledBy": { + "description": "Canceled by", + "type": "string", + "enum": [ + "merchant", + "customer" + ] + }, + "cancelCategory": { + "description": "Cancel category", + "type": "string", + "enum": [ + "did-not-use", + "did-not-want", + "missing-features", + "bugs-or-problems", + "do-not-remember", + "risk-warning", + "contract-expired", + "too-expensive", + "other" + ] + }, + "cancelDescription": { + "description": "Cancel reason description in free form", + "type": "string", + "maxLength": 255 + } + } + }, + "SubscriptionSwitch": { + "type": "object", + "required": [ + "planId", + "policy" + ], + "properties": { + "planId": { + "description": "The plan identifier string", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "policy": { + "description": "Switch policy", + "type": "string", + "enum": [ + "at-next-renewal", + "now-with-prorata-credit", + "now" + ] + }, + "websiteId": { + "description": "The website's ID", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "quantity": { + "description": "Quantity for each subscription. Default value to 1", + "type": "integer" + } + } + }, + "TaxCategory": { + "type": "object", + "properties": { + "id": { + "description": "The tax category identifier string", + "type": "string", + "readOnly": true, + "maxLength": 50, + "enum": [ + 99999, + 20010, + 40030, + 51020, + 51010, + 31000, + 30070 + ] + }, + "description": { + "description": "The tax category description", + "type": "string", + "maxLength": 512 + }, + "taxProvider": { + "description": "The tax category maps to a provider", + "type": "string", + "enum": [ + "tax-jar" + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "ThreeDSecure": { + "type": "object", + "required": [ + "enrolled", + "enrollmentEci", + "customerId", + "gatewayAccountId", + "paymentCardId", + "websiteId", + "currency", + "amount" + ], + "properties": { + "id": { + "description": "The 3D Secure entry identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "customerId": { + "description": "Related customer ID", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "gatewayAccountId": { + "description": "Related gateway account ID", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "paymentCardId": { + "description": "Related payment card ID", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "websiteId": { + "description": "Related Website ID", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "enrolled": { + "description": "Is the cardholder enrolled in 3DSecure", + "type": "string", + "enum": [ + "Y", + "N", + "U" + ] + }, + "enrollmentEci": { + "description": "The 3D Secure entry enrollment eci", + "type": "string" + }, + "eci": { + "description": "The 3D Secure entry electronic commerce indicator", + "type": "integer" + }, + "cavv": { + "description": "The 3D Secure entry cardholder authentication verification value", + "type": "string" + }, + "xid": { + "description": "The 3D Secure entry transaction Id", + "type": "string" + }, + "payerAuthResponseStatus": { + "description": "The 3D Secure entry Auth Response Status", + "type": "string", + "enum": [ + "Y", + "N", + "U", + "A" + ] + }, + "signatureVerification": { + "description": "If signature was verified", + "type": "string", + "enum": [ + "Y", + "N" + ] + }, + "amount": { + "description": "Transaction amount", + "type": "number", + "format": "double" + }, + "currency": { + "description": "The currency three letter code", + "type": "string" + }, + "createdTime": { + "description": "The 3D Secure entry created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "ApiTracking": { + "type": "object", + "description": "Tracking API Requests.", + "readOnly": true, + "properties": { + "id": { + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "status": { + "type": "integer", + "description": "HTTP response code" + }, + "url": { + "type": "string", + "description": "API request address" + }, + "method": { + "type": "string", + "description": "HTTP method", + "enum": [ + "HEAD", + "GET", + "POST", + "PUT", + "DELETE", + "PATCH" + ] + }, + "request": { + "type": "string", + "description": "Request JSON-string" + }, + "response": { + "type": "string", + "description": "Response JSON-string" + }, + "requestHeaders": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "example": { + "User-Agent": "Mozilla/5.0", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + } + }, + "responseHeaders": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "example": { + "Rate-Limit-Limit": 5000, + "Rate-Limit-Remaining": 4999, + "Rate-Limit-Reset": "Mon, 31 Jul 2017 04:16:00 +0000" + } + }, + "user": { + "type": "object", + "description": "The user who has made a request", + "readOnly": true, + "properties": { + "userId": { + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "apiKeyId": { + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "email": { + "description": "The user email", + "type": "string", + "format": "email" + }, + "firstName": { + "description": "The user first name", + "type": "string" + }, + "lastName": { + "description": "The user last name", + "type": "string" + }, + "ipAddress": { + "type": "string", + "description": "Client IP address", + "format": "ipv4" + }, + "userAgent": { + "description": "The software that is acting on behalf of a user", + "type": "string" + }, + "fingerprint": { + "description": "The user device fingerprint hash", + "type": "string" + }, + "isSupport": { + "description": "If user from support", + "type": "boolean" + } + } + }, + "duration": { + "type": "integer", + "description": "Request duration in milliseconds" + }, + "createdTime": { + "description": "The log created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "SubscriptionTracking": { + "type": "object", + "description": "Tracking subscription log", + "readOnly": true, + "properties": { + "id": { + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "subscriptionId": { + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "invoiceItemId": { + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "result": { + "type": "string", + "description": "Subscription's result", + "enum": [ + "created", + "postponed", + "stopped", + "error" + ] + }, + "message": { + "type": "string", + "description": "It contains the transaction number and renewal time" + }, + "createdTime": { + "description": "The log created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "WebhookTracking": { + "type": "object", + "description": "Webhook Tracking Requests.", + "readOnly": true, + "properties": { + "id": { + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "eventType": { + "$ref": "#/components/schemas/EventType" + }, + "url": { + "type": "string", + "description": "Url where webhook was sent" + }, + "method": { + "type": "string", + "description": "HTTP method which was used to send webhook", + "example": "POST" + }, + "headers": { + "type": "object", + "description": "HTTP headers which were used to send webhook", + "additionalProperties": { + "type": "string" + }, + "example": { + "My-Header": "Cool-Value" + } + }, + "responseCode": { + "type": "integer", + "description": "HTTP code response" + }, + "responseBody": { + "type": "string", + "description": "Response body received" + }, + "payload": { + "type": "object", + "description": "Event's data information" + }, + "sentTime": { + "description": "Sent time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "initiatedTime": { + "description": "Initiated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "createdTime": { + "description": "The log created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "WebsiteWebhookTracking": { + "type": "object", + "description": "Webhook Tracking Requests.", + "readOnly": true, + "properties": { + "id": { + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "websiteId": { + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "eventName": { + "type": "string", + "description": "Event name for which webhook was called" + }, + "status": { + "type": "string", + "description": "Event's status", + "enum": [ + "fail", + "success" + ] + }, + "response": { + "type": "integer", + "description": "HTTP code response" + }, + "pushData": { + "type": "string", + "description": "Event's data information JSON-string" + }, + "sentTime": { + "description": "Sent time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "nextSendTime": { + "description": "Next send time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "createdTime": { + "description": "The log created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "TransactionGatewayLog": { + "type": "object", + "readOnly": true, + "properties": { + "headers": { + "description": "The request headers", + "type": "array", + "items": { + "type": "string" + } + }, + "url": { + "description": "The request URL", + "type": "string" + }, + "request": { + "description": "The request body", + "type": "string" + }, + "response": { + "description": "The response body", + "type": "string" + }, + "duration": { + "description": "The request time, msec", + "type": "integer" + }, + "createdTime": { + "description": "The log entry created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/TransactionLink" + } + } + } + }, + "TransactionRefund": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "description": "Refund amount", + "type": "number", + "format": "double" + } + } + }, + "RedirectUrls": { + "type": "object", + "description": "The redirect URIs", + "required": [ + "error", + "success", + "decline", + "cancel" + ], + "properties": { + "error": { + "type": "string", + "format": "url" + }, + "success": { + "type": "string", + "format": "url" + }, + "decline": { + "type": "string", + "format": "url" + }, + "cancel": { + "type": "string", + "format": "url" + } + } + }, + "ThreeDSecureResult": { + "type": "object", + "readOnly": true, + "required": [ + "enrolled", + "authenticated", + "liability" + ], + "properties": { + "enrolled": { + "description": "Is the cardholder enrolled in 3D Secure", + "type": "string", + "enum": [ + "yes", + "no", + "invalid card/timeout", + "unavailable" + ] + }, + "authenticated": { + "description": "The 3D Secure entry Auth Response Status", + "type": "string", + "enum": [ + "yes", + "no", + "not applicable", + "attempted" + ] + }, + "liability": { + "type": "string", + "enum": [ + "protected", + "not protected", + "protected (attempt)" + ] + } + } + }, + "Transaction": { + "type": "object", + "properties": { + "id": { + "description": "The transaction identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "type": { + "description": "Transaction type", + "type": "string", + "readOnly": true, + "enum": [ + "authorize", + "capture", + "credit", + "refund", + "sale", + "void" + ] + }, + "status": { + "description": "Transaction status", + "type": "string", + "readOnly": true, + "enum": [ + "completed", + "connection-error", + "never-sent", + "pending", + "sending", + "suspended", + "timeout", + "waiting-capture", + "waiting-refund" + ] + }, + "result": { + "description": "Transaction result", + "type": "string", + "readOnly": true, + "enum": [ + "approved", + "canceled", + "declined", + "unknown" + ] + }, + "amount": { + "description": "The transactions's amount", + "type": "number", + "format": "double", + "readOnly": true + }, + "currency": { + "description": "The transactions's currency", + "type": "string", + "readOnly": true + }, + "parentTransactionId": { + "description": "The transactions's parent ID", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ], + "readOnly": true + }, + "childTransactions": { + "description": "The child transaction IDs", + "readOnly": true, + "type": "array", + "items": { + "$ref": "#/components/schemas/ResourceId" + } + }, + "invoiceIds": { + "description": "The invoice IDs related to transaction", + "readOnly": true, + "type": "array", + "items": { + "$ref": "#/components/schemas/ResourceId" + } + }, + "subscriptionIds": { + "description": "The subscription IDs related to transaction", + "readOnly": true, + "type": "array", + "items": { + "$ref": "#/components/schemas/ResourceId" + } + }, + "isRebill": { + "type": "boolean", + "readOnly": true + }, + "rebillNumber": { + "description": "The transactions's rebill number", + "type": "integer", + "readOnly": true + }, + "gatewayAccountId": { + "description": "The transactions's Gateway Account ID", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ], + "readOnly": true + }, + "gatewayTransactionId": { + "description": "The gateway's transaction ID", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ], + "readOnly": true + }, + "gateway": { + "type": "object", + "description": "The related gateway information", + "readOnly": true, + "properties": { + "response": { + "description": "The gateway's response", + "type": "object", + "properties": { + "code": { + "description": "The gateway's response code", + "type": "string" + }, + "message": { + "description": "The gateway's response message", + "type": "string" + }, + "type": { + "description": "The gateway's response type", + "type": "string" + }, + "originalCode": { + "description": "The raw, unmapped gateway's response code", + "type": "string" + }, + "originalMessage": { + "description": "The raw, unmapped gateway's response message", + "type": "string" + } + } + }, + "avsResponse": { + "description": "The AVS gateway's response", + "type": "object", + "properties": { + "code": { + "description": "The raw response code", + "type": "string" + }, + "message": { + "description": "The raw response message", + "type": "string" + } + } + }, + "cvvResponse": { + "description": "The CVV gateway's response", + "type": "object", + "properties": { + "code": { + "description": "The raw response code", + "type": "string" + }, + "message": { + "description": "The raw response message", + "type": "string" + } + } + } + } + }, + "gatewayName": { + "description": "Payment Gateway name, available only when transaction use gateway, else null", + "type": "string", + "readOnly": true + }, + "acquirerName": { + "description": "Acquirer name, available only when transaction use gateway, else null", + "type": "string", + "readOnly": true + }, + "websiteId": { + "description": "Website's ID", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ], + "readOnly": true + }, + "customerId": { + "description": "Customer's ID", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ], + "readOnly": true + }, + "method": { + "description": "Payment Method", + "allOf": [ + { + "$ref": "#/components/schemas/Method" + } + ] + }, + "velocity": { + "description": "The number of transactions by the same customer in the past 24 hours", + "type": "number", + "format": "integer" + }, + "bin": { + "description": "Payment Card BIN", + "type": "string", + "format": "bin", + "readOnly": true + }, + "paymentInstrument": { + "$ref": "#/components/schemas/PaymentInstrument" + }, + "billingAddress": { + "description": "Billing Address", + "allOf": [ + { + "$ref": "#/components/schemas/ContactObject" + } + ] + }, + "has3ds": { + "type": "boolean", + "readOnly": true + }, + "3ds": { + "allOf": [ + { + "$ref": "#/components/schemas/ThreeDSecureResult" + } + ] + }, + "hasDcc": { + "description": "True if transaction has Dynamic Currency Conversion applied", + "type": "boolean", + "readOnly": true + }, + "dcc": { + "description": "Dynamic Currency Conversion detailed information. Null if hasDcc is false", + "type": "object", + "readOnly": true, + "properties": { + "base": { + "type": "object", + "description": "Initial amount and currency to convert from", + "properties": { + "amount": { + "type": "number", + "format": "double" + }, + "currency": { + "type": "string" + } + } + }, + "quote": { + "type": "object", + "description": "Suggested amount and currency to convert to", + "properties": { + "amount": { + "type": "number", + "format": "double" + }, + "currency": { + "type": "string" + } + } + }, + "usdMarkup": { + "description": "The amount of markup translated to USD", + "type": "number", + "format": "double" + }, + "outcome": { + "type": "string", + "description": "Dynamic Currency Conversion outcome", + "enum": [ + "rejected", + "selected", + "unknown" + ] + } + } + }, + "riskScore": { + "description": "The transactions's risk score", + "type": "number", + "format": "integer", + "readOnly": true + }, + "riskMetadata": { + "description": "Risk metadata", + "allOf": [ + { + "$ref": "#/components/schemas/RiskMetadata" + } + ] + }, + "redirectUrls": { + "$ref": "#/components/schemas/RedirectUrls" + }, + "retryInstruction": { + "$ref": "#/components/schemas/PaymentRetry" + }, + "retryNumber": { + "type": "integer", + "readOnly": true, + "description": "The position in the sequence of retries" + }, + "retriedTransactionId": { + "readOnly": true, + "description": "The retried transaction ID", + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "description": { + "type": "string", + "description": "The payment description", + "maxLength": 255 + }, + "isDisputed": { + "description": "True if transaction is disputed", + "type": "boolean", + "readOnly": true + }, + "customFields": { + "$ref": "#/components/schemas/ResourceCustomFields" + }, + "scheduledTime": { + "type": "string", + "description": "The time the transaction is scheduled for collection", + "format": "date-time" + }, + "processedTime": { + "description": "Transaction processed time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "createdTime": { + "description": "Transaction created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "Transaction updated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 13, + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/SelfLink" + }, + { + "$ref": "#/components/schemas/GatewayAccountLink" + }, + { + "$ref": "#/components/schemas/CustomerLink" + }, + { + "$ref": "#/components/schemas/WebsiteLink" + }, + { + "$ref": "#/components/schemas/PaymentCardLink" + }, + { + "$ref": "#/components/schemas/ParentTransactionLink" + }, + { + "$ref": "#/components/schemas/RetriedTransactionLink" + }, + { + "$ref": "#/components/schemas/BillingContactLink" + }, + { + "$ref": "#/components/schemas/LeadSourceLink" + }, + { + "$ref": "#/components/schemas/ApprovalUrlLink" + }, + { + "$ref": "#/components/schemas/CancelUrlLink" + }, + { + "$ref": "#/components/schemas/RefundUrlLink" + }, + { + "$ref": "#/components/schemas/DisputeLink" + } + ] + } + } + } + }, + "UpdatePassword": { + "type": "object", + "properties": { + "currentPassword": { + "description": "Current user's password - used when requesting password change", + "type": "string", + "format": "password" + }, + "newPassword": { + "description": "New user's password - used when requesting password change", + "type": "string", + "format": "password" + } + } + }, + "User": { + "type": "object", + "required": [ + "email", + "firstName", + "lastName" + ], + "properties": { + "id": { + "description": "The user identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "email": { + "description": "The user email", + "type": "string", + "format": "email", + "maxLength": 100 + }, + "firstName": { + "description": "User's first name", + "type": "string" + }, + "lastName": { + "description": "User's last name", + "type": "string" + }, + "businessPhone": { + "description": "The user business phone number", + "type": "string" + }, + "mobilePhone": { + "description": "The user mobile phone number", + "type": "string" + }, + "password": { + "description": "User's password. If not provided, password reset email will be sent", + "type": "string", + "format": "password" + }, + "permissions": { + "description": "The user's permissions. See the format in example", + "allOf": [ + { + "$ref": "#/components/schemas/UserPermissions" + } + ] + }, + "createdTime": { + "description": "The user created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "The user updated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "loginTime": { + "description": "The user last login time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "reportingCurrency": { + "description": "The user's ISO Alpha-3 code used for reports", + "type": "string" + }, + "availableCurrencies": { + "type": "array", + "description": "An array of reporting currencies enabled for the merchant", + "readOnly": true, + "items": { + "type": "string" + } + }, + "totpRequired": { + "description": "The user setting of two-factor authentification", + "type": "boolean" + }, + "totpSecret": { + "description": "The user TOTP key for authentification app (if TOTP enabled)", + "type": "string" + }, + "totpUrl": { + "description": "The user link to QR-code for TOTP authentification app (if TOTP enabled)", + "type": "string", + "format": "url" + }, + "status": { + "description": "The user status", + "type": "string", + "enum": [ + "active", + "inactive", + "pending-confirmation" + ], + "readOnly": true + }, + "country": { + "description": "The user country setting - two letter code", + "type": "string" + }, + "preferences": { + "description": "User preferences like timezone, language and many more. This is an object with custom properties.", + "type": "object" + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/components/schemas/SelfLink" + } + } + } + }, + "UserPermissions": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "resourceName": { + "type": "string", + "enum": [ + "3dsecure", + "api-keys", + "api-tracking", + "authentication-options", + "authentication-tokens", + "bank-accounts", + "blacklists", + "checkout-pages", + "contacts", + "coupons", + "coupons-redemptions", + "credentials", + "custom-events", + "custom-fields", + "customers", + "disputes", + "events", + "gateway-accounts", + "invoices", + "layouts", + "lead-sources", + "lists", + "matched-rules", + "notes", + "oct-batch", + "organizations", + "password-tokens", + "payments", + "payment-cards", + "payment-cards-migrations", + "paypal-accounts", + "plans", + "reports", + "reset-sandbox", + "rulesets", + "sessions", + "subscriptions", + "subscription-tracking", + "tokens", + "transactions", + "users", + "webhook", + "webhook-tracking", + "websites" + ] + }, + "methods": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "enum": [ + "HEAD", + "GET", + "POST", + "PUT", + "DELETE", + "PATCH" + ] + } + }, + "resourceIds": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ResourceId" + } + } + } + }, + "example": [ + { + "resourceName": "plans", + "methods": [ + "GET", + "POST", + "PUT", + "HEAD", + "DELETE" + ] + }, + { + "resourceName": "invoices", + "methods": [ + "GET", + "HEAD" + ], + "resourceIds": [ + "4f6cf35x-2c4y-483z-a0a9-158621f77a21", + "1586f35x-4f6c-483z-a0a9-2c4y21f77a21" + ] + }, + { + "resourceName": null, + "methods": null, + "resourceIds": null + } + ] + }, + "WebhookAuthorization": { + "type": "object", + "discriminator": { + "propertyName": "type" + }, + "properties": { + "type": { + "type": "string", + "description": "The authorization type", + "enum": [ + "none", + "basic", + "digest", + "oauth1" + ], + "default": "none" + } + }, + "required": [ + "type" + ] + }, + "basic": { + "allOf": [ + { + "$ref": "#/components/schemas/WebhookAuthorization" + }, + { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string", + "format": "password" + } + }, + "required": [ + "username", + "password" + ] + } + ] + }, + "digest": { + "allOf": [ + { + "$ref": "#/components/schemas/WebhookAuthorization" + }, + { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string", + "format": "password" + } + }, + "required": [ + "username", + "password" + ] + } + ] + }, + "oauth1": { + "allOf": [ + { + "$ref": "#/components/schemas/WebhookAuthorization" + }, + { + "type": "object", + "properties": { + "consumerKey": { + "type": "string" + }, + "consumerSecret": { + "type": "string" + }, + "token": { + "type": "string" + }, + "tokenSecret": { + "type": "string" + } + }, + "required": [ + "consumerKey", + "consumerSecret", + "token", + "tokenSecret" + ] + } + ] + }, + "WebhookCredential": { + "type": "object", + "description": "Webhook credential", + "properties": { + "hash": { + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "host": { + "type": "string", + "description": "The host name" + }, + "auth": { + "$ref": "#/components/schemas/WebhookAuthorization" + } + }, + "required": [ + "host" + ] + }, + "Website": { + "type": "object", + "required": [ + "name", + "url", + "servicePhone", + "serviceEmail" + ], + "properties": { + "id": { + "description": "The website identifier string", + "readOnly": true, + "allOf": [ + { + "$ref": "#/components/schemas/ResourceId" + } + ] + }, + "name": { + "description": "The website's name", + "type": "string" + }, + "url": { + "description": "The website's domain address", + "type": "string" + }, + "servicePhone": { + "description": "The website's customer service phone number", + "type": "string" + }, + "serviceEmail": { + "description": "The website's customer service email address", + "type": "string", + "format": "email" + }, + "checkoutPageUri": { + "description": "Your own custom URI for this Checkout Page", + "type": "string" + }, + "createdTime": { + "description": "Website created time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "updatedTime": { + "description": "Website updated time", + "allOf": [ + { + "$ref": "#/components/schemas/ServerTimestamp" + } + ] + }, + "customFields": { + "$ref": "#/components/schemas/ResourceCustomFields" + }, + "_links": { + "type": "array", + "description": "The links related to resource", + "readOnly": true, + "minItems": 1, + "maxItems": 2, + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/SelfLink" + }, + { + "$ref": "#/components/schemas/NotesLink" + } + ] + } + } + } + }, + "WebsiteWebhook": { + "type": "object", + "required": [ + "webHookUrl", + "webHookUsername", + "webHookPassword" + ], + "properties": { + "webHookUrl": { + "description": "Webhook Url", + "type": "string" + }, + "webHookUsername": { + "description": "Webhook HTTP Authentication Username", + "type": "string" + }, + "webHookPassword": { + "description": "Webhook HTTP Authentication Password. An empty string will be returned in the response", + "type": "string" + } + } + } + }, + "responses": { + "AccessForbidden": { + "description": "Access forbidden, invalid API-KEY was used", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "NotFound": { + "description": "Resource was not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "Conflict": { + "description": "Conflict", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "InvalidDataError": { + "description": "Invalid data was sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidError" + } + } + } + } + }, + "parameters": { + "resourceId": { + "name": "id", + "in": "path", + "description": "The resource identifier string", + "required": true, + "schema": { + "type": "string" + } + }, + "collectionLimit": { + "name": "limit", + "in": "query", + "description": "The collection items limit", + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000 + } + }, + "collectionOffset": { + "name": "offset", + "in": "query", + "description": "The collection items offset", + "schema": { + "type": "integer", + "minimum": 0 + } + }, + "collectionFilter": { + "name": "filter", + "in": "query", + "description": "The collection items filter requires a special format.\nUse \",\" for multiple allowed values. Use \";\" for multiple fields.\nSee the filter guide for more options and examples about this format.\n", + "schema": { + "type": "string" + } + }, + "collectionQuery": { + "name": "q", + "in": "query", + "description": "The partial search of the text fields.", + "schema": { + "type": "string" + } + }, + "collectionCriteria": { + "name": "criteria", + "in": "query", + "description": "The json criteria for collection", + "schema": { + "type": "string" + } + }, + "collectionSort": { + "name": "sort", + "in": "query", + "description": "The collection items sort field and order (prefix with \"-\" for descending sort).", + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "collectionFields": { + "name": "fields", + "in": "query", + "description": "Limit the returned fields to the list specified, separated by comma. Note that id is always returned.", + "schema": { + "type": "string" + } + }, + "collectionExpand": { + "name": "expand", + "in": "query", + "description": "Expand response to get full related object intead of ID. See the expand guide for more info.", + "schema": { + "type": "string" + } + }, + "systemEventType": { + "name": "eventType", + "in": "path", + "description": "The event type", + "required": true, + "schema": { + "type": "string" + } + }, + "rulesVersion": { + "name": "version", + "in": "path", + "required": true, + "description": "The rule set version. Expand response to get full related object instead of ID. See the expand guide for more info.", + "schema": { + "type": "integer", + "minimum": 1 + } + }, + "hash": { + "name": "hash", + "in": "path", + "description": "The token identifier string", + "required": true, + "schema": { + "type": "string" + } + }, + "mediaType": { + "name": "Accept", + "in": "header", + "description": "The response media type", + "schema": { + "type": "string", + "enum": [ + "application/json" + ], + "default": "application/json" + } + } + }, + "requestBodies": { + "ApiKey": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiKey" + } + } + }, + "description": "ApiKey resource", + "required": true + }, + "Attachment": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Attachment" + } + } + }, + "description": "Attachment resource", + "required": true + }, + "BankAccount": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccount" + } + } + }, + "description": "BankAccount resource", + "required": true + }, + "Blacklist": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Blacklist" + } + } + }, + "description": "Blacklist resource", + "required": true + }, + "CheckoutPage": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CheckoutPage" + } + } + }, + "description": "Checkout Page resource", + "required": true + }, + "Contact": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Contact" + } + } + }, + "description": "Contact resource", + "required": true + }, + "Coupon": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Coupon" + } + } + }, + "description": "Coupon resource", + "required": true + }, + "Credential": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Credential" + } + } + }, + "description": "Credential resource", + "required": true + }, + "CustomEvent": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomEvent" + } + } + }, + "description": "Custom event resource", + "required": true + }, + "RuleSet": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuleSet" + } + } + }, + "description": "Set of rules resource", + "required": true + }, + "Customer": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Customer" + } + } + }, + "description": "Customer resource", + "required": true + }, + "LeadSource": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LeadSource" + } + } + }, + "description": "Lead Source resource", + "required": true + }, + "Dispute": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Dispute" + } + } + }, + "description": "Dispute resource", + "required": true + }, + "GatewayAccount": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GatewayAccount" + } + } + }, + "description": "Gateway Account resource", + "required": true + }, + "Invoice": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invoice" + } + } + }, + "description": "Invoice resource", + "required": true + }, + "Layout": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Layout" + } + } + }, + "description": "Layout resource", + "required": true + }, + "List": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/List" + } + } + }, + "description": "List resource", + "required": true + }, + "Note": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Note" + } + } + }, + "description": "Note resource", + "required": true + }, + "Organization": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Organization" + } + } + }, + "description": "Organization resource", + "required": true + }, + "Payment": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Payment" + } + } + }, + "description": "Payment resource", + "required": true + }, + "Plan": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Plan" + } + } + }, + "description": "Plan resource", + "required": true + }, + "GlobalWebhook": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GlobalWebhook" + } + } + }, + "description": "Webhook resource", + "required": true + }, + "Product": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Product" + } + } + }, + "description": "Product resource", + "required": true + }, + "UpdatePassword": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdatePassword" + } + } + }, + "description": "currentPassword and newPassword", + "required": true + }, + "Subscription": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Subscription" + } + } + }, + "description": "Subscription resource", + "required": true + }, + "PaymentToken": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentToken" + } + } + }, + "description": "PaymentToken resource", + "required": true + }, + "User": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + }, + "description": "User resource", + "required": true + }, + "Website": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Website" + } + } + }, + "description": "Website resource", + "required": true + } + }, + "securitySchemes": { + "ApiKey": { + "description": "When you sign up for an account, you are given your first API key.\nTo do so please follow this link: https://www.rebilly.com/site/signup/\nAlso you can generate additional API keys, and delete API keys (as you may\nneed to rotate your keys in the future).\n", + "name": "REB-APIKEY", + "type": "apiKey", + "in": "header" + }, + "JWT": { + "description": "You can create a JSON Web Token (JWT) via our Sessions resource.\n", + "type": "http", + "scheme": "basic" + }, + "RebAuth": { + "description": "Only for the Tokens resource.\nThe REB-AUTH value is created in this way\nGenerate a Nonce (random string), and a Timestamp (unix timestamp)\nConcatenate the values of ApiUser, Nonce, and Timestamp (in that order).\nUse that value as the input data when you calculate the HMAC-SHA1 with your secret key, called the signature.\nForming the four data points into a JSON object.\n\n```json\n{\n \"REB-APIUSER\": \"abcdefg\",\n \"REB-NONCE\": \"adfsjtreitou345fgkdafgj\",\n \"REB-TIMESTAMP\": \"1382591345\",\n \"REB-SIGNATURE\": \"a234fc95460401cfg09c9d09\"\n}\n```\n\nThen base64 encode that JSON object. This is the final value of the REB-AUTH HTTP header\n", + "name": "REB-AUTH", + "type": "apiKey", + "in": "header" + } + } + } +} diff --git a/demo/examples/multiple-apis/index.html b/demo/examples/multiple-apis/index.html deleted file mode 100644 index 1b12cbdc..00000000 --- a/demo/examples/multiple-apis/index.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - ReDoc Demo: Multiple apis - - - - - - - - - - - - - - diff --git a/demo/index-gh.html b/demo/index-gh.html deleted file mode 100644 index 04e0e3ec..00000000 --- a/demo/index-gh.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - ReDoc - - - - - - - - - - - - - - - diff --git a/demo/index.html b/demo/index.html index e1727b54..094f8b49 100644 --- a/demo/index.html +++ b/demo/index.html @@ -1,35 +1,24 @@ - - ReDoc - - - - - - - - + + + ReDoc + + + + + + + - - - - - - diff --git a/demo/main.css b/demo/main.css deleted file mode 100644 index 15e54589..00000000 --- a/demo/main.css +++ /dev/null @@ -1,133 +0,0 @@ -body { - margin: 0; - padding-top: 50px; - -webkit-tap-highlight-color: rgba(0,0,0,0); - -moz-tap-highlight-color: rgba(0,0,0,0); - -ms-tap-highlight-color: rgba(0,0,0,0); - -o-tap-highlight-color: rgba(0,0,0,0); - tap-highlight-color: rgba(0,0,0,0); - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - font-smoothing: antialiased; - -webkit-osx-font-smoothing: grayscale; - -moz-osx-font-smoothing: grayscale; - osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; - -moz-text-size-adjust: 100%; - text-size-adjust: 100%; - -webkit-text-shadow: 1px 1px 1px rgba(0,0,0,0.004); - -ms-text-shadow: 1px 1px 1px rgba(0,0,0,0.004); - text-shadow: 1px 1px 1px rgba(0,0,0,0.004); - text-rendering: optimizeSpeed!important; - font-smooth: always; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; - text-size-adjust: 100%; - font-family: Monserrat, sans-serif; -} - -nav input, nav button { - font-size: 16px; - height: 28px; - box-sizing: border-box; - vertical-align: middle; - line-height: 1; - outline: none; -} -nav header { - font-family: Monserrat, sans-serif; - float: left; - margin-left: 20px; - font-size: 25px; - color: #00329F; - font-weight: bold; -} - -nav input { - width: 50%; - box-sizing: border-box; - max-width: 500px; - padding: 0 10px; - - color: #555; - background-color: #fff; - border: 1px solid #ccc; - -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075); - box-shadow: inset 0 1px 1px rgba(0,0,0,.075); - -webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s; - -o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; - transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; -} - -nav input:focus { - border-color: #66afe9; - outline: 0; - -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6); - box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6); -} - -nav button { - background-color: #fff; - color: #333; - padding: 2px 10px; - - touch-action: manipulation; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - border: 1px solid #ccc; -} - -nav button:hover { - background-color: #e6e6e6; - border-color: #adadad; -} -nav button:active { - background-color: #d4d4d4; - border-color: #8c8c8c; -} - -nav { - width: 100%; - height: 50px; - line-height: 50px; - text-align: center; - background-color: white; - border-bottom: 1px solid #ccc; - position: fixed; - top: 0; - z-index: 1; - box-sizing: border-box; -} - -nav iframe { - margin: 10px 0; - position: absolute; - right: 0; - top: 0; -} - -header a { - color: inherit; - text-decoration: none; -} -@media (min-width: 1000px) { - nav header { - position: absolute; - } -} - -@media (max-width: 500px) { - nav input { - width: 70%; - } - nav header { - display: none; - } - - nav iframe { - display: none; - } -} diff --git a/demo/main.js b/demo/main.js deleted file mode 100644 index 9bc97062..00000000 --- a/demo/main.js +++ /dev/null @@ -1,74 +0,0 @@ -;(function() { - 'use strict'; - - var schemaUrlForm = document.getElementById('schema-url-form'); - var schemaUrlInput; - - var url = window.location.search.match(/url=([^&]+)/); - if (url && url.length > 1) { - url = decodeURIComponent(url[1]); - url = window.__REDOC_DEV__ ? url : '\\\\cors.apis.guru/' + url; - document.getElementsByTagName('redoc')[0].setAttribute('spec-url', url); - } - - function updateQueryStringParameter(uri, key, value) { - var re = new RegExp("([?|&])" + key + "=.*?(&|#|$)", "i"); - if (uri.match(re)) { - return uri.replace(re, '$1' + key + "=" + value + '$2'); - } else { - var hash = ''; - if( uri.indexOf('#') !== -1 ){ - hash = uri.replace(/.*#/, '#'); - uri = uri.replace(/#.*/, ''); - } - var separator = uri.indexOf('?') !== -1 ? "&" : "?"; - return uri + separator + key + "=" + value + hash; - } - } - - var specs = document.querySelector('#specs'); - specs.addEventListener('dom-change', function() { - schemaUrlForm = document.getElementById('schema-url-form'); - schemaUrlInput = document.getElementById('schema-url-input'); - - schemaUrlForm.addEventListener('submit', function(event) { - event.preventDefault(); - event.stopPropagation(); - location.search = updateQueryStringParameter(location.search, 'url', schemaUrlInput.value) - return false; - }) - - schemaUrlInput.addEventListener('mousedown', function(e) { - e.stopPropagation(); - }); - schemaUrlInput.value = url; - specs.specs = [ - 'https://api.apis.guru/v2/specs/instagram.com/1.0.0/swagger.yaml', - 'https://api.apis.guru/v2/specs/googleapis.com/calendar/v3/swagger.yaml', - 'https://api.apis.guru/v2/specs/data2crm.com/1/swagger.yaml', - 'https://api.apis.guru/v2/specs/graphhopper.com/1.0/swagger.yaml' - ]; - - var $specInput = document.getElementById('spec-input'); - - $specInput.addEventListener('value-changed', function(e) { - schemaUrlInput.value = e.detail.value; - location.search = updateQueryStringParameter(location.search, 'url', schemaUrlInput.value); - }); - - function selectItem() { - let value = this.innerText.trim(); - schemaUrlInput.value = value; - location.search = updateQueryStringParameter(location.search, 'url', schemaUrlInput.value); - } - - // for some reason events are not triggered so have to dirty fix this - $specInput.addEventListener('click', function(event) { - let $elems = document.querySelectorAll('.item.vaadin-combo-box-overlay'); - $elems.forEach(function($el) { - $el.addEventListener('mousedown', selectItem); - $el.addEventListener('mousedown', selectItem); - }); - }); - }); -})(); diff --git a/demo/petstore-logo.png b/demo/petstore-logo.png deleted file mode 100644 index 79ad81d626026fb7e97a0a49014d5b438d27d745..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9002 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 F{|9wNW`zI% diff --git a/demo/redoc-demo.png b/demo/redoc-demo.png deleted file mode 100644 index 550a1575dfa202d965a3c3b12d9b325d28133afd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 202376 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 Eqxvq?AJ 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�+IRkX3zcjr!g?W0@Bpw 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

g=*^Uwf1$5!?;}0cSb*YW4)rs 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 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. + + [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. + # Introduction + This API is documented in **OpenAPI format** and is based on - [Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team. - It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo) - tool and [ReDoc](https://github.com/Rebilly/ReDoc) documentation. In addition to standard - OpenAPI syntax we use a few [vendor extensions](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md). + + [Petstore sample](http://petstore.swagger.io/) provided by + [swagger.io](http://swagger.io) team. + + It was **extended** to illustrate features of + [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo) + + tool and [ReDoc](https://github.com/Rebilly/ReDoc) documentation. In + addition to standard + + OpenAPI syntax we use a few [vendor + extensions](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md). + # OpenAPI Specification + This API is documented in **OpenAPI format** and is based on - [Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team. - It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo) - tool and [ReDoc](https://github.com/Rebilly/ReDoc) documentation. In addition to standard - OpenAPI syntax we use a few [vendor extensions](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md). + + [Petstore sample](http://petstore.swagger.io/) provided by + [swagger.io](http://swagger.io) team. + + It was **extended** to illustrate features of + [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo) + + tool and [ReDoc](https://github.com/Rebilly/ReDoc) documentation. In + addition to standard + + OpenAPI syntax we use a few [vendor + extensions](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md). + # Cross-Origin Resource Sharing - This API features Cross-Origin Resource Sharing (CORS) implemented in compliance with [W3C spec](https://www.w3.org/TR/cors/). + + This API features Cross-Origin Resource Sharing (CORS) implemented in + compliance with [W3C spec](https://www.w3.org/TR/cors/). + And that allows cross-domain communication from the browser. - All responses have a wildcard same-origin which makes them completely public and accessible to everyone, including any code on any site. + + All responses have a wildcard same-origin which makes them completely public + and accessible to everyone, including any code on any site. + # Authentication + Petstore offers two forms of authentication: - API Key - OAuth2 OAuth2 - an open protocol to allow secure authorization in a simple and standard method from web, mobile and desktop applications. + + version: 1.0.0 title: Swagger Petstore termsOfService: 'http://swagger.io/terms/' contact: + name: API Support email: apiteam@swagger.io url: https://github.com/Rebilly/ReDoc x-logo: @@ -63,49 +99,22 @@ x-tagGroups: - name: User Management tags: - user -securityDefinitions: - petstore_auth: - description: | - Get access to data while protecting your account credentials. - OAuth2 is also a safer and more secure way to give you access. - type: oauth2 - authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog' - flow: implicit - scopes: - 'write:pets': modify pets in your account - 'read:pets': read your pets - api_key: - description: | - For this sample, you can use the api key `special-key` to test the authorization filters. - type: apiKey - name: api_key - in: header -x-servers: - - url: //petstore.swagger.io/v2 - description: Default server - - url: //petstore.swagger.io/sandbox - description: Sandbox server paths: /pet: + parameters: + - name: pathParam + in: cookie + description: Some cookie + required: true + schema: + type: integer + format: int64 post: tags: - pet summary: Add a new pet to the store description: Add new pet to the store inventory. 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 @@ -132,26 +141,24 @@ paths: Console.WriteLine(response.getRawResponse()); } - lang: PHP - source: "$form = new \\PetStore\\Entities\\Pet();\n$form->setPetType(\"Dog\");\n$form->setName(\"Rex\");\n// set other fields\ntry {\n $pet = $client->pets()->create($form);\n} catch (UnprocessableEntityException $e) {\n var_dump($e->getErrors());\n}\n" + source: | + $form = new \PetStore\Entities\Pet(); + $form->setPetType("Dog"); + $form->setName("Rex"); + // set other fields + try { + $pet = $client->pets()->create($form); + } catch (UnprocessableEntityException $e) { + var_dump($e->getErrors()); + } + requestBody: + $ref: '#/components/requestBodies/Pet' 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 @@ -165,7 +172,19 @@ paths: - 'read:pets' x-code-samples: - lang: PHP - source: "$form = new \\PetStore\\Entities\\Pet();\n$form->setPetId(1);\n$form->setPetType(\"Dog\");\n$form->setName(\"Rex\");\n// set other fields\ntry {\n $pet = $client->pets()->update($form);\n} catch (UnprocessableEntityException $e) {\n var_dump($e->getErrors());\n}\n" + source: | + $form = new \PetStore\Entities\Pet(); + $form->setPetId(1); + $form->setPetType("Dog"); + $form->setName("Rex"); + // set other fields + try { + $pet = $client->pets()->update($form); + } catch (UnprocessableEntityException $e) { + var_dump($e->getErrors()); + } + requestBody: + $ref: '#/components/requestBodies/Pet' '/pet/{petId}': get: tags: @@ -173,21 +192,25 @@ paths: 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 + deprecated: true + schema: + type: integer + format: int64 responses: '200': description: successful operation - schema: - $ref: '#/definitions/Pet' + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' '400': description: Invalid ID supplied '404': @@ -200,28 +223,14 @@ paths: 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 + schema: + type: integer + format: int64 responses: '405': description: Invalid input @@ -229,27 +238,38 @@ paths: - 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 - produces: - - application/xml - - application/json parameters: - name: api_key in: header required: false - type: string - x-example: Bearer + schema: + type: string + example: "Bearer " - name: petId in: path description: Pet id to delete required: true - type: integer - format: int64 + schema: + type: integer + format: int64 responses: '400': description: Invalid pet value @@ -264,36 +284,31 @@ paths: 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 + schema: + type: integer + format: int64 responses: '200': description: successful operation - schema: - $ref: '#/definitions/ApiResponse' + 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 /pet/findByStatus: get: tags: @@ -301,30 +316,35 @@ paths: summary: Finds Pets by status description: Multiple status values can be provided with comma seperated 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: csv - responses: - '200': - description: successful operation + style: form schema: type: array items: - $ref: '#/definitions/Pet' + type: string + enum: + - available + - pending + - sold + default: available + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' '400': description: Invalid status value security: @@ -336,28 +356,35 @@ paths: tags: - pet summary: Finds Pets by tags - description: 'Muliple tags can be provided with comma seperated strings. Use tag1, tag2, tag3 for testing.' + description: >- + Muliple tags can be provided with comma seperated strings. Use tag1, + tag2, tag3 for testing. operationId: findPetsByTags deprecated: true - produces: - - application/xml - - application/json parameters: - name: tags in: query description: Tags to filter by required: true - type: array - items: - type: string - collectionFormat: csv - responses: - '200': - description: successful operation + style: form schema: type: array items: - $ref: '#/definitions/Pet' + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' '400': description: Invalid tag value security: @@ -371,17 +398,16 @@ paths: 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 + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 security: - api_key: [] /store/order: @@ -391,47 +417,59 @@ paths: 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' + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' '400': description: Invalid Order + content: + application/json: + example: + status: 400 + message: "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 <= 5 or > 10. Other values will generated exceptions' + description: >- + For valid response try integer IDs with value <= 5 or > 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: 5 - minimum: 1 - format: int64 + schema: + type: integer + format: int64 + minimum: 1 + maximum: 5 responses: '200': description: successful operation - schema: - $ref: '#/definitions/Order' + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' '400': description: Invalid ID supplied '404': @@ -440,18 +478,18 @@ paths: tags: - store summary: Delete purchase order by ID - description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors + description: >- + For valid response try integer IDs with value < 1000. Anything above + 1000 or nonintegers 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: string - minimum: 1 + schema: + type: string + minimum: 1 responses: '400': description: Invalid ID supplied @@ -464,19 +502,16 @@ paths: 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 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: Created user object + required: true '/user/{username}': get: tags: @@ -484,20 +519,23 @@ paths: 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 + schema: + type: string responses: '200': description: successful operation - schema: - $ref: '#/definitions/User' + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' '400': description: Invalid username supplied '404': @@ -508,41 +546,38 @@ paths: 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 deleted required: true - type: string - - in: body - name: body - description: Updated user object - required: true schema: - $ref: '#/definitions/User' + 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 - produces: - - application/xml - - application/json parameters: - name: username in: path description: The name that needs to be deleted required: true - type: string + schema: + type: string responses: '400': description: Invalid username supplied @@ -555,21 +590,11 @@ paths: 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 + requestBody: + $ref: '#/components/requestBodies/UserArray' /user/createWithList: post: tags: @@ -577,21 +602,11 @@ paths: 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 + requestBody: + $ref: '#/components/requestBodies/UserArray' /user/login: get: tags: @@ -599,38 +614,50 @@ paths: 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 + schema: + type: string - name: password in: query description: The password for login in clear text required: true - type: string + schema: + type: string responses: '200': description: successful operation - schema: - type: string - examples: - application/json: OK - application/xml: OK - text/plain: OK headers: X-Rate-Limit: - type: integer - format: int32 description: calls per hour allowed by the user + schema: + type: integer + format: int32 X-Expires-After: - type: string - format: date-time description: date in UTC when toekn expires + schema: + type: string + format: date-time + content: + application/json: + schema: + type: string + examples: + response: + value: OK + application/xml: + schema: + type: string + examples: + response: + value: OK + text/plain: + examples: + response: + value: OK '400': description: Invalid username/password supplied /user/logout: @@ -640,226 +667,282 @@ paths: summary: Logs out current logged in user session description: '' operationId: logoutUser - produces: - - application/xml - - application/json - parameters: [] responses: default: description: successful operation -definitions: - ApiResponse: - type: object - properties: - code: - type: integer - format: int32 - type: - type: string - message: - type: string - Cat: - description: A representation of a cat - allOf: - - $ref: '#/definitions/Pet' - - type: object - properties: - huntingSkill: - type: string - description: The measured skill for hunting - default: lazy - enum: - - clueless - - lazy - - adventurous - - aggressive - required: - - huntingSkill - Category: - type: object - properties: - id: - description: Category ID - allOf: - - $ref: '#/definitions/Id' - name: - description: Category name - type: string - minLength: 1 - sub: - description: Test Sub Category - type: object - properties: - prop1: - type: string - description: Dumb Property - xml: - name: Category - Dog: - description: A representation of a dog - allOf: - - $ref: '#/definitions/Pet' - - type: object - properties: - packSize: - type: integer - format: int32 - description: The size of the pack the dog is from - default: 1 - minimum: 1 - required: - - packSize - HoneyBee: - description: A representation of a honey bee - allOf: - - $ref: '#/definitions/Pet' - - type: object - properties: - honeyPerDay: - type: number - description: Average amount of honey produced per day in ounces - example: 3.14 - required: - - honeyPerDay - Id: - type: integer - format: int64 - Order: - type: object - properties: - id: - description: Order ID - allOf: - - $ref: '#/definitions/Id' - petId: - description: Pet ID - allOf: - - $ref: '#/definitions/Id' - quantity: - type: integer - format: int32 - minimum: 1 - default: 1 - shipDate: - description: Estimated ship date - type: string - format: date-time - status: - type: string - description: Order Status - enum: - - placed - - approved - - delivered - complete: - description: Indicates whenever order was completed or not - type: boolean - default: false - xml: - name: Order - Pet: - type: object - required: - - name - - photoUrls - discriminator: petType - properties: - id: - description: Pet ID - allOf: - - $ref: '#/definitions/Id' - category: - description: Categories this pet belongs to - allOf: - - $ref: '#/definitions/Category' - name: - description: The name given to a pet - type: string - example: Guru - photoUrls: - description: The list of URL to a cute photos featuring pet - type: array - xml: - name: photoUrl - wrapped: true - items: +components: + schemas: + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: type: string - format: url - tags: - description: Tags attached to the pet - type: array - xml: - name: tag - wrapped: true - items: - $ref: '#/definitions/Tag' - status: - type: string - description: Pet status in the store - enum: - - available - - pending - - sold - petType: - description: Type of a pet - type: string - xml: - name: Pet - Tag: - type: object - properties: - id: - description: Tag ID - allOf: - - $ref: '#/definitions/Id' - name: - description: Tag name - type: string - minLength: 1 - xml: - name: Tag - User: - type: object - properties: - id: - description: User ID - $ref: '#/definitions/Id' - username: - description: User supplied username - type: string - minLength: 4 - example: John78 - firstName: - description: User first name - type: string - minLength: 1 - example: John - lastName: - description: User last name - type: string - minLength: 1 - example: Smith - email: - description: User email address - type: string - format: email - example: john.smith@example.com - password: - type: string - description: 'User password, MUST contain a mix of upper and lower case letters, as well as digits' - format: password - minLength: 8 - pattern: '(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])' - example: drowssaP123 - phone: - description: User phone number in international format - type: string - pattern: "^\\+(?:[0-9]-?){6,14}[0-9]$" - example: +1-202-555-0192 - x-nullable: true - userStatus: - description: User status - type: integer - format: int32 - xml: - name: User + message: + type: string + Cat: + description: A representation of a cat + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + properties: + huntingSkill: + type: string + description: The measured skill for hunting + default: lazy + enum: + - clueless + - lazy + - adventurous + - aggressive + required: + - huntingSkill + Category: + type: object + properties: + id: + description: Category ID + allOf: + - $ref: '#/components/schemas/Id' + name: + description: Category name + type: string + minLength: 1 + sub: + description: Test Sub Category + type: object + properties: + prop1: + type: string + description: Dumb Property + xml: + name: Category + Dog: + description: A representation of a dog + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + properties: + packSize: + type: integer + format: int32 + description: The size of the pack the dog is from + default: 1 + minimum: 1 + required: + - packSize + HoneyBee: + description: A representation of a honey bee + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + properties: + honeyPerDay: + type: number + description: Average amount of honey produced per day in ounces + example: 3.14 + required: + - honeyPerDay + Id: + type: integer + format: int64 + Order: + type: object + properties: + id: + description: Order ID + allOf: + - $ref: '#/components/schemas/Id' + petId: + description: Pet ID + allOf: + - $ref: '#/components/schemas/Id' + quantity: + type: integer + format: int32 + minimum: 1 + default: 1 + shipDate: + description: Estimated ship date + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + description: Indicates whenever order was completed or not + type: boolean + default: false + xml: + name: Order + Pet: + type: object + required: + - name + - photoUrls + discriminator: + propertyName: petType + mapping: + cat: '#/components/schemas/Cat' + dog: '#/components/schemas/Dog' + bee: '#/components/schemas/HoneyBee' + properties: + id: + description: Pet ID + allOf: + - $ref: '#/components/schemas/Id' + category: + description: Categories this pet belongs to + allOf: + - $ref: '#/components/schemas/Category' + name: + description: The name given to a pet + type: string + example: Guru + photoUrls: + description: The list of URL to a cute photos featuring pet + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + format: url + friend: + allOf: + - $ref: '#/components/schemas/Pet' + tags: + description: Tags attached to the pet + 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 + petType: + description: Type of a pet + type: string + xml: + name: Pet + Tag: + type: object + properties: + id: + description: Tag ID + allOf: + - $ref: '#/components/schemas/Id' + name: + description: Tag name + type: string + minLength: 1 + xml: + name: Tag + User: + type: object + properties: + id: + $ref: '#/components/schemas/Id' + pet: + oneOf: + - $ref: '#/components/schemas/Pet' + - $ref: '#/components/schemas/Tag' + username: + description: User supplied username + type: string + minLength: 4 + example: John78 + firstName: + description: User first name + type: string + minLength: 1 + example: John + lastName: + description: User last name + type: string + minLength: 1 + example: Smith + email: + description: User email address + type: string + format: email + example: john.smith@example.com + password: + type: string + description: >- + User password, MUST contain a mix of upper and lower case letters, + as well as digits + format: password + minLength: 8 + pattern: '(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])' + example: drowssaP123 + phone: + description: User phone number in international format + type: string + pattern: '^\+(?:[0-9]-?){6,14}[0-9]$' + example: +1-202-555-0192 + nullable: true + userStatus: + description: User status + type: integer + format: int32 + xml: + name: User + requestBodies: + Pet: + content: + application/json: + schema: + allOf: + - description: My Pet + title: Pettie + - $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: 'object' + properties: + name: + type: string + description: hooray + 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: + description: | + Get access to data while protecting your account credentials. + OAuth2 is also a safer and more secure way to give you access. + type: oauth2 + flows: + implicit: + authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog' + scopes: + 'write:pets': modify pets in your account + 'read:pets': read your pets + api_key: + description: > + For this sample, you can use the api key `special-key` to test the + authorization filters. + type: apiKey + name: api_key + in: header diff --git a/empty.js b/empty.js new file mode 100644 index 00000000..f053ebf7 --- /dev/null +++ b/empty.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/karma.conf.js b/karma.conf.js deleted file mode 100644 index f3d059bb..00000000 --- a/karma.conf.js +++ /dev/null @@ -1,53 +0,0 @@ -module.exports = function(config) { - const testWebpackConfig = require('./build/webpack.test.js'); - const travis = process.env.TRAVIS; - - config.set({ - frameworks: ['jasmine', 'sinon', 'should'], - preprocessors: { - './tests/spec-bundle.js': ['coverage', 'webpack', 'sourcemap'], - }, - - coverageReporter: { - type: 'in-memory', - }, - - remapCoverageReporter: { - 'text-summary': null, - 'text-lcov': './coverage/lcov.info', - html: './coverage/html', - }, - webpack: testWebpackConfig, - webpackMiddleware: { - stats: 'errors-only', - state: true, - }, - client: { - chai: { - truncateThreshold: 0, - }, - }, - files: [ - { pattern: './tests/spec-bundle.js', watched: false }, - { pattern: 'tests/schemas/**/*.json', included: false }, - { pattern: 'tests/schemas/**/*.yml', included: false }, - { pattern: 'lib/**/*.html', included: false }, - { pattern: 'lib/**/*.css', included: false }, - ], - - proxies: { - '/tests/schemas': '/base/tests/schemas', - '/lib/': '/base/lib/', - '/node_modules/': '/base/node_modules/', - }, - colors: true, - singleRun: true, - reporters: travis - ? ['mocha', 'coverage', 'remap-coverage', 'coveralls'] - : ['mocha', 'coverage', 'remap-coverage'], - - browsers: ['ChromeHeadless'], - - browserNoActivityTimeout: 60000, - }); -}; diff --git a/lib/app.module.ts b/lib/app.module.ts deleted file mode 100644 index 37ee2c5a..00000000 --- a/lib/app.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; - -import { RedocModule } from './redoc.module'; -import { Redoc } from './components/index'; - -@NgModule({ - imports: [ BrowserModule, RedocModule ], - bootstrap: [ Redoc ], - exports: [ Redoc ] -}) -export class AppModule { -} diff --git a/lib/bootstrap.dev.ts b/lib/bootstrap.dev.ts deleted file mode 100644 index 0ae0cee2..00000000 --- a/lib/bootstrap.dev.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { NgModuleRef } from '@angular/core'; -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { AppModule } from './app.module'; - -export function bootstrapRedoc(): Promise> { - return platformBrowserDynamic().bootstrapModule(AppModule); -} diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts deleted file mode 100644 index 080140d2..00000000 --- a/lib/bootstrap.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { NgModuleRef } from '@angular/core'; -import { platformBrowser } from '@angular/platform-browser'; -import { AppModule } from './app.module'; -import { AppModuleNgFactory } from '../compiled/lib/app.module.ngfactory'; - -export function bootstrapRedoc():Promise> { - return platformBrowser().bootstrapModuleFactory(AppModuleNgFactory); -} diff --git a/lib/components/ApiInfo/api-info.html b/lib/components/ApiInfo/api-info.html deleted file mode 100644 index 55f0a4e9..00000000 --- a/lib/components/ApiInfo/api-info.html +++ /dev/null @@ -1,24 +0,0 @@ -

diff --git a/lib/components/ApiInfo/api-info.scss b/lib/components/ApiInfo/api-info.scss deleted file mode 100644 index 2f06d834..00000000 --- a/lib/components/ApiInfo/api-info.scss +++ /dev/null @@ -1,33 +0,0 @@ -@import '../../shared/styles/variables'; - -:host > .api-info-wrapper { - box-sizing: border-box; - padding: $section-spacing; - width: 60%; - - - @media (max-width: $right-panel-squash-breakpoint) { - width: 100%; - } - - @media (max-width: $side-menu-mobile-breakpoint) { - padding-top: $section-spacing + 20px; - } -} - -.openapi-button { - border: 1px solid $primary-color; - color: $primary-color; - font-weight: normal; - margin-left: 0.5em; - padding: 3px 8px 4px; - display: inline-block; -} - -:host /deep/ [section] { - padding-top: 2 * $section-spacing; -} - -:host /deep/ h2[section] { - padding-top: $section-spacing; -} diff --git a/lib/components/ApiInfo/api-info.spec.ts b/lib/components/ApiInfo/api-info.spec.ts deleted file mode 100644 index 20db1bbc..00000000 --- a/lib/components/ApiInfo/api-info.spec.ts +++ /dev/null @@ -1,66 +0,0 @@ -'use strict'; - -import { getChildDebugElement } from '../../../tests/helpers'; -import { Component } from '@angular/core'; -import { OptionsService } from '../../services/index'; - -import { - inject, - async, - TestBed -} from '@angular/core/testing'; - -import { SpecManager } from '../../utils/spec-manager'; - -describe('Redoc components', () => { - describe('ApiInfo Component', () => { - let component; - let fixture; - let opts; - let specMgr; - beforeEach(() => { - TestBed.configureTestingModule({ declarations: [ TestAppComponent ] }); - }); - beforeEach(async(inject([SpecManager, OptionsService], (_specMgr, _opts) => { - opts = _opts; - opts.options = { - scrollYOffset: () => 0, - $scrollParent: window - }; - specMgr = _specMgr; - }))); - - beforeEach(done => { - specMgr.load('/tests/schemas/api-info-test.json').then(done, done.fail); - }); - - beforeEach(async(() => { - fixture = TestBed.createComponent(TestAppComponent); - component = getChildDebugElement(fixture.debugElement, 'api-info').componentInstance; - fixture.detectChanges(); - })); - - - it('should init component data', () => { - expect(component).not.toBeNull(); - expect(component.info).not.toBeNull(); - component.info.title.should.be.equal('Swagger Petstore'); - }); - - it('should render api name and version', () => { - let nativeElement = getChildDebugElement(fixture.debugElement, 'api-info').nativeElement; - let headerElement = nativeElement.querySelector('h1'); - expect(headerElement.innerText).toContain('Swagger Petstore (v1.0.0)'); - }); - }); -}); - - -/** Test component that contains an ApiInfo. */ -@Component({ - selector: 'test-app', - template: - `` -}) -class TestAppComponent { -} diff --git a/lib/components/ApiInfo/api-info.ts b/lib/components/ApiInfo/api-info.ts deleted file mode 100644 index 1f6240c8..00000000 --- a/lib/components/ApiInfo/api-info.ts +++ /dev/null @@ -1,44 +0,0 @@ -'use strict'; -import { Component, ChangeDetectionStrategy, OnInit, ElementRef } from '@angular/core'; -import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; -import { SpecManager, BaseComponent } from '../base'; -import { OptionsService, Marker } from '../../services/index'; - -@Component({ - selector: 'api-info', - styleUrls: ['./api-info.css'], - templateUrl: './api-info.html', - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ApiInfo extends BaseComponent implements OnInit { - info: any = {}; - specUrl: String | SafeResourceUrl; - downloadFilename = ''; - constructor(specMgr: SpecManager, - private optionsService: OptionsService, - elRef: ElementRef, - marker: Marker, - private sanitizer: DomSanitizer - ) { - super(specMgr); - marker.addElement(elRef.nativeElement); - } - - init() { - this.info = this.componentSchema.info; - this.specUrl = this.specMgr.specUrl; - if (!this.specUrl && window.Blob && window.URL) { - const blob = new Blob([JSON.stringify(this.specMgr.rawSpec, null, 2)], {type : 'application/json'}); - this.specUrl = this.sanitizer.bypassSecurityTrustResourceUrl(window.URL.createObjectURL(blob)); - this.downloadFilename = 'swagger.json'; - } - - if (!isNaN(parseInt(this.info.version.toString().substring(0, 1)))) { - this.info.version = 'v' + this.info.version; - } - } - - ngOnInit() { - this.preinit(); - } -} diff --git a/lib/components/ApiLogo/api-logo.html b/lib/components/ApiLogo/api-logo.html deleted file mode 100644 index e152677b..00000000 --- a/lib/components/ApiLogo/api-logo.html +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/lib/components/ApiLogo/api-logo.scss b/lib/components/ApiLogo/api-logo.scss deleted file mode 100644 index cf1cc7f6..00000000 --- a/lib/components/ApiLogo/api-logo.scss +++ /dev/null @@ -1,19 +0,0 @@ -@import '../../shared/styles/variables'; - -:host { - display: block; - text-align: center; - - @media (max-width: $side-menu-mobile-breakpoint) { - display: none; - } -} - -img { - max-height: 150px; - width: auto; - display: inline-block; - max-width: 100%; - //padding: 0 5px; - box-sizing: border-box; -} diff --git a/lib/components/ApiLogo/api-logo.spec.ts b/lib/components/ApiLogo/api-logo.spec.ts deleted file mode 100644 index b78d78b9..00000000 --- a/lib/components/ApiLogo/api-logo.spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -'use strict'; - -import { getChildDebugElement } from '../../../tests/helpers'; -import { Component } from '@angular/core'; - -import { - inject, - async, - TestBed -} from '@angular/core/testing'; - -import { SpecManager } from '../../utils/spec-manager'; - - -describe('Redoc components', () => { - describe('ApiLogo Component', () => { - let builder; - let component; - let fixture; - let specMgr; - - let schemaUrl = '/tests/schemas/api-info-test.json'; - beforeEach(() => { - TestBed.configureTestingModule({ declarations: [ TestAppComponent ] }); - }); - - beforeEach(async(inject([SpecManager], ( _specMgr) => { - specMgr = _specMgr; - }))); - - beforeEach(done => { - specMgr.load(schemaUrl).then(done, done.fail); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(TestAppComponent); - component = getChildDebugElement(fixture.debugElement, 'api-logo').componentInstance; - fixture.detectChanges(); - }); - - - it('should init component data', () => { - if (specMgr.a) return; - expect(component).not.toBeNull(); - expect(component.logo).not.toBeNull(); - }); - - it('should not display image when no x-logo', () => { - component.logo.should.be.empty(); - let nativeElement = getChildDebugElement(fixture.debugElement, 'api-logo').nativeElement; - let imgElement = nativeElement.querySelector('img'); - expect(imgElement).toBeNull(); - - // update schemaUrl to load other schema in the next test - schemaUrl = '/tests/schemas/extended-petstore.yml'; - }); - - it('should load values from spec and use transparent bgColor by default', () => { - component.logo.imgUrl.should.endWith('petstore-logo.png'); - component.logo.bgColor.should.be.equal('transparent'); - }); - }); -}); - - -/** Test component that contains an ApiInfo. */ -@Component({ - selector: 'test-app', - template: - `` -}) -class TestAppComponent { -} diff --git a/lib/components/ApiLogo/api-logo.ts b/lib/components/ApiLogo/api-logo.ts deleted file mode 100644 index f47fd61f..00000000 --- a/lib/components/ApiLogo/api-logo.ts +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; -import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core'; -import { BaseComponent, SpecManager } from '../base'; - -@Component({ - selector: 'api-logo', - styleUrls: ['./api-logo.css'], - templateUrl: './api-logo.html', - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ApiLogo extends BaseComponent implements OnInit { - logo:any = {}; - - constructor(specMgr:SpecManager) { - super(specMgr); - } - - init() { - const info = this.componentSchema.info; - const logoInfo = info['x-logo']; - if (!logoInfo) return; - this.logo.imgUrl = logoInfo.url; - this.logo.bgColor = logoInfo.backgroundColor || 'transparent'; - this.logo.url = info.contact && info.contact.url || null; - } - - ngOnInit() { - this.preinit(); - } -} diff --git a/lib/components/EndpointLink/endpoint-link.html b/lib/components/EndpointLink/endpoint-link.html deleted file mode 100644 index b8055dec..00000000 --- a/lib/components/EndpointLink/endpoint-link.html +++ /dev/null @@ -1,17 +0,0 @@ -
-
{{verb}}
- {{path}} - - - -
-
-
-
-
- {{server.url}}{{path}} -
-
-
diff --git a/lib/components/EndpointLink/endpoint-link.scss b/lib/components/EndpointLink/endpoint-link.scss deleted file mode 100644 index c9ff1a2a..00000000 --- a/lib/components/EndpointLink/endpoint-link.scss +++ /dev/null @@ -1,153 +0,0 @@ -@import '../../shared/styles/variables'; - -:host { - display: block; - position: relative; - cursor: pointer; -} - -.operation-endpoint { - padding: 10px 30px 10px 20px; - border-radius: $border-radius*2; - background-color: darken($black, 2%); - display: block; - font-weight: $light; - white-space: nowrap; - overflow-x: hidden; - text-overflow: ellipsis; - border: 1px solid transparent; -} - -.operation-endpoint > .operation-params-subheader { - padding-top: 1px; - padding-bottom: 0; - margin: 0; - font-size: 12/14em; - color: $black; - vertical-align: middle; - display: inline-block; - border-radius: $border-radius; -} - -.operation-api-url { - color: rgba($black, 0.8); - &-path { - font-family: $headers-font, $headers-font-family; - position: relative; - top: 1px; - color: #ffffff; - margin-left: 10px; - } -} - -.http-verb { - color: $black; - background: #ffffff; - padding: 3px 10px; - text-transform: uppercase; - display: inline-block; - margin: 0; -} - -.servers-overlay { - position: absolute; - width: 100%; - z-index: 100; - background: $side-bar-bg-color; - color: $black; - box-sizing: border-box; - box-shadow: 4px 4px 6px rgba(0, 0, 0, 0.33); - overflow: hidden; - border-bottom-left-radius: $border-radius*2; - border-bottom-right-radius: $border-radius*2; -} - -.server-item { - padding: 10px; - //margin-bottom: 10px; - - & > .url { - padding: 5px; - border: 1px solid $border-color; - background: $background-color; - word-break: break-all; - color: $primary-color; - } - - &:last-child { - margin-bottom: 0; - } -} - -.expand-icon { - height: 20px; - width: 20px; - display: inline-block; - float: right; - background: darken($black, 2%); - transform: rotateZ(0); - transition: all 0.2s ease; - top: 15px; - right: 5px; - position: absolute; -} - -.servers-overlay { - transform: translateY(-50%) scaleY(0); - transition: all 0.25s ease; -} -:host.expanded { - > .operation-endpoint { - border-color: $side-bar-bg-color; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } - - .expand-icon { - transform: rotateZ(180deg); - } - - .servers-overlay { - transform: translateY(0%) scaleY(1); - } -} - -.http-verb { - color: white; - - &.get { - background-color: $get-color; - } - - &.post { - background-color: $post-color; - } - - &.put { - background-color: $put-color; - } - - &.options { - background-color: $options-color; - } - - &.patch { - background-color: $patch-color; - } - - &.delete { - background-color: $delete-color; - } - - &.basic { - background-color: $basic-color; - } - - &.link { - background-color: $link-color; - } - - &.head { - background-color: $head-color; - } -} diff --git a/lib/components/EndpointLink/endpoint-link.spec.ts b/lib/components/EndpointLink/endpoint-link.spec.ts deleted file mode 100644 index ac0e50f5..00000000 --- a/lib/components/EndpointLink/endpoint-link.spec.ts +++ /dev/null @@ -1,82 +0,0 @@ -'use strict'; - -import { Component } from '@angular/core'; -import { - inject, - async, - TestBed -} from '@angular/core/testing'; - -import { getChildDebugElement } from '../../../tests/helpers'; - -import { EndpointLink } from './endpoint-link'; -import { SpecManager } from '../../utils/spec-manager'; -import { OptionsService } from '../../services/'; - -describe('Redoc components', () => { - beforeEach(() => { - TestBed.configureTestingModule({ declarations: [ TestAppComponent ] }); - }); - describe('EndpointLink Component', () => { - let builder; - let component: EndpointLink; - let specMgr: SpecManager; - let opts: OptionsService; - - beforeEach(async(inject([SpecManager, OptionsService], (_specMgr, _opts) => { - specMgr = _specMgr; - opts = _opts; - }))); - - beforeEach(() => { - specMgr.apiUrl = 'http://test.com/v1'; - specMgr._schema = { - info: {}, - host: 'petstore.swagger.io', - baseName: '/v2', - schemes: ['https', 'http'], - 'x-servers': [ - { - url: '//test.com/v2' - }, - { - url: 'ws://test.com/v3', - description: 'test' - } - ] - }; - specMgr.init(); - - component = new EndpointLink(specMgr, opts); - }); - - it('should replace // with appropriate protocol', () => { - component.ngOnInit(); - component.servers[0].url.should.be.equal('https://test.com/v2'); - }); - - - it('should preserve other protocols', () => { - component.ngOnInit(); - component.servers[1].url.should.be.equal('ws://test.com/v3'); - }); - - it('should fallback to host + basePath + schemas if no x-servers', () => { - specMgr._schema['x-servers'] = null; - specMgr.init(); - component.ngOnInit(); - component.servers.should.be.lengthOf(1); - component.servers[0].url.should.be.equal('https://petstore.swagger.io'); - }); - }); -}); - - -/** Test component that contains an Operation. */ -@Component({ - selector: 'test-app', - template: - `` -}) -class TestAppComponent { -} diff --git a/lib/components/EndpointLink/endpoint-link.ts b/lib/components/EndpointLink/endpoint-link.ts deleted file mode 100644 index 51dad69b..00000000 --- a/lib/components/EndpointLink/endpoint-link.ts +++ /dev/null @@ -1,63 +0,0 @@ -'use strict'; -import { Component, ChangeDetectionStrategy, Input, OnInit, HostListener, HostBinding} from '@angular/core'; -import { BaseComponent, SpecManager } from '../base'; -import { OptionsService } from '../../services/'; -import { stripTrailingSlash } from '../../utils/'; - -export interface ServerInfo { - description: string; - url: string; -} - -@Component({ - selector: 'endpoint-link', - styleUrls: ['./endpoint-link.css'], - templateUrl: './endpoint-link.html', - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class EndpointLink implements OnInit { - @Input() path:string; - @Input() verb:string; - - apiUrl: string; - servers: ServerInfo[]; - @HostBinding('class.expanded') expanded: boolean = false; - - // @HostListener('click') - handleClick() { - this.expanded = !this.expanded; - } - - constructor(public specMgr:SpecManager, public optionsService: OptionsService) { - this.expanded = false; - } - - init() { - let servers:ServerInfo[] = this.specMgr.schema['x-servers']; - if (servers) { - this.servers = servers.map(({url, description}) => ({ - description, - url: stripTrailingSlash(url.startsWith('//') ? `${this.specMgr.apiProtocol}:${url}` : url) - })); - } else { - this.servers = [ - { - description: 'Server URL', - url: this.getBaseUrl() - } - ]; - } - } - - getBaseUrl():string { - if (this.optionsService.options.hideHostname) { - return ''; - } else { - return this.specMgr.apiUrl; - } - } - - ngOnInit() { - this.init(); - } -} diff --git a/lib/components/ExternalDocs/external-docs.ts b/lib/components/ExternalDocs/external-docs.ts deleted file mode 100644 index 08871cf7..00000000 --- a/lib/components/ExternalDocs/external-docs.ts +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; -import { Component, Input, ChangeDetectionStrategy, OnInit } from '@angular/core'; -import { BaseComponent, SpecManager } from '../base'; - -@Component({ - selector: 'redoc-externalDocs', - template: ``, - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ExternalDocs implements OnInit { - @Input() docs; - - ngOnInit() { - if (this.docs && !this.docs.description) { - this.docs.description = 'External Docs'; - } - } -} diff --git a/lib/components/JsonSchema/_json-schema-common.scss b/lib/components/JsonSchema/_json-schema-common.scss deleted file mode 100644 index b9a48e89..00000000 --- a/lib/components/JsonSchema/_json-schema-common.scss +++ /dev/null @@ -1,260 +0,0 @@ -@import '../../shared/styles/variables'; -$lines-width: 1px; -$bullet-size: 1px; -$cell-spacing: 25px; -$cell-padding: 10px; -$bullet-margin: 10px; -$line-border: $lines-width solid $tree-lines-color; -$line-border-erase: ($lines-width + 1px) solid $background-color; - -$border-color: lighten($secondary-color, 50%); -$nullable-color: #3195a6; -$hint-border: 1px dotted rgba(38, 50, 56, 0.4); - -$param-name-height: 20px; - -$sub-schema-offset: ($bullet-size / 2) + $bullet-margin; - -.param-name-wrap { - display: inline-block; - padding-right: $cell-spacing; - font-family: $headers-font, $headers-font-family; -} - -.param-info { - border-bottom: 1px solid $border-color; - padding: $cell-padding 0; - width: 75%; - - box-sizing: border-box; - - > div { - line-height: 1; - } -} - -.param-range { - position: relative; - top: 1px; - margin-right: 6px; - margin-left: 6px; - border-radius: $border-radius; - background-color: rgba($primary-color, 0.1); - padding: 0 4px; - color: rgba($primary-color, 0.7); -} - -.param-description { - //font-size: 14px; -} - -.param-required { - vertical-align: middle; - line-height: $param-name-height; - color: $red; - font-size: 12px; - font-weight: bold; -} - -.param-nullable { - vertical-align: middle; - line-height: $param-name-height; - color: $nullable-color; - font-size: 12px; - font-weight: bold; -} - -.param-type, -.param-array-format { - vertical-align: middle; - line-height: $param-name-height; - color: rgba($black, 0.4); - font-size: 0.929em; -} -.param-type { - font-weight: normal; - word-break: break-all; - &.array::before, - &.tuple::before { - color: $black; - font-weight: $base-font-weight; - .param-collection-format-multi + & { - content: none; - } - } - - &.array::before { - content: $array-text; - } - &.tuple::before { - content: $tuple-text; - } - - &.with-hint { - display: inline-block; - margin-bottom: 0.4em; - border-bottom: $hint-border; - padding: 0; - cursor: help; - } - - &-trivial { - display: inline-block; - } - - &-file { - font-weight: bold; - text-transform: capitalize; - } -} - -// tree - -// Bullet - -.param-name { - border-left: $line-border; - box-sizing: border-box; - position: relative; - - padding: $cell-padding 0; - vertical-align: top; - line-height: $param-name-height; - - white-space: nowrap; - font-size: 0.929em; - font-weight: $regular; - - > span::before { - content: ''; - display: inline-block; - width: $bullet-size; - height: $bullet-size + 6; - background-color: $primary-color; - margin: 0 $bullet-margin; - vertical-align: middle; - } - - > span::after { - content: ''; - position: absolute; - border-top: $line-border; - width: $bullet-margin; - left: 0; - top: ($param-name-height / 2) + $cell-padding + 1; - } -} - -.param:first-of-type { - > .param-name::before { - content: ''; - display: block; - position: absolute; - left: -$lines-width; - top: 0; - border-left: $line-border-erase; - height: ($param-name-height / 2) + $cell-padding + 1; - } -} - -.param:last-of-type, -.param.last { - > .param-name { - position: relative; - - &::after { - content: ''; - display: block; - position: absolute; - left: -$lines-width - 1px; - border-left: $line-border-erase; - top: ($param-name-height / 2) + $cell-padding + $lines-width + 1; - background-color: $background-color; - bottom: 0; - } - } -} - -.param-wrap:last-of-type > .param-schema { - border-left-color: transparent; -} - -.param-schema { - .param-wrap:first-of-type { - .param-name::before { - display: none; - } - } -} - -.param-schema.last { - > td { - border-left: 0; - } -} - -.param-enum { - color: $text-color; - font-size: 0.95em; - - &::before { - content: 'Valid values: '; - } -} - -.param-enum { - color: $text-color; - font-size: 0.95em; - - &::before { - content: 'Valid values: '; - } - - .param-type.array ~ &::before { - content: 'Valid items values: '; - } -} - -.param-pattern { - color: $nullable-color; - white-space: nowrap; - - &::before, - &::after { - content: '/'; - margin: 0 3px; - font-size: 1.2em; - font-weight: bold; - } -} - -.param-default { - font-size: 0.95em; - - &::before { - content: 'Default: '; - } -} - -.param-example { - font-size: 0.95em; - - &::before { - content: 'Example: '; - } -} - -.param-enum-value, -.param-default-value, -.param-example-value { - font-family: Courier, monospace; - background-color: rgba($secondary-color, 0.02); - border: 1px solid rgba($secondary-color, 0.1); - margin: 2px 3px; - padding: 0.1em 0.2em 0.2em; - border-radius: $border-radius; - color: $text-color; - display: inline-block; - min-width: 20px; - text-align: center; -} diff --git a/lib/components/JsonSchema/json-schema-lazy.spec.ts b/lib/components/JsonSchema/json-schema-lazy.spec.ts deleted file mode 100644 index abcbd7d9..00000000 --- a/lib/components/JsonSchema/json-schema-lazy.spec.ts +++ /dev/null @@ -1,55 +0,0 @@ -'use strict'; - -import { getChildDebugElement } from '../../../tests/helpers'; -import { Component } from '@angular/core'; - -import { - inject, - TestBed -} from '@angular/core/testing'; - -import { JsonSchemaLazy } from './json-schema-lazy'; - -describe('Redoc components', () => { - beforeEach(() => { - TestBed.configureTestingModule({ declarations: [ TestAppComponent ] }); - }); - - describe('JsonSchemaLazy Component', () => { - let builder; - let component; - let fixture; - - beforeEach(() => { - fixture = TestBed.createComponent(TestAppComponent); - let debugEl = getChildDebugElement(fixture.debugElement, 'json-schema-lazy'); - component = debugEl.componentInstance; - spyOn(component, '_loadAfterSelf').and.stub(); - }); - - afterEach(() => { - component._loadAfterSelf.and.callThrough(); - }); - - it('should init component', () => { - expect(component).not.toBeNull(); - }); - - it('should run loadNextToLocation on load', () => { - component.pointer = '#/def'; - fixture.detectChanges(); - component.load(); - expect(component._loadAfterSelf).toHaveBeenCalled(); - }); - }); -}); - - -/** Test component that contains a lazy schema. */ -@Component({ - selector: 'test-app', - template: - `` -}) -class TestAppComponent { -} diff --git a/lib/components/JsonSchema/json-schema-lazy.ts b/lib/components/JsonSchema/json-schema-lazy.ts deleted file mode 100644 index c3a81bb1..00000000 --- a/lib/components/JsonSchema/json-schema-lazy.ts +++ /dev/null @@ -1,100 +0,0 @@ -'use strict'; - -import { Component, ElementRef, ViewContainerRef, OnDestroy, OnInit, Input, - AfterViewInit, ComponentFactoryResolver, Renderer } from '@angular/core'; - -import { JsonSchema } from './json-schema'; -import { OptionsService } from '../../services/options.service'; -import { SpecManager } from '../../utils/spec-manager'; - -var cache = {}; - -@Component({ - selector: 'json-schema-lazy', - entryComponents: [ JsonSchema ], - template: '', - styles: [':host { display:none }'] -}) -export class JsonSchemaLazy implements OnDestroy, OnInit, AfterViewInit { - @Input() pointer: string; - @Input() absolutePointer: string; - @Input() auto: boolean; - @Input() isRequestSchema: boolean; - @Input() final: boolean = false; - @Input() nestOdd: boolean; - @Input() childFor: string; - @Input() isArray: boolean; - disableLazy: boolean = false; - loaded: boolean = false; - constructor(private specMgr:SpecManager, private location:ViewContainerRef, private elementRef:ElementRef, - private resolver:ComponentFactoryResolver, private optionsService:OptionsService, private _renderer: Renderer) { - this.disableLazy = this.optionsService.options.disableLazySchemas; - } - - normalizePointer() { - let schema = this.specMgr.byPointer(this.pointer); - return schema && schema.$ref || this.pointer; - } - - private _loadAfterSelf() { - var componentFactory = this.resolver.resolveComponentFactory(JsonSchema); - let contextInjector = this.location.parentInjector; - let compRef = this.location.createComponent(componentFactory, null, contextInjector, null); - this.projectComponentInputs(compRef.instance); - this._renderer.setElementAttribute(compRef.location.nativeElement, 'class', this.location.element.nativeElement.className); - compRef.changeDetectorRef.detectChanges(); - this.loaded = true; - return compRef; - } - - load() { - if (this.disableLazy) return; - if (this.loaded) return; - if (this.pointer) { - this._loadAfterSelf(); - } - } - - // cache JsonSchema view - loadCached() { - this.pointer = this.normalizePointer(); - if (cache[this.pointer]) { - let compRef = cache[this.pointer]; - let $element = compRef.location.nativeElement; - - // skip caching view with descendant schemas - // as it needs attached controller - let hasDescendants = compRef.instance.descendants && compRef.instance.descendants.length; - if (!this.disableLazy && (hasDescendants || compRef.instance._hasSubSchemas)) { - this._loadAfterSelf(); - return; - } - insertAfter($element.cloneNode(true), this.elementRef.nativeElement); - this.loaded = true; - } else { - cache[this.pointer] = this._loadAfterSelf(); - } - } - - projectComponentInputs(instance:JsonSchema) { - Object.assign(instance, this); - } - - ngOnInit() { - if (!this.absolutePointer) this.absolutePointer = this.pointer; - } - - ngAfterViewInit() { - if (!this.auto && !this.disableLazy) return; - this.loadCached(); - } - - ngOnDestroy() { - // clear cache - cache = {}; - } -} - -function insertAfter(newNode, referenceNode) { - referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); -} diff --git a/lib/components/JsonSchema/json-schema.html b/lib/components/JsonSchema/json-schema.html deleted file mode 100644 index e32a8578..00000000 --- a/lib/components/JsonSchema/json-schema.html +++ /dev/null @@ -1,107 +0,0 @@ - - - - file -
-
    -
  • {{type}}
  • -
-
-
-
    -
  • {{type}}
  • -
-
-
-
- - - {{schema._displayType}} {{schema._displayFormat}} - {{schema._range}} - - Nullable -
- {{enumItem.val | json}} -
- {{schema.pattern}} -
-
- -
- -
- [{{idx}}]: - - -
-
-
-
- - - - - - - - - - - - - - - -
- - - {{prop.name}} - {{prop._enumItem?.val | json}} - - - - - - -
- {{prop._displayType}} {{prop._displayFormat}} - {{prop._range}} - - Required - Nullable -
- {{prop.default | json}} -
-
- {{enumItem.val | json}} -
- {{prop.pattern}} -
-
-
- - - -
-
- - - - -
-
- -
diff --git a/lib/components/JsonSchema/json-schema.scss b/lib/components/JsonSchema/json-schema.scss deleted file mode 100644 index f0d98758..00000000 --- a/lib/components/JsonSchema/json-schema.scss +++ /dev/null @@ -1,221 +0,0 @@ -@import 'json-schema-common'; - -// styles for array-schema for array -$array-marker-font-sz: 13px; -$array-marker-line-height: 1.5; - -:host { - display: block; -} - -.param-schema > td { - border-left: $line-border; - padding: 0 10px; -} - -.derived-schema { - display: none; -} - -.derived-schema.active { - display: block; -} - -:host.nested-schema { - background-color: white; - padding: 10px 20px; - position: relative; - border-radius: $border-radius; - - &:before, &:after { - content: ""; - width: 0; - height: 0; - position: absolute; - top: 0; - border-style: solid; - border-color: transparent; - border-width: 10px 15px 0; - margin-left: -7.5px; - border-top-color: $side-menu-active-bg-color; - } - &:before { - left: 10%; - } - - &:after { - right: 10%; - } - - .param:first-of-type > .param-name:before, .param:last-of-type > .param-name:after { - border-color: white; - } -} - -:host[nestodd="true"] { - background-color: $side-menu-active-bg-color; - border-radius: $border-radius; - - &:before, &:after { - border-top-color: white; - } - - > .params-wrap > .param:first-of-type > .param-name:before, - > .params-wrap > .param:last-of-type > .param-name:after { - border-color: $side-menu-active-bg-color; - } - - > .params-wrap > .param:last-of-type, - > .params-wrap > .param.last { - > .param-name:after { - border-color: $side-menu-active-bg-color; - } - } -} - -zippy { - overflow: visible; -} - -.zippy-content-wrap { - padding: 0; -} - -.param.complex.expanded > .param-info { - border-bottom: 0; -} - -.param.complex > .param-name .param-name-wrap { - font-weight: bold; - cursor: pointer; - color: $black; -} - -.param.complex > .param-name svg { - height: 1.2em; - width: 1.2em; - vertical-align: middle; - transition: all 0.3s ease; -} - -.param.complex.expanded > .param-name svg{ - transform: rotateZ(-180deg); -} - -.param.additional > .param-name { - color: rgba($black, 0.4); -} - -.params-wrap { - width: 100%; -} - -table { - border-spacing: 0; -} - -.params-wrap.params-array:before, .params-wrap.params-array:after { - display: block; - font-weight: $base-font-weight; - color: $black; - font-size: $array-marker-font-sz; - line-height: $array-marker-line-height; -} - -.params-wrap.params-array:after { - content: "]"; - font-family: monospace; -} - -.params-wrap.params-array:before { - content: "Array ["; - padding-top: 1em; - font-family: monospace; -} - -.params-wrap.params-array { - padding-left: $bullet-margin; -} - -.param-schema.param-array:before { - bottom: ($array-marker-font-sz * $array-marker-line-height) / 2; - width: $bullet-margin; - border-left-style: dashed; - border-bottom: $lines-width dashed $tree-lines-color; -} - -.params-wrap.params-array > .param-wrap:first-of-type > .param > .param-name:after { - content: ""; - display: block; - position: absolute; - left: -$lines-width; - top: 0; - border-left: $line-border-erase; - height: ($param-name-height/2) + $cell-padding; -} - -.params-wrap > .param > .param-schema.param-array { - border-left-color: transparent; -} - -.discriminator-info { - margin-top: 5px; -} - -.discriminator-wrap:not(.empty) > td { - //border-left: $line-border; - padding: 0; - position: relative; - - &:before { - content: ""; - display: block; - position: absolute; - left: 0; - top: 0; - border-left: $line-border; - height: ($param-name-height/2) + $cell-padding + 1; - z-index: 1; - } -} - -ul, li { - margin: 0; -} - -ul { - list-style: none; - padding-left: 1em; -} - -li:before { - content: "- "; - font-weight: bold; -} - -.array-tuple > .tuple-item { - margin-top: 1.5em; - display: flex; - - > span { - flex: 0; - padding: 10px 15px 10px 0; - font-family: monospace; - } - - > json-schema { - flex: 1; - &:before, &:after { - display: none; - } - } -} - -.param-name-enumvalue { - padding: 2px; - background-color: #e6ebf6; - - &:before { - content: " = "; - } -} diff --git a/lib/components/JsonSchema/json-schema.spec.ts b/lib/components/JsonSchema/json-schema.spec.ts deleted file mode 100644 index fc7d3f9c..00000000 --- a/lib/components/JsonSchema/json-schema.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -'use strict'; - -import { Component } from '@angular/core'; -import { - inject, - TestBed -} from '@angular/core/testing'; - -import { getChildDebugElement } from '../../../tests/helpers'; - -import { SpecManager } from '../../utils/spec-manager';; - -describe('Redoc components', () => { - beforeEach(() => { - TestBed.configureTestingModule({ declarations: [ TestAppComponent ] }); - }); - describe('JsonSchema Component', () => { - let builder; - let component; - let fixture; - let specMgr; - - beforeEach(inject([SpecManager], ( _spec) => { - - specMgr = _spec; - })); - - beforeEach(() => { - fixture = TestBed.createComponent(TestAppComponent); - let debugEl = getChildDebugElement(fixture.debugElement, 'json-schema'); - component = debugEl.componentInstance; - }); - - it('should init component', () => { - component.pointer = ''; - (specMgr)._schema = {type: 'object'}; - fixture.detectChanges(); - expect(component).not.toBeNull(); - }); - - it('should set isTrivial for non-object/array types', () => { - component.pointer = '#'; - (specMgr)._schema = {type: 'string'}; - fixture.detectChanges(); - component.schema.isTrivial.should.be.true(); - }); - - it('should use < anything > notation for prop without type', () => { - component.pointer = '#'; - (specMgr)._schema = {type: 'object', properties: { - test: {} - }}; - fixture.detectChanges(); - component.schema._properties[0]._displayType.should.be.equal('< anything >'); - }); - }); -}); - - -/** Test component that contains a json schema. */ -@Component({ - selector: 'test-app', - template: - `` -}) -class TestAppComponent { -} diff --git a/lib/components/JsonSchema/json-schema.ts b/lib/components/JsonSchema/json-schema.ts deleted file mode 100644 index e514b79b..00000000 --- a/lib/components/JsonSchema/json-schema.ts +++ /dev/null @@ -1,203 +0,0 @@ -'use strict'; - -import { Component, - Input, - Renderer, - ElementRef, - OnInit, - ChangeDetectionStrategy, - ChangeDetectorRef -} from '@angular/core'; - -import { BaseSearchableComponent, SpecManager } from '../base'; -import { SchemaNormalizer, SchemaHelper, AppStateService, OptionsService } from '../../services/'; -import { JsonPointer, DescendantInfo } from '../../utils/'; -import { Zippy } from '../../shared/components'; -import { JsonSchemaLazy } from './json-schema-lazy'; - -@Component({ - selector: 'json-schema', - templateUrl: './json-schema.html', - styleUrls: ['./json-schema.css'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class JsonSchema extends BaseSearchableComponent implements OnInit { - @Input() pointer: string; - @Input() absolutePointer: string; - @Input() final: boolean = false; - @Input() nestOdd: boolean; - @Input() childFor: string; - @Input() isRequestSchema: boolean; - - schema: any = {}; - activeDescendant:any = {}; - discriminator: string = null; - _hasSubSchemas: boolean = false; - properties: any; - _isArray: boolean; - normalizer: SchemaNormalizer; - descendants: DescendantInfo[]; - - constructor( - specMgr: SpecManager, - app: AppStateService, - private _renderer: Renderer, - private cdr: ChangeDetectorRef, - private _elementRef: ElementRef, - private optionsService: OptionsService) { - super(specMgr, app); - this.normalizer = new SchemaNormalizer(specMgr); - } - - get normPointer() { - return this.schema._pointer || this.pointer; - } - - selectDescendantByIdx(idx) { - this.selectDescendant(this.descendants[idx]); - } - - selectDescendant(activeDescendant: DescendantInfo) { - if (!activeDescendant || activeDescendant.active) return; - this.descendants.forEach(d => { - d.active = false; - }); - activeDescendant.active = true; - - this.schema = this.specMgr.getDescendant(activeDescendant, this.componentSchema); - this.pointer = this.schema._pointer || activeDescendant.$ref; - this.normalizer.reset(); - this.schema = this.normalizer.normalize(this.schema, this.normPointer, - {resolved: true}); - this.preprocessSchema(); - this.activeDescendant = activeDescendant; - } - - initDescendants() { - this.descendants = this.specMgr.findDerivedDefinitions(this.normPointer, this.schema); - if (!this.descendants.length) return; - let discriminator = this.discriminator = this.schema.discriminator || this.schema['x-extendedDiscriminator']; - let discrProperty = this.schema.properties && - this.schema.properties[discriminator]; - if (discrProperty && discrProperty.enum) { - let enumOrder = {}; - discrProperty.enum.forEach((enumItem, idx) => { - enumOrder[enumItem] = idx; - }); - - this.descendants = this.descendants - .filter(a => { - return enumOrder[a.name] != undefined; - }).sort((a, b) => { - return enumOrder[a.name] > enumOrder[b.name] ? 1 : -1; - }); - } - this.descendants.forEach((d, idx) => d.idx = idx); - this.selectDescendantByIdx(0); - } - - init() { - if (!this.pointer) return; - if (!this.absolutePointer) this.absolutePointer = this.pointer; - - this.schema = this.componentSchema; - if (!this.schema) { - throw new Error(`Can't load component schema at ${this.pointer}`); - } - - this.applyStyling(); - - this.schema = this.normalizer.normalize(this.schema, this.normPointer, {resolved: true}); - this.schema = SchemaHelper.unwrapArray(this.schema, this.normPointer); - this._isArray = this.schema._isArray; - this.absolutePointer += (this._isArray ? '/items' : ''); - this.initDescendants(); - this.preprocessSchema(); - } - - preprocessSchema() { - SchemaHelper.preprocess(this.schema, this.normPointer, this.pointer); - - if (!this.schema.isTrivial) { - SchemaHelper.preprocessProperties(this.schema, this.normPointer, { - childFor: this.childFor, - discriminator: this.discriminator - }); - } - - this.properties = this.schema._properties || []; - if (this.isRequestSchema) { - this.properties = this.properties.filter(prop => !prop.readOnly); - } - - if (this.optionsService.options.requiredPropsFirst) { - SchemaHelper.moveRequiredPropsFirst(this.properties, this.schema.required); - } - - this._hasSubSchemas = this.properties && this.properties.some( - propSchema => { - if (propSchema.type === 'array') { - propSchema = propSchema.items; - } - return (propSchema && propSchema.type === 'object' && propSchema._pointer); - }); - - if (this.properties.length === 1) { - this.properties[0].expanded = true; - } - } - - applyStyling() { - if (this.nestOdd) { - this._renderer.setElementAttribute(this._elementRef.nativeElement, 'nestodd', 'true'); - } - } - - trackByName(_: number, item: any): number { - return item.name + (item._pointer || ''); - } - - trackByIdx(idx: number, _: any): number { - return idx; - } - - findDescendantWithField(fieldName: string): DescendantInfo { - let res: DescendantInfo; - for (let descendantInfo of this.descendants) { - let schema = this.specMgr.getDescendant(descendantInfo, this.schema); - this.normalizer.reset(); - schema = this.normalizer.normalize(schema, this.normPointer, - {resolved: true}); - if (schema.properties && schema.properties[fieldName]) { - res = descendantInfo; - break; - }; - }; - return res; - } - - ensureSearchIsShown(ptr: string) { - if (ptr.startsWith(this.absolutePointer)) { - let props = this.properties; - if (!props) return; - let relative = JsonPointer.relative(this.absolutePointer, ptr); - let propName; - if (relative.length > 1 && relative[0] === 'properties') { - propName = relative[1]; - } - let prop = props.find(p => p.name === propName); - if (!prop) { - let d = this.findDescendantWithField(propName); - this.selectDescendant(d); - prop = this.properties.find(p => p.name === propName); - } - if (prop && !prop.isTrivial) prop.expanded = true; - this.cdr.markForCheck(); - this.cdr.detectChanges(); - } - } - - ngOnInit() { - this.preinit(); - } -} diff --git a/lib/components/LoadingBar/loading-bar.scss b/lib/components/LoadingBar/loading-bar.scss deleted file mode 100644 index 421e7dfb..00000000 --- a/lib/components/LoadingBar/loading-bar.scss +++ /dev/null @@ -1,21 +0,0 @@ -:host { - position: fixed; - top: 0; - left: 0; - right: 0; - display: block; - - height: 5px; - z-index: 100; -} - -span { - display: block; - position: absolute; - left: 0; - top: 0; - bottom: 0; - right: attr(progress percentage); - background-color: #5f7fc3; - transition: right 0.2s linear; -} diff --git a/lib/components/LoadingBar/loading-bar.spec.ts b/lib/components/LoadingBar/loading-bar.spec.ts deleted file mode 100644 index 2c883462..00000000 --- a/lib/components/LoadingBar/loading-bar.spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -'use strict'; - -import { - Component -} from '@angular/core'; - -import { - ComponentFixture, - inject, - fakeAsync, - tick, - TestBed, -} from '@angular/core/testing'; - -import { getChildDebugElement } from '../../../tests/helpers'; -import { LoadingBar } from './loading-bar'; - -describe('Redoc components', () => { - describe('Loading Bar', () => { - let component: LoadingBar; - - it('should init component', () => { - let fixture = TestBed.createComponent(LoadingBar); - component = fixture.componentInstance; - fixture.detectChanges(); - should.exist(component); - component.progress.should.be.equal(0); - component.display.should.be.equal('block'); - }); - - it('should hide itself in 500ms if progress is 100', fakeAsync(() => { - TestBed.configureTestingModule({ declarations: [ TestAppComponent ] }); - let fixture = TestBed.createComponent(TestAppComponent); - let parentComp = fixture.componentInstance; - component = getChildDebugElement(fixture.debugElement, 'loading-bar').componentInstance; - // need to pass update through parent component as ngOnChanges is run only for view changes - parentComp.progress = 50; - fixture.detectChanges(); - parentComp.progress = 100; - fixture.detectChanges(); - - component.display.should.be.equal('block'); - tick(500); - component.display.should.be.equal('none'); - })); - }); -}); - - -/** Test component that contains an ApiInfo. */ -@Component({ - selector: 'test-app', - template: - `` -}) -class TestAppComponent { - progress = 0; -} diff --git a/lib/components/LoadingBar/loading-bar.ts b/lib/components/LoadingBar/loading-bar.ts deleted file mode 100644 index 784b397e..00000000 --- a/lib/components/LoadingBar/loading-bar.ts +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; -import { Input, HostBinding, Component, OnChanges } from '@angular/core'; - -@Component({ - selector: 'loading-bar', - template: ` - - `, - styleUrls: ['loading-bar.css'] -}) -export class LoadingBar implements OnChanges { - @Input() progress:number = 0; - @HostBinding('style.display') display = 'block'; - - ngOnChanges(ch) { - if (ch.progress.currentValue === 100) { - setTimeout(() => { - this.display = 'none'; - }, 500); - } - } -} diff --git a/lib/components/Operation/operation.html b/lib/components/Operation/operation.html deleted file mode 100644 index 3ddea4f5..00000000 --- a/lib/components/Operation/operation.html +++ /dev/null @@ -1,32 +0,0 @@ -
-
-

- {{operation.summary}} -

- - -

-

- - - -
-
- - - -
- - -
-
-
- -
-
-
diff --git a/lib/components/Operation/operation.scss b/lib/components/Operation/operation.scss deleted file mode 100644 index 708b71c8..00000000 --- a/lib/components/Operation/operation.scss +++ /dev/null @@ -1,148 +0,0 @@ -@import '../../shared/styles/variables'; - - -:host { - padding-bottom: 100px; - display: block; - border-bottom: 1px solid rgba(127, 127, 127, 0.25); - margin-top: 1em; - transform: translateZ(0); - z-index: 2; -} - -// :host:last-of-type { -// border-bottom: 0; -// } - -.operation-header { - margin-bottom: calc(1em - 6px); - - &.deprecated { - &:after { - content: 'Deprecated'; - display: inline-block; - padding: 0 5px; - margin: 0; - background-color: $yellow; - color: white; - font-weight: bold; - font-size: 13px; - vertical-align: text-top; - } - } -} - -.operation-tags { - margin-top: 20px; - - > a { - font-size: 16px; - color: #999; - display: inline-block; - padding: 0 0.5em; - text-decoration: none; - - &:before { - content: '#'; - margin-right: -0.4em; - } - - &:first-of-type { - padding: 0; - } - } -} - -.operation-content, .operation-samples { - display: block; - box-sizing: border-box; - float: left; -} - -.operation-content { - width: 100% - $samples-panel-width; - padding: $section-spacing; -} - -.operation-samples { - color: $sample-panel-color; - width: 40%; - padding: $section-spacing; - background: $samples-panel-bg-color; -} - -.operation-samples pre { - color: $sample-panel-color; -} - -.operation-samples header, -.operation-samples > h5 { - color: $sample-panel-headers-color; - text-transform: uppercase; -} - -.operation-samples > h5 { - margin-bottom: 8px; -} - -.operation-samples schema-sample { - display: block; -} - -.operation:after { - content: ""; - display: table; - clear:both; -} - -.operation-description { - padding: 6px 0 10px 0; - margin: 0; -} - -[select-on-click] { - cursor: pointer; -} - -@media (max-width: $right-panel-squash-breakpoint) { - .operations:before { - display: none; - } - - .operation-samples, .operation-content { - width: 100%; - } - - .operation-samples { - margin-top: 2em; - } - - :host { - padding-bottom: 0; - } -} - -.operation-content /deep/ endpoint-link { - margin-bottom: 16px; - - .operation-endpoint[class] { - padding: 5px 30px 5px 5px; - border: 0; - border-bottom: 1px solid $border-color; - border-radius: 0; - background-color: transparent; - } - .operation-api-url-path { - color: $black; - } - - .expand-icon { - top: 8px; - background-color: $border-color; - } - - .servers-overlay { - border: 1px solid $border-color; - border-top: 0; - } -} diff --git a/lib/components/Operation/operation.spec.ts b/lib/components/Operation/operation.spec.ts deleted file mode 100644 index 44be2aae..00000000 --- a/lib/components/Operation/operation.spec.ts +++ /dev/null @@ -1,65 +0,0 @@ -'use strict'; - -import { Component } from '@angular/core'; -import { - inject, - async, - TestBed -} from '@angular/core/testing'; - -import { getChildDebugElement } from '../../../tests/helpers'; - -import { Operation } from './operation'; -import { SpecManager } from '../../utils/spec-manager';; -import { LazyTasksService } from '../../shared/components/LazyFor/lazy-for';; - -describe('Redoc components', () => { - beforeEach(() => { - TestBed.configureTestingModule({ declarations: [ TestAppComponent ] }); - }); - describe('Operation Component', () => { - let builder; - let component: Operation; - let specMgr; - - beforeEach(async(inject([SpecManager, LazyTasksService], (_specMgr, lazyTasks) => { - lazyTasks.sync = true; - specMgr = _specMgr; - }))); - - beforeEach(done => { - specMgr.load('/tests/schemas/extended-petstore.yml').then(done, done.fail); - }); - - beforeEach(() => { - let fixture = TestBed.createComponent(TestAppComponent); - component = getChildDebugElement(fixture.debugElement, 'operation').componentInstance; - fixture.detectChanges(); - }); - - - it('should init component', () => { - expect(component).not.toBeNull(); - }); - - it('should init basic component data', () => { - component.operation.verb.should.be.equal('put'); - component.operation.path.should.be.equal('/user/{username}'); - }); - - - it('should main tag', () => { - component.operation.info.tags.should.be.empty(); - }); - }); -}); - - -/** Test component that contains a Operation. */ -@Component({ - selector: 'test-app', - template: - `` -}) -class TestAppComponent { -} diff --git a/lib/components/Operation/operation.ts b/lib/components/Operation/operation.ts deleted file mode 100644 index dc86e7e6..00000000 --- a/lib/components/Operation/operation.ts +++ /dev/null @@ -1,89 +0,0 @@ -'use strict'; -import { Input, HostBinding, Component, OnInit, ChangeDetectionStrategy, ElementRef } from '@angular/core'; -import JsonPointer from '../../utils/JsonPointer'; -import { BaseComponent, SpecManager } from '../base'; -import { SchemaHelper } from '../../services/schema-helper.service'; -import { OptionsService, MenuService } from '../../services/'; -import { SwaggerBodyParameter } from '../../utils/swagger-typings'; - -export interface OperationInfo { - deprecated: boolean; - verb: string; - path: string; - info: { - tags: string[]; - description: string; - }; - bodyParam: any; - summary: string; - anchor: string; - externalDocs?: { - url: string; - description?: string; - } -} - -@Component({ - selector: 'operation', - templateUrl: './operation.html', - styleUrls: ['./operation.css'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class Operation extends BaseComponent implements OnInit { - @Input() pointer :string; - @Input() parentTagId :string; - - @HostBinding('attr.operation-id') operationId; - - operation: OperationInfo; - pathInMiddlePanel: boolean; - - constructor( - specMgr:SpecManager, - private optionsService: OptionsService, - private menu: MenuService) { - super(specMgr); - - this.pathInMiddlePanel = optionsService.options.pathInMiddlePanel; - } - - init() { - this.operationId = this.componentSchema.operationId; - - this.operation = { - deprecated: this.componentSchema.deprecated, - verb: JsonPointer.baseName(this.pointer), - path: JsonPointer.baseName(this.pointer, 2), - info: { - description: this.componentSchema.description, - tags: this.filterMainTags(this.componentSchema.tags) - }, - bodyParam: this.findBodyParam(), - summary: SchemaHelper.operationSummary(this.componentSchema), - anchor: this.buildAnchor(), - externalDocs: this.componentSchema.externalDocs - }; - } - - buildAnchor():string { - return this.menu.hashFor(this.pointer, - { type: 'operation', operationId: this.operationId, pointer: this.pointer }, - this.parentTagId ); - } - - filterMainTags(tags) { - var tagsMap = this.specMgr.getTagsMap(); - if (!tags) return []; - return tags.filter(tag => tagsMap[tag] && tagsMap[tag]['x-traitTag']); - } - - findBodyParam():SwaggerBodyParameter { - let params = this.specMgr.getOperationParams(this.pointer); - let bodyParam = params.find(param => param.in === 'body'); - return bodyParam; - } - - ngOnInit() { - this.preinit(); - } -} diff --git a/lib/components/OperationsList/operations-list.html b/lib/components/OperationsList/operations-list.html deleted file mode 100644 index aa57ed89..00000000 --- a/lib/components/OperationsList/operations-list.html +++ /dev/null @@ -1,12 +0,0 @@ -
-
-
-

{{tag.name}}

-

- -
- -
-
diff --git a/lib/components/OperationsList/operations-list.scss b/lib/components/OperationsList/operations-list.scss deleted file mode 100644 index a66e1310..00000000 --- a/lib/components/OperationsList/operations-list.scss +++ /dev/null @@ -1,37 +0,0 @@ -@import '../../shared/styles/variables'; - -:host { - display: block; - overflow: hidden; -} - -:host [hidden] { - display: none; -} - -.tag-info { - padding: $section-spacing; - box-sizing: border-box; - width: 60%; - - @media (max-width: $right-panel-squash-breakpoint) { - width: 100%; - } -} - -.tag-info:after, .tag-info:before { - content: ""; - display: table; -} - -.tag-info h1 { - color: $headers-color; - text-transform: capitalize; - font-weight: normal; - margin-top: 0; -} - -.operations { - display: block; - position: relative;; -} diff --git a/lib/components/OperationsList/operations-list.spec.ts b/lib/components/OperationsList/operations-list.spec.ts deleted file mode 100644 index 6f613fcf..00000000 --- a/lib/components/OperationsList/operations-list.spec.ts +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -import { Component } from '@angular/core'; -import { - inject, - async, - TestBed -} from '@angular/core/testing'; - -import { getChildDebugElement } from '../../../tests/helpers'; - - -import { OperationsList } from './operations-list'; -import { SpecManager } from '../../utils/spec-manager'; - -describe('Redoc components', () => { - beforeEach(() => { - TestBed.configureTestingModule({ declarations: [ TestAppComponent ] }); - }); - describe('OperationsList Component', () => { - let builder; - let component; - let fixture; - let specMgr; - - beforeEach(async(inject([SpecManager], (_specMgr) => { - specMgr = _specMgr; - }))); - - beforeEach(done => { - specMgr.load('/tests/schemas/operations-list-component.json').then(done, done.fail); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(TestAppComponent); - component = getChildDebugElement(fixture.debugElement, 'operations-list').componentInstance; - fixture.detectChanges(); - }); - - - it('should init component and component data', () => { - expect(component).not.toBeNull(); - }); - - it('should build correct tags list', () => { - expect(component.tags).not.toBeNull(); - component.tags.should.have.lengthOf(2); - component.tags[0].name.should.be.equal('traitTag'); - should.not.exist(component.tags[0].items); - component.tags[1].name.should.be.equal('tag1'); - component.tags[1].items.should.have.lengthOf(2); - }); - }); -}); - -@Component({ - selector: 'test-app', - template: - `` -}) -class TestAppComponent { -} diff --git a/lib/components/OperationsList/operations-list.ts b/lib/components/OperationsList/operations-list.ts deleted file mode 100644 index 620f4c3f..00000000 --- a/lib/components/OperationsList/operations-list.ts +++ /dev/null @@ -1,57 +0,0 @@ -'use strict'; -import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core'; -import { BaseComponent, SpecManager } from '../base'; -import { MenuService } from '../../services/index'; - -@Component({ - selector: 'operations-list', - templateUrl: './operations-list.html', - styleUrls: ['./operations-list.css'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class OperationsList extends BaseComponent implements OnInit { - @Input() pointer:string; - - tags:Array = []; - - constructor(specMgr:SpecManager, private menu: MenuService) { - super(specMgr); - } - - init() { - let flatMenuItems = this.menu.flatItems; - this.tags = []; - let emptyTag = { - name: '', - items: [] - }; - flatMenuItems.forEach(menuItem => { - // skip items that are not bound to swagger tags/operations - if (!menuItem.metadata) return; - - if (menuItem.metadata.type === 'tag') { - this.tags.push({ - ...menuItem, - anchor: this.buildAnchor(menuItem.id) - }); - } - if (menuItem.metadata.type === 'operation' && !menuItem.parent) { - emptyTag.items.push(menuItem); - } - }); - if (emptyTag.items.length) this.tags.push(emptyTag); - } - - buildAnchor(tagId):string { - return this.menu.hashFor(tagId, - { type: 'tag'}); - } - - trackByTagName(_, el) { - return el.name; - } - - ngOnInit() { - this.preinit(); - } -} diff --git a/lib/components/ParamsList/params-list.html b/lib/components/ParamsList/params-list.html deleted file mode 100644 index fc6f705a..00000000 --- a/lib/components/ParamsList/params-list.html +++ /dev/null @@ -1,53 +0,0 @@ -
Parameters
- -
- {{paramType.place}} Parameters - ? -
-
-
-
- {{param.name}} -
-
-
- - {{param | collectionFormat}} - - {{param._displayType}} {{param._displayFormat}} - {{param._range}} - Required -
- {{param.default | json}} -
-
- {{param.example | json}} -
-
- - {{enumItem.val | json}} - - - {{param._enumItem.val | json}} - -
- {{param.pattern}} -
-
-
-
-
-
- -
-
Request Body
- -
-
-
- - -
-
diff --git a/lib/components/ParamsList/params-list.scss b/lib/components/ParamsList/params-list.scss deleted file mode 100644 index 12b7f981..00000000 --- a/lib/components/ParamsList/params-list.scss +++ /dev/null @@ -1,99 +0,0 @@ -@import '../../shared/styles/variables'; - -$hint-color: #999999; - -:host { - display: block; -} - -.param-list-header { - border-bottom: 1px solid rgba($text-color, .3); -// padding: 0.2em 0; - margin: 3em 0 1em 0; - color: rgba($text-color, .5); - font-weight: normal; - text-transform: uppercase; -} - -@import '../JsonSchema/json-schema-common'; - -header.paramType { - margin: 25px 0 5px 0; - text-transform: capitalize; -} - -.param-array-format { - color: black; - font-weight: 300; -} - -// paramters can't be multilevel so table representation works for it without javascript -.params-wrap { - display: table; - width: 100%; -} - -.param-name { - display: table-cell; - vertical-align: top; -} - -.param-info { - display: table-cell; - width: 100%; -} - -.param { - display: table-row; -} - -.param:last-of-type > .param-name { - border-left: 0; - &:after { - content: ""; - display: block; - position: absolute; - left: 0; - border-left: $line-border; - height: ($param-name-height/2) + $cell-padding + $lines-width; - background-color: white; - top: 0; - } -} - -.param:first-of-type .param-name:after { - content: ""; - display: block; - position: absolute; - left: -$lines-width; - border-left: $line-border-erase; - height: ($param-name-height/2) + $cell-padding; - background-color: white; - top: 0; -} - -[data-hint] { - width: 1.2em; - text-align: center; - border-radius: 50%; - vertical-align: middle; - color: $hint-color; - line-height: 1.2; - text-transform: none; - cursor: help; - border: 1px solid $hint-color; - margin-left: 0.5em; -} - -@media (max-width: 520px) { - [data-hint] { - float: right; - } - - [data-hint]:after { - margin-left: 12px; - transform: translateX(-100%) translateY(-8px); - -moz-transform: translateX(-100%) translateY(-8px); - -webkit-transform: translateX(-100%) translateY(-8px); - } -} diff --git a/lib/components/ParamsList/params-list.ts b/lib/components/ParamsList/params-list.ts deleted file mode 100644 index 63629d08..00000000 --- a/lib/components/ParamsList/params-list.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; - -import { OptionsService } from '../../services/options.service'; -import { SchemaHelper } from '../../services/schema-helper.service'; -import { BaseComponent, SpecManager } from '../base'; - -function safePush(obj, prop, item) { - if (!obj[prop]) obj[prop] = []; - obj[prop].push(item); -} - -@Component({ - selector: 'params-list', - templateUrl: './params-list.html', - styleUrls: ['./params-list.css'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class ParamsList extends BaseComponent implements OnInit { - @Input() pointer: string; - - params: Array; - empty: boolean; - bodyParam: any; - - constructor(specMgr: SpecManager, private options: OptionsService) { - super(specMgr); - } - - init() { - this.params = []; - let paramsList = this.specMgr.getOperationParams(this.pointer); - - const igrnoredHeaders = - this.specMgr.schema['x-ignoredHeaderParameters'] || - this.options.options.ignoredHeaderParameters || - []; - - paramsList = paramsList - .map(paramSchema => { - let propPointer = paramSchema._pointer; - if (paramSchema.in === 'body') return paramSchema; - return SchemaHelper.preprocess(paramSchema, propPointer, this.pointer); - }) - .filter(param => { - return param.in !== 'header' || igrnoredHeaders.indexOf(param.name) < 0; - }); - - let paramsMap = this.orderParams(paramsList); - - if (paramsMap.body && paramsMap.body.length) { - let bodyParam = paramsMap.body[0]; - this.bodyParam = bodyParam; - paramsMap.body = undefined; - } - - this.empty = !(Object.keys(paramsMap).length || this.bodyParam); - - let paramsPlaces = ['path', 'query', 'formData', 'header', 'body']; - let placeHint = { - path: `Used together with Path Templating, where the parameter value is actually part - of the operation's URL. This does not include the host or base path of the API. - For example, in /items/{itemId}, the path parameter is itemId`, - query: `Parameters that are appended to the URL. - For example, in /items?id=###, the query parameter is id`, - formData: `Parameters that are submitted through a form. - application/x-www-form-urlencoded, multipart/form-data or both are usually - used as the content type of the request`, - header: 'Custom headers that are expected as part of the request' - }; - let params = []; - paramsPlaces.forEach(place => { - if (paramsMap[place] && paramsMap[place].length) { - params.push({place: place, placeHint: placeHint[place], params: paramsMap[place]}); - } - }); - this.params = params; - } - - orderParams(params):any { - let res = {}; - params.forEach((param) => safePush(res, param.in, param)); - return res; - } - - ngOnInit() { - this.preinit(); - } -} diff --git a/lib/components/Redoc/redoc-initial-styles.scss b/lib/components/Redoc/redoc-initial-styles.scss deleted file mode 100644 index abb29d88..00000000 --- a/lib/components/Redoc/redoc-initial-styles.scss +++ /dev/null @@ -1,56 +0,0 @@ -@import url('//fonts.googleapis.com/css?family=Roboto:300,400,700'); -@import url('//fonts.googleapis.com/css?family=Montserrat:400,700'); - -redoc.loading { - position: relative; - display: block; - min-height: 350px; -} - -@keyframes rotate { - 0% { - transform: rotate(0deg); } - 100% { - transform: rotate(360deg); - } -} - -redoc.loading:before { - font-family: Helvetica; - content: "Loading"; - font-size: 24px; - text-align: center; - padding-top: 40px; - color: #0033a0; - font-weight: normal; - display: block; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - background-color: white; - z-index: 9999; - opacity: 1; - transition: all 0.6s ease-out; -} - -redoc.loading:after { - z-index: 10000; - background-image: url('data:image/svg+xml;utf8,'); - animation: 2s rotate linear infinite; - width: 50px; - height: 50px; - position: absolute; - content: ""; - left: 50%; - margin-left: -25px; - background-size: cover; - top: 75px; - transition: all 0.6s ease-out; - opacity: 1; -} - -redoc.loading-remove:before, redoc.loading-remove:after { - opacity: 0; -} diff --git a/lib/components/Redoc/redoc.html b/lib/components/Redoc/redoc.html deleted file mode 100644 index 2bb172a7..00000000 --- a/lib/components/Redoc/redoc.html +++ /dev/null @@ -1,30 +0,0 @@ -
-

Oops... ReDoc failed to render this spec

-
{{error.message}}
-
- -
-
-
-
- -
- - - - -
-
diff --git a/lib/components/Redoc/redoc.scss b/lib/components/Redoc/redoc.scss deleted file mode 100644 index 59becb06..00000000 --- a/lib/components/Redoc/redoc.scss +++ /dev/null @@ -1,362 +0,0 @@ -@import '../../shared/styles/variables'; - -:host { - display: block; - box-sizing: border-box; - - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - -moz-tap-highlight-color: rgba(0, 0, 0, 0); - -ms-tap-highlight-color: rgba(0, 0, 0, 0); - -o-tap-highlight-color: rgba(0, 0, 0, 0); - tap-highlight-color: rgba(0, 0, 0, 0); - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - font-smoothing: antialiased; - -webkit-osx-font-smoothing: grayscale; - -moz-osx-font-smoothing: grayscale; - osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; - -moz-text-size-adjust: 100%; - text-size-adjust: 100%; - -webkit-text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.004); - -ms-text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.004); - text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.004); - text-rendering: optimizeSpeed !important; - font-smooth: always; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; - text-size-adjust: 100%; -} - -.redoc-wrap { - z-index: 0; - position: relative; - overflow: hidden; - font-family: $base-font, $base-font-family; - font-size: $em-size; - line-height: $base-line-height; - color: $text-color; -} - -.menu-content { - overflow: hidden; - display: flex; - flex-direction: column; -} - -side-menu { - overflow: hidden; -} - -[sticky-sidebar] { - width: $side-bar-width; - background-color: $side-bar-bg-color; - overflow-x: hidden; - transform: translateZ(0); - z-index: 75; - - @media (max-width: $side-menu-mobile-breakpoint) { - width: 100%; - bottom: auto !important; - } -} - -.api-content { - margin-left: $side-bar-width; - z-index: 50; - position: relative; - // height: 100vh; - // overflow: scroll; - top: 0; - @media (max-width: $side-menu-mobile-breakpoint) { - padding-top: 3em; - margin-left: 0; - } -} - -.background { - position: absolute; - top: 0; - bottom: 0; - right: 0; - left: $side-bar-width; - z-index: 1; - - &-actual { - background: $samples-panel-bg-color; - left: 100% - $samples-panel-width; - right: 0; - top: 0; - bottom: 0; - position: absolute; - } - - @media (max-width: $right-panel-squash-breakpoint) { - display: none; - } -} - -.redoc-error { - padding: 20px; - text-align: center; - color: $red; - > h2 { - color: $red; - font-size: 40px; - } -} - -.redoc-error-details { - max-width: 750px; - margin: 0 auto; - font-size: 18px; -} - -/* global menu items styles (search results + menu) */ -:host /deep/ { - .menu-item-header > span { - display: inline-block; - vertical-align: middle; - } - - .menu-item-header > .operation-type + .menu-item-title { - width: calc(100% - 32px); // 32 = 26px image width + 6px margin left - } - - .menu-item-header > .operation-type { - width: 26px; - display: inline-block; - height: 13px; - background-color: #333; - border-radius: 3px; - vertical-align: top; - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAACgCAMAAADZ0KclAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRF////////VXz1bAAAAAJ0Uk5T/wDltzBKAAAA80lEQVR42uSWSwLCIAxEX+5/aa2QZBJw5UIt9QMdRqSPEAAw/TyvqzZf150NzdXL49qreXwXjeqz9bqN1tgJl/KLyaVrrL7K7gx+1vlNMqy+helOO4rfBGYZiEkq1ubQ3DeKvc97Et+d+e01vIZlLZZqb1WNJFd8ZKYsmv4Hh3H2fDgjMUI5WSExjiEZs7rEZ5T+/jQn9lhgsw53j/e9MQtxqPsbZY54M5fNl/MY/f1s7NbRSkYlYjc0KPsWMrmhIU9933ywxDiSE+upYNH8TdusUotllNvcAUzfnE/NC4OSYyklQhpdl9E4Tw0Cm4/G9xBgAO7VCkjWLOMfAAAAAElFTkSuQmCC'); - background-repeat: no-repeat; - background-position: 6px 4px; - text-indent: -9000px; - margin-right: 6px; - margin-top: 2px; - - &.get { - background-position: 8px -12px; - background-color: $get-color; - } - - &.post { - background-position: 6px 4px; - background-color: $post-color; - } - - &.put { - background-position: 8px -28px; - background-color: $put-color; - } - - &.options { - background-position: 4px -148px; - background-color: $options-color; - } - - &.patch { - background-position: 4px -114px; - background-color: $patch-color; - } - - &.delete { - background-position: 4px -44px; - background-color: $delete-color; - } - - &.basic { - background-position: 5px -79px; - background-color: $basic-color; - } - - &.link { - background-position: 4px -131px; - background-color: $link-color; - } - - &.head { - background-position: 6px -102px; - background-color: $head-color; - } - } -} - -/* global redoc styles */ - -@for $index from 1 through 5 { - :host /deep/ h#{$index} { - margin-top: 0; - font-family: $headers-font, $headers-font-family; - color: $secondary-color; - font-weight: $headers-font-weight; - line-height: 1.5; - margin-bottom: 0.5em; - } -} - -:host /deep/ { - h1 { - font-size: $h1; - color: $headers-color; - } - h2 { - font-size: $h2; - } - h3 { - font-size: $h3; - } - h4 { - font-size: $h4; - } - h5 { - font-size: $h5; - line-height: 20px; - } - - p { - font-family: $base-font, $base-font-family; - font-weight: $base-font-weight; - margin: 0; - margin-bottom: 1em; - line-height: $base-line-height; - } - - a { - text-decoration: none; - color: $primary-color; - } - - p > code { - color: $red; - border: 1px solid rgba(38, 50, 56, 0.1); - } - - .hint--inversed { - &:before { - border-top-color: #fff; - } - - &:after { - background: #fff; - color: #383838; - } - } - - @import '../../shared/styles/share-link'; -} - -footer { - position: relative; - text-align: right; - padding: 10px $section-spacing; - font-size: 15px; - margin-top: -35px; - color: white; - a { - color: white; - } - strong { - font-size: 18px; - } -} - -/* markdown elements */ - -:host /deep/ .redoc-markdown-block { - pre { - font-family: Courier, monospace; - white-space: pre-wrap; - background-color: #263238; - color: white; - padding: 12px 14px 15px 14px; - overflow-x: auto; - line-height: normal; - border-radius: $border-radius; - border: 1px solid rgba(38, 50, 56, 0.1); - - code { - background-color: transparent; - color: white; - - &:before, - &:after { - content: none; - } - } - } - - code { - font-family: Courier, monospace; - background-color: rgba(38, 50, 56, 0.04); - padding: 0.1em 0.2em 0.2em; - font-size: 1em; - border-radius: $border-radius; - color: $red; - border: 1px solid rgba(38, 50, 56, 0.1); - } - - p:last-of-type { - margin-bottom: 0; - } - - blockquote { - margin: 0; - margin-bottom: 1em; - padding: 0 15px; - color: #777; - border-left: 4px solid #ddd; - } - - img { - max-width: 100%; - box-sizing: content-box; - } - - ul, - ol { - padding-left: 2em; - margin: 0; - margin-bottom: 1em; - font-family: $base-font, $base-font-family; - font-weight: $base-font-weight; - line-height: $base-line-height; - > li { - margin: 1em 0; - } - } - - table { - display: block; - width: 100%; - overflow: auto; - word-break: normal; - word-break: keep-all; - border-collapse: collapse; - border-spacing: 0; - margin-top: 0.5em; - margin-bottom: 0.5em; - } - - table tr { - background-color: #fff; - border-top: 1px solid #ccc; - - &:nth-child(2n) { - background-color: #f8f8f8; - } - } - - table th, - table td { - padding: 6px 13px; - border: 1px solid #ddd; - } - - table th { - text-align: left; - font-weight: bold; - } -} diff --git a/lib/components/Redoc/redoc.spec.ts b/lib/components/Redoc/redoc.spec.ts deleted file mode 100644 index 3f158fa3..00000000 --- a/lib/components/Redoc/redoc.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -'use strict'; - -import { getChildDebugElement } from '../../../tests/helpers'; -import { Component } from '@angular/core'; - -import { - inject, - async -} from '@angular/core/testing'; - -import { TestBed } from '@angular/core/testing'; - -import { Redoc } from './redoc'; -import { SpecManager } from '../../utils/spec-manager'; -import { OptionsService } from '../../services/index'; - -let optsMgr:OptionsService; - -describe('Redoc components', () => { - beforeEach(() => { - TestBed.configureTestingModule({ declarations: [ TestAppComponent ] }); - }); - describe('Redoc Component', () => { - let builder; - let specMgr; - - beforeEach(async(inject([SpecManager, OptionsService], - ( _specMgr, _optsMgr) => { - optsMgr = _optsMgr; - - specMgr = _specMgr; - }))); - - beforeEach(done => { - specMgr.load('/tests/schemas/extended-petstore.yml').then(done, done.fail); - }) - - it('should init component', () => { - let fixture = TestBed.createComponent(TestAppComponent); - let component = getChildDebugElement(fixture.debugElement, 'redoc').componentInstance; - expect(component).not.toBeNull(); - fixture.destroy(); - }); - - it('should init components tree without errors', () => { - let fixture = TestBed.createComponent(TestAppComponent); - (() => fixture.detectChanges()).should.not.throw(); - fixture.destroy(); - }); - }); -}); - -/** Test component that contains a Redoc. */ -@Component({ - selector: 'test-app', - template: - `` -}) -class TestAppComponent { -} diff --git a/lib/components/Redoc/redoc.ts b/lib/components/Redoc/redoc.ts deleted file mode 100644 index 26677218..00000000 --- a/lib/components/Redoc/redoc.ts +++ /dev/null @@ -1,162 +0,0 @@ -'use strict'; - -import { ElementRef, - ChangeDetectorRef, - Input, - Component, - OnInit, - OnDestroy, - HostBinding -} from '@angular/core'; - -import { BrowserDomAdapter as DOM } from '../../utils/browser-adapter'; -import { BaseComponent } from '../base'; - -import * as detectScollParent from 'scrollparent'; - -import { SpecManager } from '../../utils/spec-manager'; -import { - SearchService, - OptionsService, - Options, - Hash, - AppStateService, - SchemaHelper, - MenuService, - Marker -} from '../../services/'; -import { LazyTasksService } from '../../shared/components/LazyFor/lazy-for'; - -function getPreOptions() { - return Redoc._preOptions || {}; -} - -@Component({ - selector: 'redoc', - templateUrl: './redoc.html', - styleUrls: ['./redoc.css'], - providers: [ - SpecManager, - MenuService, - SearchService, - LazyTasksService, - Marker - ] - //changeDetection: ChangeDetectionStrategy.OnPush -}) -export class Redoc extends BaseComponent implements OnInit { - static _preOptions: any = {}; - - error: any; - specLoaded: boolean; - options: Options; - - loadingProgress: number; - - @Input() specUrl: string; - @HostBinding('class.loading') specLoading: boolean = false; - @HostBinding('class.loading-remove') specLoadingRemove: boolean = false; - - private element: HTMLElement; - private $parent: Element; - private $refElem: Element; - - constructor( - specMgr: SpecManager, - optionsMgr: OptionsService, - elementRef: ElementRef, - private changeDetector: ChangeDetectorRef, - private appState: AppStateService, - private lazyTasksService: LazyTasksService, - private hash: Hash - ) { - super(specMgr); - SchemaHelper.setSpecManager(specMgr); - // merge options passed before init - optionsMgr.options = getPreOptions(); - - this.element = elementRef.nativeElement; - this.$parent = this.element.parentElement; - this.$refElem = this.element.nextElementSibling; - - //parse options (top level component doesn't support inputs) - optionsMgr.parseOptions( this.element ); - let scrollParent = detectScollParent( this.element ); - if (scrollParent === (document.scrollingElement || document.documentElement)) scrollParent = window; - optionsMgr.options.$scrollParent = scrollParent; - this.options = optionsMgr.options; - this.lazyTasksService.allSync = !this.options.lazyRendering; - } - - hideLoadingAnimation() { - if (this.options.hideLoading) { - return - } - requestAnimationFrame(() => { - this.specLoadingRemove = true; - setTimeout(() => { - this.specLoadingRemove = false; - this.specLoading = false; - }, 400); - }); - } - - showLoadingAnimation() { - if (this.options.hideLoading) { - return - } - this.specLoading = true; - this.specLoadingRemove = false; - } - - load() { - // bunlde spec directly if passsed or load by URL - this.specMgr.load(this.options.spec || this.options.specUrl).catch(err => { - throw err; - }); - - this.appState.loading.subscribe(loading => { - if (loading) { - this.showLoadingAnimation(); - } else { - this.hideLoadingAnimation(); - } - }); - - this.specMgr.spec.subscribe((spec) => { - if (!spec) { - this.appState.startLoading(); - } else { - this.specLoaded = true; - this.changeDetector.markForCheck(); - this.changeDetector.detectChanges(); - setTimeout(() => { - this.hash.start(); - }); - } - }); - } - - ngOnInit() { - this.lazyTasksService.loadProgress.subscribe(progress => this.loadingProgress = progress) - this.appState.error.subscribe(_err => { - if (!_err) return; - - this.appState.stopLoading(); - - if (this.loadingProgress === 100) return; - this.error = _err; - this.changeDetector.markForCheck(); - }); - - if (this.specUrl) { - this.options.specUrl = this.specUrl; - } - this.load(); - } - - ngOnDestroy() { - let $clone = this.element.cloneNode(); - this.$parent.insertBefore($clone, this.$refElem); - } -} diff --git a/lib/components/RequestSamples/request-samples.html b/lib/components/RequestSamples/request-samples.html deleted file mode 100644 index 8f4956cd..00000000 --- a/lib/components/RequestSamples/request-samples.html +++ /dev/null @@ -1,15 +0,0 @@ -
Request samples
- - - - - - -
-
- Copy -
-

-    
-
-
diff --git a/lib/components/RequestSamples/request-samples.scss b/lib/components/RequestSamples/request-samples.scss deleted file mode 100644 index cc06902c..00000000 --- a/lib/components/RequestSamples/request-samples.scss +++ /dev/null @@ -1,81 +0,0 @@ -@import '../../shared/styles/variables'; - -:host { - overflow: hidden; - display: block; -} - -.action-buttons { - opacity: 0; - transition: opacity 0.3s ease; - transform: translateY(100%); - z-index: 3; - position: relative; - height: 2em; - line-height: 2em; - padding-right: 10px; - text-align: right; - margin-top: -1em; - - > span > a { - padding: 2px 10px; - color: #ffffff; - cursor: pointer; - - &:hover { - background-color: lighten($black, 15%); - } - } -} - -.code-sample:hover > .action-buttons { - opacity: 1; -} - -header { - font-family: $headers-font; - font-size: $h5; - text-transform: uppercase; - margin: 0; - color: $sample-panel-headers-color; - text-transform: uppercase; - font-weight: normal; - margin-top: 20px; -} - -:host /deep/ > tabs > ul li { - font-family: $headers-font; - font-size: .9em; - border-radius: $border-radius; - margin: 2px 0; - padding: 3px 10px 2px 10px; - line-height: 16px; - color: $sample-panel-headers-color; - - &:hover { - background-color: rgba(white, .1); - color: #ffffff; - } - - &.active { - background-color: #ffffff; - color: $text-color; - } -} - -:host /deep/ tabs ul { - padding-top: 10px; -} - -.code-sample pre { - overflow-x: auto; - word-break: break-all; - word-wrap: break-word; - white-space: pre-wrap; - margin-top: 0; - overflow-x: auto; - padding: 20px; - border-radius: 4px; - background-color: #222d32; - margin-bottom: 36px; -} diff --git a/lib/components/RequestSamples/request-samples.ts b/lib/components/RequestSamples/request-samples.ts deleted file mode 100644 index b0ebda94..00000000 --- a/lib/components/RequestSamples/request-samples.ts +++ /dev/null @@ -1,60 +0,0 @@ -'use strict'; - -import { Component, ViewChildren, QueryList, Input, - ChangeDetectionStrategy, OnInit, HostBinding, ElementRef, NgZone } from '@angular/core'; - -import { Subject } from 'rxjs/Subject'; - -import { BaseComponent, SpecManager } from '../base'; -import JsonPointer from '../../utils/JsonPointer'; -import { Tabs } from '../../shared/components/index'; -import { AppStateService, ScrollService } from '../../services/index'; - -@Component({ - selector: 'request-samples', - templateUrl: './request-samples.html', - styleUrls: ['./request-samples.css'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RequestSamples extends BaseComponent implements OnInit { - @Input() pointer:string; - @Input() schemaPointer:string; - @ViewChildren(Tabs) childQuery:QueryList; - @HostBinding('attr.hidden') hidden; - - childTabs: Tabs; - selectedLang: Subject; - samples: Array; - - constructor( - specMgr:SpecManager, - public appState:AppStateService, - private scrollService: ScrollService, - private el: ElementRef, - private zone: NgZone - ) { - super(specMgr); - - this.selectedLang = this.appState.samplesLanguage; - } - - changeLangNotify(lang) { - let relativeScrollPos = this.scrollService.relativeScrollPos(this.el.nativeElement); - this.selectedLang.next(lang); - // do scroll in the end of VM turn to have it seamless - let subscription = this.zone.onMicrotaskEmpty.subscribe(() => { - this.scrollService.scrollTo(this.el.nativeElement, relativeScrollPos); - subscription.unsubscribe(); - }); - } - - init() { - this.schemaPointer = this.schemaPointer ? JsonPointer.join(this.schemaPointer, 'schema') : null; - this.samples = this.componentSchema['x-code-samples'] || []; - if (!this.schemaPointer && !this.samples.length) this.hidden = true; - } - - ngOnInit() { - this.preinit(); - } -} diff --git a/lib/components/ResponsesList/responses-list.html b/lib/components/ResponsesList/responses-list.html deleted file mode 100644 index d046d43e..00000000 --- a/lib/components/ResponsesList/responses-list.html +++ /dev/null @@ -1,26 +0,0 @@ -

Responses

- -
-
- Headers -
-
-
{{header.name}}
-
{{header._displayType}} {{header._displayFormat}} - {{header._range}} -
-
Default: {{header.default}}
-
- {{enumItem.val | json}} -
-
-
-
-
- Response Schema -
- - -
diff --git a/lib/components/ResponsesList/responses-list.scss b/lib/components/ResponsesList/responses-list.scss deleted file mode 100644 index 4058ff2e..00000000 --- a/lib/components/ResponsesList/responses-list.scss +++ /dev/null @@ -1,61 +0,0 @@ -@import '../../shared/styles/variables'; - -:host { - display: block; -} - -.responses-list-header { - font-size: 18px; - padding: 0.2em 0; - margin: 3em 0 1.1em; - color: #253137; - font-weight: normal; -} - -:host .zippy-title { - font-family: $headers-font, $headers-font-family; -} - -.header-name { - font-weight: bold; - display: inline-block; -} - -.header-type { - display: inline-block; - font-weight: bold; - color: #999; -} - -header { - font-size: 14px; - font-weight: bold; - text-transform: uppercase; - margin-bottom: 15px; - - &:not(:first-child) { - margin-top: 15px; - margin-bottom: 0; - } -} - -.header { - margin-bottom: 10px; -} - -.header-range { - position: relative; - top: 1px; - margin-right: 6px; - margin-left: 6px; - border-radius: $border-radius; - background-color: rgba($primary-color, 0.1); - padding: 0 4px; - color: rgba($primary-color, 0.7); -} - -.header-type.array::before { - content: $array-text; - color: $black; - font-weight: $base-font-weight; -} diff --git a/lib/components/ResponsesList/responses-list.spec.ts b/lib/components/ResponsesList/responses-list.spec.ts deleted file mode 100644 index 4b340a6f..00000000 --- a/lib/components/ResponsesList/responses-list.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -'use strict'; - -import { Component } from '@angular/core'; -import { - inject, - async, - TestBed, - ComponentFixture -} from '@angular/core/testing'; - -import { getChildDebugElement } from '../../../tests/helpers'; - - -import { ResponsesList } from './responses-list'; -import { SpecManager } from '../../utils/spec-manager'; - -describe('Redoc components', () => { - - describe('ResponsesList Component', () => { - let builder; - let component: ResponsesList; - let fixture: ComponentFixture - let specMgr; - - beforeEach(async(inject([SpecManager], (_specMgr) => { - specMgr = _specMgr; - }))); - - beforeEach(done => { - specMgr.load('/tests/schemas/responses-list-component.json').then(done, done.fail); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(ResponsesList); - component = fixture.componentInstance; - }); - - it('should instantiate without errors', () => { - should.exist(component); - }); - - it('should init repsonses list', () => { - component.pointer = '#/paths/~1test1/get/responses'; - fixture.detectChanges(); - should.exist(component.responses); - component.responses.should.be.lengthOf(2); - }); - - it('should not overwrite codes for shared schemas', () => { - component.pointer = '#/paths/~1test1/get/responses'; - fixture.detectChanges(); - let resp1 = component.responses[0]; - let resp2 = component.responses[1]; - resp1.code.should.not.be.equal(resp2.code); - }); - - it('should set type of default as error if other 200-399 response is defined', () => { - component.pointer = '#/paths/~1test2/get/responses'; - fixture.detectChanges(); - let resp1 = component.responses[1]; - resp1.type.should.be.equal('error'); - }); - }); -}); diff --git a/lib/components/ResponsesList/responses-list.ts b/lib/components/ResponsesList/responses-list.ts deleted file mode 100644 index 1c4315b0..00000000 --- a/lib/components/ResponsesList/responses-list.ts +++ /dev/null @@ -1,108 +0,0 @@ -'use strict'; - -import { Component, - Input, - OnInit, - AfterViewInit, - ChangeDetectionStrategy, - ChangeDetectorRef - } from '@angular/core'; -import { BaseSearchableComponent, SpecManager } from '../base'; -import JsonPointer from '../../utils/JsonPointer'; -import { statusCodeType } from '../../utils/helpers'; -import { OptionsService, AppStateService } from '../../services/index'; -import { SchemaHelper } from '../../services/schema-helper.service'; - -function isNumeric(n) { - return (!isNaN(parseFloat(n)) && isFinite(n)); -} - -@Component({ - selector: 'responses-list', - templateUrl: './responses-list.html', - styleUrls: ['./responses-list.css'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ResponsesList extends BaseSearchableComponent implements OnInit { - @Input() pointer:string; - - responses: Array; - options: any; - - constructor(specMgr:SpecManager, - optionsMgr:OptionsService, - app: AppStateService, - private cdr: ChangeDetectorRef - ) { - super(specMgr, app); - this.options = optionsMgr.options; - } - - init() { - this.responses = []; - - let responses = this.componentSchema; - if (!responses) return; - - let hasSuccessResponses = false; - let respCodes = Object.keys(responses).filter(respCode => { - if ((parseInt(respCode) >= 100) && (parseInt(respCode) <=399)) { - hasSuccessResponses = true; - } - // only response-codes and "default" - return ( isNumeric(respCode) || (respCode === 'default')); - }); - - responses = respCodes.map(respCode => { - let resp = responses[respCode]; - resp.pointer = JsonPointer.join(this.pointer, respCode); - if (resp.$ref) { - let ref = resp.$ref; - resp = Object.assign({}, this.specMgr.byPointer(resp.$ref)); - resp.pointer = ref; - } - - resp.empty = !resp.schema; - resp.code = respCode; - resp.type = statusCodeType(resp.code, hasSuccessResponses); - - resp.expanded = false; - if (this.options.expandResponses) { - if (this.options.expandResponses === 'all' || this.options.expandResponses.has(respCode.toString())) { - resp.expanded = true; - } - } - - if (resp.headers && !(resp.headers instanceof Array)) { - resp.headers = Object.keys(resp.headers).map((k) => { - let respInfo = resp.headers[k]; - respInfo.name = k; - return SchemaHelper.preprocess(respInfo, this.pointer, this.pointer); - }); - resp.empty = false; - } - resp.extendable = resp.headers || resp.length; - return resp; - }); - this.responses = responses; - } - - trackByCode(_, el) { - return el.code; - } - - ensureSearchIsShown(ptr: string) { - if (ptr.startsWith(this.pointer)) { - let code = JsonPointer.relative(this.pointer, ptr)[0]; - if (code && this.componentSchema[code]) { - this.componentSchema[code].expanded = true; - this.cdr.markForCheck(); - this.cdr.detectChanges(); - } - } - } - - ngOnInit() { - this.preinit(); - } -} diff --git a/lib/components/ResponsesSamples/responses-samples.html b/lib/components/ResponsesSamples/responses-samples.html deleted file mode 100644 index 1b370272..00000000 --- a/lib/components/ResponsesSamples/responses-samples.html +++ /dev/null @@ -1,7 +0,0 @@ -
Response samples
- - - - - diff --git a/lib/components/ResponsesSamples/responses-samples.scss b/lib/components/ResponsesSamples/responses-samples.scss deleted file mode 100644 index e6191dfc..00000000 --- a/lib/components/ResponsesSamples/responses-samples.scss +++ /dev/null @@ -1,40 +0,0 @@ -@import '../../shared/styles/variables'; - -:host { - overflow: hidden; - display: block; -} - -header { - font-family: $headers-font; - font-size: 0.929em; - text-transform: uppercase; - margin: 0; - color: $sample-panel-headers-color; - text-transform: uppercase; - font-weight: normal; -} - -:host /deep/ > tabs > ul li { - font-family: $headers-font; - font-size: 0.929em; - border-radius: $border-radius; - margin: 2px 0; - padding: 2px 8px 3px 8px; - color: $sample-panel-headers-color; - line-height: 16px; - - &:hover { - color: #ffffff; - background-color: rgba(white, .1); - } - - &.active { - background-color: white; - color: $black; - } -} - -:host /deep/ tabs ul { - padding-top: 10px; -} diff --git a/lib/components/ResponsesSamples/responses-samples.ts b/lib/components/ResponsesSamples/responses-samples.ts deleted file mode 100644 index a7585d7d..00000000 --- a/lib/components/ResponsesSamples/responses-samples.ts +++ /dev/null @@ -1,66 +0,0 @@ -'use strict'; - -import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core'; -import { BaseComponent, SpecManager } from '../base'; -import JsonPointer from '../../utils/JsonPointer'; -import { statusCodeType, getJsonLikeSample, getXmlLikeSample } from '../../utils/helpers'; - - -function isNumeric(n) { - return (!isNaN(parseFloat(n)) && isFinite(n)); -} - -function hasExample(response) { - return response.schema || getXmlLikeSample(response.examples) || getJsonLikeSample(response.examples) -} - -@Component({ - selector: 'responses-samples', - templateUrl: './responses-samples.html', - styleUrls: ['./responses-samples.css'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ResponsesSamples extends BaseComponent implements OnInit { - @Input() pointer:string; - - data: any; - - constructor(specMgr:SpecManager) { - super(specMgr); - } - - init() { - this.data = {}; - this.data.responses = []; - - let responses = this.componentSchema; - if (!responses) return; - - let hasSuccessResponses = false; - responses = Object.keys(responses).filter(respCode => { - if ((parseInt(respCode) >= 100) && (parseInt(respCode) <=399)) { - hasSuccessResponses = true; - } - // only response-codes and "default" - return ( isNumeric(respCode) || (respCode === 'default')); - }).map(respCode => { - let resp = responses[respCode]; - resp.pointer = JsonPointer.join(this.pointer, respCode); - if (resp.$ref) { - let ref = resp.$ref; - resp = this.specMgr.byPointer(resp.$ref); - resp.pointer = ref; - } - - resp.code = respCode; - resp.type = statusCodeType(resp.code, hasSuccessResponses); - return resp; - }) - .filter(response => hasExample(response)); - this.data.responses = responses; - } - - ngOnInit() { - this.preinit(); - } -} diff --git a/lib/components/SchemaSample/schema-sample.html b/lib/components/SchemaSample/schema-sample.html deleted file mode 100644 index 27357ef8..00000000 --- a/lib/components/SchemaSample/schema-sample.html +++ /dev/null @@ -1,34 +0,0 @@ - -
- -
 Sample unavailable 
- -

-  
-
- - - - - - -
-
- Copy -
-

-    
-
- -
-
- Copy -
-
{{textSample}}
-
-
-
diff --git a/lib/components/SchemaSample/schema-sample.scss b/lib/components/SchemaSample/schema-sample.scss deleted file mode 100644 index ff3fbf9e..00000000 --- a/lib/components/SchemaSample/schema-sample.scss +++ /dev/null @@ -1,195 +0,0 @@ -@import '../../shared/styles/variables'; - -:host { - display: block; -} - - -/* tabs */ - -:host /deep/ tabs { - margin-top: 1em; - > ul { - margin: 0; - padding: 0; - - > li { - padding: 2px 10px; - display: inline-block; - background: #131a1d; - border-bottom: 1px solid trasparent; - color: $sample-panel-headers-color; - - &.active { - color: white; - border-bottom: 1px solid $sample-panel-headers-color; - } - } - } - - .action-buttons { - margin-top: -2em; - } -} - -pre { - background-color: transparent; - padding: 0; - margin: 0; - clear: both; - position: relative; -} - -.action-buttons { - opacity: 0; - transition: opacity 0.3s ease; - transform: translateY(100%); - z-index: 3; - position: relative; - height: 2em; - line-height: 2em; - padding-right: 10px; - text-align: right; - margin-top: -1em; - - > span > a { - padding: 2px 10px; - color: #ffffff; - cursor: pointer; - - &:hover { - background-color: lighten($black, 15%); - } - } -} - -.snippet:hover .action-buttons { - opacity: 1; -} - -:host /deep/ { - .property { - //font-weight: bold; - } - - .type-null { - color: gray; - } - - .type-boolean { - color: firebrick; - } - - .type-number { - color: #4A8BB3; - } - - .type-string { - color: #66B16E; - & + a { - color: #66B16E; - text-decoration: underline; - } - } - - .callback-function { - color: gray; - } - - .collapser:after { - content: "-"; - cursor: pointer; - } - - .collapsed > .collapser:after { - content: "+"; - cursor: pointer; - } - - .ellipsis:after { - content: " … "; - } - - .collapsible { - margin-left: 2em; - } - - .hoverable { - padding-top: 1px; - padding-bottom: 1px; - padding-left: 2px; - padding-right: 2px; - border-radius: 2px; - } - - .hovered { - background-color: rgba(235, 238, 249, 1); - } - - .collapser { - padding-right: 6px; - padding-left: 6px; - } - - .redoc-json, .response-sample { - overflow-x: auto; - padding: 20px; - border-radius: $border-radius*2; - background-color: darken($black, 2%); - margin-bottom: 36px; - } - - ul, .redoc-json ul { - list-style-type: none; - padding: 0px; - margin: 0px 0px 0px 26px; - } - - li { - position: relative; - display: block; - } - - .hoverable { - transition: background-color .2s ease-out 0s; - -webkit-transition: background-color .2s ease-out 0s; - display: inline-block; - } - - .hovered { - transition-delay: .2s; - -webkit-transition-delay: .2s; - } - - .selected { - outline-style: solid; - outline-width: 1px; - outline-style: dotted; - } - - .collapsed>.collapsible { - display: none; - } - - .ellipsis { - display: none; - } - - .collapsed > .ellipsis { - display: inherit; - } - - .collapser { - position: absolute; - top: 1px; - left: -1.5em; - cursor: default; - user-select: none; - -webkit-user-select: none; - } - - // hide top-level collapser - .redoc-json > .collapser { - display: none; - } -} diff --git a/lib/components/SchemaSample/schema-sample.ts b/lib/components/SchemaSample/schema-sample.ts deleted file mode 100644 index 4bbbe4d9..00000000 --- a/lib/components/SchemaSample/schema-sample.ts +++ /dev/null @@ -1,156 +0,0 @@ -'use strict'; - -import { Component, ElementRef, Input, ChangeDetectionStrategy, OnInit } from '@angular/core'; - -import * as OpenAPISampler from 'openapi-sampler'; -import JsonPointer from '../../utils/JsonPointer'; -import { BaseComponent, SpecManager } from '../base'; -import { SchemaNormalizer } from '../../services/schema-normalizer.service'; -import { getJsonLikeSample, getXmlLikeSample, getTextLikeSample } from '../../utils/helpers'; - -@Component({ - selector: 'schema-sample', - templateUrl: './schema-sample.html', - styleUrls: ['./schema-sample.css'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class SchemaSample extends BaseComponent implements OnInit { - @Input() pointer:string; - @Input() skipReadOnly:boolean; - - element: any; - sample: any; - xmlSample: string; - textSample: string; - enableButtons: boolean = false; - - private _normalizer:SchemaNormalizer; - - constructor(specMgr:SpecManager, elementRef:ElementRef) { - super(specMgr); - this.element = elementRef.nativeElement; - this._normalizer = new SchemaNormalizer(specMgr); - } - - init() { - this.bindEvents(); - - let base:any = this.componentSchema; - let sample, xmlSample; - - // got pointer not directly to the schema but e.g. to the response obj - if (this.componentSchema.schema) { - base = this.componentSchema; - this.componentSchema = this.componentSchema.schema; - this.pointer += '/schema'; - } - - // Support x-examples, allowing requests to specify an example. - let examplePointer:string = JsonPointer.join(JsonPointer.dirName(this.pointer), 'x-examples'); - let requestExamples:any = this.specMgr.byPointer(examplePointer); - if (requestExamples) { - base.examples = requestExamples; - } - - this.xmlSample = base.examples && getXmlLikeSample(base.examples); - this.textSample = base.examples && getTextLikeSample(base.examples); - - let jsonLikeSample = base.examples && getJsonLikeSample(base.examples); - if (jsonLikeSample) { - sample = jsonLikeSample; - } else { - let selectedDescendant; - - this.componentSchema = this._normalizer.normalize(this.componentSchema, this.pointer); - - let discriminator = this.componentSchema.discriminator || this.componentSchema['x-discriminatorBasePointer']; - if (discriminator) { - let descendants = this.specMgr.findDerivedDefinitions(this.componentSchema._pointer || this.pointer, this.componentSchema); - if (descendants.length) { - // TODO: sync up with dropdown - selectedDescendant = descendants[0]; - let descSchema = this.specMgr.getDescendant(selectedDescendant, this.componentSchema); - this.componentSchema = this._normalizer.normalize(Object.assign({}, descSchema), selectedDescendant.$ref, - {omitParent: false}); - } - } - if (this.fromCache()) { - this.initButtons(); - return; - } - try { - sample = OpenAPISampler.sample(this.componentSchema, { - skipReadOnly: this.skipReadOnly - }); - } catch(e) { - // no sample available - } - if (selectedDescendant) { - sample[discriminator] = selectedDescendant.name; - } - } - this.cache(sample); - this.sample = sample; - this.initButtons(); - } - - initButtons() { - if (typeof this.sample === 'object') { - this.enableButtons = true; - } - } - - cache(sample) { - if (this.skipReadOnly) { - this.componentSchema['x-redoc-ro-sample'] = sample; - } else { - this.componentSchema['x-redoc-rw-sample'] = sample; - } - } - - fromCache() { - if (this.skipReadOnly && this.componentSchema['x-redoc-ro-sample']) { - this.sample = this.componentSchema['x-redoc-ro-sample']; - return true; - } else if (!this.skipReadOnly && this.componentSchema['x-redoc-rw-sample']) { - this.sample = this.componentSchema['x-redoc-rw-sample']; - return true; - } - return false; - } - - bindEvents() { - this.element.addEventListener('click', (event) => { - var collapsed, target = event.target; - if (event.target.className === 'collapser') { - collapsed = target.parentNode.getElementsByClassName('collapsible')[0]; - if (collapsed.parentNode.classList.contains('collapsed')) { - collapsed.parentNode.classList.remove('collapsed'); - } else { - collapsed.parentNode.classList.add('collapsed'); - } - } - }); - } - - expandAll() { - let elements = this.element.getElementsByClassName('collapsible'); - for (let i = 0; i < elements.length; i++) { - let collapsed = elements[i]; - collapsed.parentNode.classList.remove('collapsed'); - } - } - - collapseAll() { - let elements = this.element.getElementsByClassName('collapsible'); - for (let i = 0; i < elements.length; i++) { - let expanded = elements[i]; - if (expanded.parentNode.classList.contains('redoc-json')) continue; - expanded.parentNode.classList.add('collapsed'); - } - } - - ngOnInit() { - this.preinit(); - } -} diff --git a/lib/components/Search/redoc-search.html b/lib/components/Search/redoc-search.html deleted file mode 100644 index aef24371..00000000 --- a/lib/components/Search/redoc-search.html +++ /dev/null @@ -1,15 +0,0 @@ -
-
×
- - - - -
-
    - -
diff --git a/lib/components/Search/redoc-search.scss b/lib/components/Search/redoc-search.scss deleted file mode 100644 index 601917af..00000000 --- a/lib/components/Search/redoc-search.scss +++ /dev/null @@ -1,85 +0,0 @@ -@import '../../shared/styles/variables'; - -:host { - display: block; - margin: 10px 0; -} - -.search-input-wrap { - padding: 0 20px; - - > svg { - width: 13px; - height: 27px; - display: inline-block; - position: absolute; - - path { - fill: lighten($text-color, 20%); - } - } - - .clear-button { - position: absolute; - display: inline-block; - width: 13px; - text-align: center; - right: 20px; - height: 28px; - line-height: 28px; - vertical-align: middle; - cursor: pointer; - } -} - -input { - width: 100%; - box-sizing: border-box; - padding: 5px 20px 5px 20px; - - border: 0; - border-bottom: 1px solid darken($side-bar-bg-color, 10%); - font-weight: bold; - - font-size: 13px; - color: $text-color; - background-color: transparent; - outline: none; -} - -.search-results { - margin: 10px 0 0; - list-style: none; - padding: 10px 0; - background-color: darken($side-bar-bg-color, 5%); - max-height: 100px; - overflow-y: auto; - border-bottom: 1px solid darken($side-bar-bg-color, 10%); - border-top: 1px solid darken($side-bar-bg-color, 10%); - line-height: 1.2; - - min-height: 150px; - max-height: 250px; - - > li { - display: block; - cursor: pointer; - font-family: Montserrat, sans-serif; - font-size: 13px; - padding: 5px 20px; - - &:hover { - background-color: darken($side-bar-bg-color, 10%); - } - } - - li.menu-item-depth-1 { - color: $primary-color; - text-transform: uppercase; - } - - > li.disabled { - cursor: default; - color: lighten($text-color, 60%); - } -} diff --git a/lib/components/Search/redoc-search.ts b/lib/components/Search/redoc-search.ts deleted file mode 100644 index 96b85ea2..00000000 --- a/lib/components/Search/redoc-search.ts +++ /dev/null @@ -1,93 +0,0 @@ -'use strict'; -import { Component, ChangeDetectionStrategy, ChangeDetectorRef, OnInit, HostBinding } from '@angular/core'; -import { Marker, SearchService, MenuService, MenuItem } from '../../services/'; -import { throttle } from '../../utils/'; - -@Component({ - selector: 'redoc-search', - styleUrls: ['./redoc-search.css'], - templateUrl: './redoc-search.html', - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RedocSearch implements OnInit { - logo:any = {}; - items: { menuItem: MenuItem, pointers: string[] }[] = []; - searchTerm = ''; - throttledSearch: Function; - - _subscription; - - constructor( - cdr: ChangeDetectorRef, - private marker: Marker, - public search: SearchService, - public menu: MenuService) { - this._subscription = menu.changed.subscribe(() => { - cdr.markForCheck(); - cdr.detectChanges(); - }); - - this.throttledSearch = throttle(() => { - this.updateSearch(); - cdr.markForCheck(); - cdr.detectChanges(); - }, 300, this); - } - - init() { - this.search.indexAll(); - } - - clearSearch() { - this.searchTerm = ''; - this.updateSearch(); - } - - update(event:KeyboardEvent, val) { - if (event && event.keyCode === 27) { // escape - this.searchTerm = ''; - } else { - this.searchTerm = val; - } - - this.throttledSearch(); - } - - updateSearch() { - if (!this.searchTerm || this.searchTerm.length < 2) { - this.items = []; - this.marker.unmark(); - return; - } - - let searchRes = this.search.search(this.searchTerm); - this.items = Object.keys(searchRes).map(id => ({ - menuItem: this.menu.getItemById(id), - pointers: searchRes[id].map(el => el.pointer) - })).filter(res => !!res.menuItem); - - this.items.sort((a, b) => { - if (a.menuItem.depth > b.menuItem.depth) return 1; - else if (a.menuItem.depth < b.menuItem.depth) return -1; - else return 0; - }); - this.marker.mark(this.searchTerm); - } - - clickSearch(item) { - this.search.ensureSearchVisible( - item.pointers - ); - this.marker.remark(); - this.menu.activate(item.menuItem); - this.menu.scrollToActive(); - } - - ngOnInit() { - this.init(); - } - - destroy() { - this._subscription.unsubscribe(); - } -} diff --git a/lib/components/SecurityDefinitions/security-definitions.html b/lib/components/SecurityDefinitions/security-definitions.html deleted file mode 100644 index e5fd43a6..00000000 --- a/lib/components/SecurityDefinitions/security-definitions.html +++ /dev/null @@ -1,38 +0,0 @@ -
-

- {{def.name}}

-
- - - - - - - - - - - - - - - - - - - - - - - -
Security scheme type: {{def.details._displayType}}
{{def.details.in}} parameter name: {{def.details.name}}
OAuth2 Flow {{def.details.flow}}
Authorization URL {{def.details.authorizationUrl}}
Token URL {{def.details.tokenUrl}}
- -

OAuth2 Scopes

- - - - - -
{{scopeName}} {{def.details.scopes[scopeName]}}
-
-
diff --git a/lib/components/SecurityDefinitions/security-definitions.scss b/lib/components/SecurityDefinitions/security-definitions.scss deleted file mode 100644 index 2d2a1da8..00000000 --- a/lib/components/SecurityDefinitions/security-definitions.scss +++ /dev/null @@ -1,36 +0,0 @@ -@import '../../shared/styles/variables'; - -:host { - display: block; -} - -.security-definition:not(:last-of-type) { - border-bottom: 1px solid rgba($text-color, .3); - padding-bottom: 20px; -} - -:host h2 { - padding-top: $section-spacing; -} - -h3 { - margin: 1em 0; - font-size: 1em; -} - -:host .security-scopes-details, :host .security-details { - margin-top: 20px; -} - -table.details th, table.details td { - font-weight: bold; - width: 200px; - max-width: 50%; -} - -table.details th { - text-align: left; - padding: 6px; - text-transform: capitalize; - font-weight: normal; -} diff --git a/lib/components/SecurityDefinitions/security-definitions.ts b/lib/components/SecurityDefinitions/security-definitions.ts deleted file mode 100644 index 0faa2562..00000000 --- a/lib/components/SecurityDefinitions/security-definitions.ts +++ /dev/null @@ -1,50 +0,0 @@ -'use strict'; -import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core'; -import { SpecManager, BaseComponent } from '../base'; - -import { ComponentParser } from '../../services/component-parser.service'; - -const AUTH_TYPES = { - 'oauth2': 'OAuth2', - 'apiKey': 'API Key', - 'basic': 'Basic Authorization' -} - -@Component({ - selector: 'security-definitions', - styleUrls: ['./security-definitions.css'], - templateUrl: './security-definitions.html', - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class SecurityDefinitions extends BaseComponent implements OnInit { - info: any = {}; - specUrl: String; - defs: any[]; - - static insertTagIntoDescription(md:string) { - if (ComponentParser.contains(md, 'security-definitions')) return md; - if (/^#\s?Authentication\s*$/mi.test(md)) return md; - return md + '\n# Authentication \n' + ComponentParser.build('security-definitions'); - } - - constructor(specMgr:SpecManager) { - super(specMgr); - } - - init() { - this.componentSchema = this.componentSchema.securityDefinitions; - this.defs = Object.keys(this.componentSchema).map(name => { - let details = this.componentSchema[name]; - details._displayType = AUTH_TYPES[details.type]; - return { - name, - details - } - }); - - } - - ngOnInit() { - this.preinit(); - } -} diff --git a/lib/components/SideMenu/side-menu-items.html b/lib/components/SideMenu/side-menu-items.html deleted file mode 100644 index 726900cd..00000000 --- a/lib/components/SideMenu/side-menu-items.html +++ /dev/null @@ -1,13 +0,0 @@ - diff --git a/lib/components/SideMenu/side-menu-items.scss b/lib/components/SideMenu/side-menu-items.scss deleted file mode 100644 index ebdec669..00000000 --- a/lib/components/SideMenu/side-menu-items.scss +++ /dev/null @@ -1,134 +0,0 @@ -@import '../../shared/styles/variables'; - -.menu-item-header { - cursor: pointer; - color: rgba($text-color, .9); - -webkit-transition: all .15s ease-in-out; - -moz-transition: all .15s ease-in-out; - -ms-transition: all .15s ease-in-out; - -o-transition: all .15s ease-in-out; - transition: all .15s ease-in-out; - display: block; - margin: 0; - padding: $side-menu-item-vpadding*2.5 $side-menu-item-hpadding; - - &[hidden] { - display: none; - } - - &.disabled, &.disabled:hover { - cursor: default; - color: lighten($text-color, 60%); - } - - &.deprecated { - text-decoration: line-through; - color: lighten($text-color, 60%); - } - - display: flex; - justify-content: space-between; - - > svg { - height: 18px; - vertical-align: middle; - float: right; - transform: rotateZ(-90deg); - //transition: transform 0.3s ease-out; - - polygon { - fill: $border-color; - } - - .active > & { - transform: rotateZ(0); - } - } -} - -.menu-item { - -webkit-transition: all .15s ease-in-out; - -moz-transition: all .15s ease-in-out; - -ms-transition: all .15s ease-in-out; - -o-transition: all .15s ease-in-out; - transition: all .15s ease-in-out; - list-style: none inside none; - overflow: hidden; - text-overflow: ellipsis; - padding: 0; -} - -.menu-subitems { - margin: 0; - font-size: 0.929em; - line-height: 1.2em; - font-weight: $light; - color: rgba($text-color, .9); - padding: 0; - overflow: hidden; - height: 0; - - .active > & { - height: auto; - } -} - -.menu-item-depth-1 { - > .menu-item-header { - font-family: $headers-font, $headers-font-family; - font-weight: $light; - font-size: $h5; - text-transform: uppercase; - } - - // do not capitalize method summuary in level-1 menu - &.menu-item-for-operation > .menu-item-header { - text-transform: none; - } - - > .menu-item-header:not(.disabled):hover, - &.active > .menu-item-header { - color: $primary-color; - background: $side-menu-active-bg-color; - } - &.active { - //background: $side-menu-active-bg-color; - } -} - -.menu-item-depth-2 { - > .menu-item-header { - padding-left: $side-menu-item-hpadding; - } - - > .menu-item-header:hover, - &.active > .menu-item-header { - background: darken($side-menu-active-bg-color, 6%); - } -} - -// group items -.menu-item-depth-0 { - margin-top: 15px; - - > .menu-subitems { - height: auto; - } - - > .menu-item-header { - font-family: $headers-font, $headers-font-family; - color: rgba($text-color, .4); - text-transform: uppercase; - font-size: 0.8em; - padding-bottom: 0; - cursor: default; - - > svg { - display: none; - } - } - &:hover, - &.active { - //background: none; - } -} diff --git a/lib/components/SideMenu/side-menu.html b/lib/components/SideMenu/side-menu.html deleted file mode 100644 index 301cd443..00000000 --- a/lib/components/SideMenu/side-menu.html +++ /dev/null @@ -1,17 +0,0 @@ -
- - {{activeCatCaption}} - {{activeItemCaption}} - -
- - - - -
- -
diff --git a/lib/components/SideMenu/side-menu.scss b/lib/components/SideMenu/side-menu.scss deleted file mode 100644 index f213f419..00000000 --- a/lib/components/SideMenu/side-menu.scss +++ /dev/null @@ -1,90 +0,0 @@ -@import '../../shared/styles/variables'; -$mobile-menu-compact-breakpoint: 550px; - -:host { - display: flex; - box-sizing: border-box; -} - -#resources-nav { - position: relative; - width: 100%; - overflow: scroll; -} - -ul.menu-root { - margin: 0; - padding: 0; -} - -.mobile-nav { - display: none; - height: 3em; - line-height: 3em; - box-sizing: border-box; - border-bottom: 1px solid #ccc; - cursor: pointer; - - &:after { - content: ""; - display: inline-block; - width: 3em; - height: 3em; - background: url('data:image/svg+xml;utf8,'); - background-size: 70%; - background-repeat: no-repeat; - background-position: center; - - float: right; - vertical-align: middle; - } -} - -@media (max-width: $side-menu-mobile-breakpoint) { - :host { - display: block; - } - - .mobile-nav { - display: block; - } - - #resources-nav { - height: 0; - overflow-y: auto; - transition: all 0.3s ease; - } - - .menu-subitems { - height: auto; - } -} - -.selected-tag { - text-transform: capitalize; -} - -.selected-endpoint:before { - content: "/"; - padding: 0 2px; - color: #ccc; -} - -.selected-endpoint:empty:before { - display: none; -} - -.selected-item-info { - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - box-sizing: border-box; - max-width: 350px; - - @media (max-width: $mobile-menu-compact-breakpoint) { - display: inline-block; - padding: 0 20px; - max-width: 80%; - max-width: calc(100% - 4em); - } -} diff --git a/lib/components/SideMenu/side-menu.spec.ts b/lib/components/SideMenu/side-menu.spec.ts deleted file mode 100644 index b31e476d..00000000 --- a/lib/components/SideMenu/side-menu.spec.ts +++ /dev/null @@ -1,96 +0,0 @@ -'use strict'; - -import { getChildDebugElement } from '../../../tests/helpers'; -import { Component } from '@angular/core'; -import { OptionsService, MenuItem } from '../../services/index'; - -import { - inject, - async -} from '@angular/core/testing'; - -import { TestBed, ComponentFixture } from '@angular/core/testing'; - -import { OperationsList, SideMenu } from '../index'; - -import { SpecManager } from '../../utils/spec-manager'; - -let testOptions; - -describe('Redoc components', () => { - beforeEach(() => { - TestBed.configureTestingModule({ declarations: [ TestAppComponent, OperationsList ] }); - }); - describe('SideMenu Component', () => { - let builder; - let component: SideMenu; - let fixture: ComponentFixture; - let specMgr; - - beforeEach(inject([SpecManager, OptionsService], - (_specMgr, opts) => { - - testOptions = opts; - testOptions.options = { - scrollYOffset: () => 0, - $scrollParent: window - }; - specMgr = _specMgr; - })); - - beforeEach(done => { - specMgr.load('/tests/schemas/extended-petstore.yml').then(done, done.fail); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(TestAppComponent); - component = getChildDebugElement(fixture.debugElement, 'side-menu').componentInstance; - fixture.detectChanges(); - }); - - afterEach(() => { - if (fixture) fixture.destroy(); - }); - - it('should init component and component data', () => { - should.exist(component); - }); - - it('should clear active item and cat captions on change to null', () => { - component.activeCatCaption = 'test'; - component.activeItemCaption = 'test'; - component.changed(null); - component.activeCatCaption.should.be.equal(''); - component.activeItemCaption.should.be.equal(''); - }); - - it('should set active item and cat captions on change event', () => { - let parentItem: MenuItem = { - id: 'id', - name: 'Item' - }; - component.changed(parentItem); - component.activeCatCaption.should.be.equal(parentItem.name); - component.activeItemCaption.should.be.equal(''); - - let childItem: MenuItem = { - id: 'id2', - name: 'Child', - parent: parentItem - }; - component.changed(childItem); - component.activeCatCaption.should.be.equal(parentItem.name); - component.activeItemCaption.should.be.equal(childItem.name); - }); - }); -}); - -/** Test component that contains an ApiInfo. */ -@Component({ - selector: 'test-app', - template: - ` - ` -}) -class TestAppComponent { -} diff --git a/lib/components/SideMenu/side-menu.ts b/lib/components/SideMenu/side-menu.ts deleted file mode 100644 index c92b8424..00000000 --- a/lib/components/SideMenu/side-menu.ts +++ /dev/null @@ -1,163 +0,0 @@ -'use strict'; - -import { Component, - EventEmitter, - Input, - Output, - ElementRef, - ChangeDetectorRef, - ViewChild, - OnInit, - OnDestroy -} from '@angular/core'; - -import { trigger, state, animate, transition, style } from '@angular/core'; -import { ScrollService, MenuService, OptionsService, MenuItem } from '../../services/'; -import { PerfectScrollbar } from '../../shared/components'; -import { BrowserDomAdapter as DOM } from '../../utils/browser-adapter'; - -const global = window; - -@Component({ - selector: 'side-menu-items', - templateUrl: './side-menu-items.html', - styleUrls: ['./side-menu-items.css'], -}) -export class SideMenuItems { - @Input() items: MenuItem[]; - @Output() activate = new EventEmitter(); - - activateItem(item) { - this.activate.next(item); - } -} - -@Component({ - selector: 'side-menu', - templateUrl: './side-menu.html', - styleUrls: ['./side-menu.css'] -}) -export class SideMenu implements OnInit, OnDestroy { - activeCatCaption: string; - activeItemCaption: string; - menuItems: Array; - @Input() itemsTemplate; - @ViewChild(PerfectScrollbar) PS:PerfectScrollbar; - - private options: any; - private $element: any; - private $mobileNav: any; - private $resourcesNav: any; - private $scrollParent: any; - - private changedActiveSubscription; - private changedSubscription; - - constructor( - elementRef:ElementRef, - private scrollService:ScrollService, - private menuService:MenuService, - optionsService:OptionsService, - private detectorRef:ChangeDetectorRef, - ) { - this.$element = elementRef.nativeElement; - - this.activeCatCaption = ''; - this.activeItemCaption = ''; - - this.options = optionsService.options; - - this.changedActiveSubscription = this.menuService.changedActiveItem.subscribe((evt) => this.changed(evt)); - this.changedSubscription = this.menuService.changed.subscribe((evt) => { - this.update(); - }); - } - - changed(item) { - if (!item) { - this.activeCatCaption = ''; - this.activeItemCaption = ''; - return; - } - if (item.parent) { - this.activeItemCaption = item.name; - this.activeCatCaption = item.parent.name; - } else { - this.activeCatCaption = item.name; - this.activeItemCaption = ''; - } - - // safari doesn't update bindings if not run changeDetector manually :( - this.update(); - this.scrollActiveIntoView(); - } - - update() { - this.detectorRef.detectChanges(); - this.PS && this.PS.update(); - } - - scrollActiveIntoView() { - let $item = this.$element.querySelector('li.active, label.active'); - if ($item) $item.scrollIntoViewIfNeeded(); - } - - activateAndScroll(item) { - if (this.mobileMode) { - this.toggleMobileNav(); - } - - this.menuService.activate(item); - this.menuService.scrollToActive(); - } - - init() { - this.menuItems = this.menuService.items; - - this.$mobileNav = DOM.querySelector(this.$element, '.mobile-nav'); - this.$resourcesNav = DOM.querySelector(this.$element, '#resources-nav'); - - //decorate scrollYOffset to account mobile nav - this.scrollService.scrollYOffset = () => { - let mobileNavOffset = this.$mobileNav.clientHeight; - return this.options.scrollYOffset() + mobileNavOffset; - }; - } - - get mobileMode() { - return this.$mobileNav.clientHeight > 0; - } - - toggleMobileNav() { - let $overflowParent = (this.options.$scrollParent === global) ? DOM.defaultDoc().body - : this.$scrollParent; - if (DOM.hasStyle(this.$resourcesNav, 'height')) { - DOM.removeStyle(this.$resourcesNav, 'height'); - DOM.removeStyle($overflowParent, 'overflow-y'); - } else { - let viewportHeight = this.options.$scrollParent.innerHeight - || this.options.$scrollParent.clientHeight; - let height = viewportHeight - this.$mobileNav.getBoundingClientRect().bottom; - DOM.setStyle($overflowParent, 'overflow-y', 'hidden'); - DOM.setStyle(this.$resourcesNav, 'height', height + 'px'); - } - } - - destroy() { - this.changedActiveSubscription.unsubscribe(); - this.changedSubscription.unsubscribe(); - this.scrollService.unbind(); - this.menuService.destroy(); - } - - ngOnDestroy() { - this.destroy(); - } - - ngOnInit() { - this.init(); - } - - ngAfterViewInit() { - } -} diff --git a/lib/components/Warnings/warnings.html b/lib/components/Warnings/warnings.html deleted file mode 100644 index 324fdc88..00000000 --- a/lib/components/Warnings/warnings.html +++ /dev/null @@ -1,4 +0,0 @@ -
- × -
{{message}}
-
diff --git a/lib/components/Warnings/warnings.scss b/lib/components/Warnings/warnings.scss deleted file mode 100644 index c5b2b9dc..00000000 --- a/lib/components/Warnings/warnings.scss +++ /dev/null @@ -1,33 +0,0 @@ -:host { - width: 60%; - display: block; -} - -.message { - padding: 5px 40px; - background-color: #fcf8e3; - color: #8a6d3b; - - &:before { - content: "Warning: "; - font-weight: bold; - } -} - -.warnings-close { - font-size: 150%; - color: black; - opacity: 0.4; - float: right; - margin: 5px 20px 0 0; - font-weight: bold; - cursor: pointer; - - &:hover { - opacity: 0.8; - } -} - -p { - display: inline; -} diff --git a/lib/components/Warnings/warnings.ts b/lib/components/Warnings/warnings.ts deleted file mode 100644 index 1172e1cd..00000000 --- a/lib/components/Warnings/warnings.ts +++ /dev/null @@ -1,36 +0,0 @@ -'use strict'; - -import { Component, OnInit } from '@angular/core'; -import { SpecManager, BaseComponent } from '../base'; -import { WarningsService, OptionsService } from '../../services/index'; - -@Component({ - selector: 'warnings', - styleUrls: ['./warnings.css'], - templateUrl: './warnings.html' -}) -export class Warnings extends BaseComponent implements OnInit { - warnings: Array = []; - shown: boolean = false; - suppressWarnings: boolean; - constructor(specMgr:SpecManager, optionsMgr: OptionsService) { - super(specMgr); - this.suppressWarnings = optionsMgr.options.suppressWarnings; - } - - init() { - this.shown = !this.suppressWarnings && !!this.warnings.length; - WarningsService.warnings.subscribe((warns) => { - this.warnings = warns; - this.shown = !this.suppressWarnings && !!warns.length; - }); - } - - close() { - this.shown = false; - } - - ngOnInit() { - this.preinit(); - } -} diff --git a/lib/components/base.spec.ts b/lib/components/base.spec.ts deleted file mode 100644 index 88e5a6ef..00000000 --- a/lib/components/base.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -'use strict'; - -import { SpecManager } from '../utils/spec-manager'; -import { BaseComponent } from '../components/base'; -import { OptionsService } from '../services/options.service'; - -describe('Redoc components', () => { - describe('BaseComponent', () => { - let specMgr; - let component; - - beforeAll(() => { - specMgr = new SpecManager(new OptionsService()); - specMgr._schema = {tags: []}; - }); - - beforeEach(() => { - component = new BaseComponent(specMgr); - }); - - it('should set instance properties', () => { - component.specMgr.should.be.equal(specMgr); - //component.schema.should.be.of.type('object'); - expect(component.componentSchema).toBeNull(); - }); - - it('should set componentSchema based on pointer on ngOnInit', () => { - component.pointer = '/tags'; - component.ngOnInit(); - component.componentSchema.should.be.deepEqual(specMgr._schema.tags); - }); - - it('should call init virtual methods after init', () => { - spyOn(component, 'init'); - component.ngOnInit(); - - component.init.calls.count().should.be.equal(1); - component.init.and.callThrough(); - }); - }); -}); diff --git a/lib/components/base.ts b/lib/components/base.ts deleted file mode 100644 index f38e6d2e..00000000 --- a/lib/components/base.ts +++ /dev/null @@ -1,84 +0,0 @@ -'use strict'; -import { OnInit, OnDestroy } from '@angular/core'; -import { SpecManager } from '../utils/spec-manager'; -import { AppStateService } from '../services/app-state.service'; -import { Subscription } from 'rxjs/Subscription'; - -export { SpecManager }; - -/** - * Generic Component - * @class - */ -export class BaseComponent implements OnInit, OnDestroy { - pointer: string; - componentSchema: any = null; - dereferencedCache = {}; - - constructor(public specMgr: SpecManager) { - } - - /** - * onInit method is run by angular2 after all component inputs are resolved - */ - ngOnInit() { - this.preinit(); - } - - preinit() { - this.componentSchema = this.specMgr.byPointer(this.pointer || ''); - this.init(); - } - - ngOnDestroy() { - this.destroy(); - } - - /** - * Used to initialize component - * @abstract - */ - init() { - // empty - } - - /** - + Used to destroy component - * @abstract - */ - destroy() { - // emtpy - } -} - -export abstract class BaseSearchableComponent extends BaseComponent implements OnDestroy { - searchSubscription: Subscription; - constructor(public specMgr: SpecManager, public app: AppStateService) { - super(specMgr); - } - - subscribeForSearch() { - this.searchSubscription = this.app.searchContainingPointers.subscribe(ptrs => { - for (let i = 0; i < ptrs.length; ++i) { - if (ptrs[i]) this.ensureSearchIsShown(ptrs[i]); - } - }); - } - - preinit() { - super.preinit(); - this.subscribeForSearch(); - } - - ngOnDestroy() { - if (this.searchSubscription) { - this.searchSubscription.unsubscribe(); - } - } - - /** - + Used to destroy component - * @abstract - */ - abstract ensureSearchIsShown(ptr: string); -} diff --git a/lib/components/index.ts b/lib/components/index.ts deleted file mode 100644 index 719d20d6..00000000 --- a/lib/components/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; - -import { ApiInfo } from './ApiInfo/api-info'; -import { ApiLogo } from './ApiLogo/api-logo'; -import { JsonSchema } from './JsonSchema/json-schema'; -import { JsonSchemaLazy } from './JsonSchema/json-schema-lazy'; -import { ParamsList } from './ParamsList/params-list'; -import { RequestSamples } from './RequestSamples/request-samples'; -import { ResponsesList } from './ResponsesList/responses-list'; -import { ResponsesSamples } from './ResponsesSamples/responses-samples'; -import { SchemaSample } from './SchemaSample/schema-sample'; -import { SideMenu, SideMenuItems } from './SideMenu/side-menu'; -import { OperationsList } from './OperationsList/operations-list'; -import { Operation } from './Operation/operation'; -import { Warnings } from './Warnings/warnings'; -import { SecurityDefinitions } from './SecurityDefinitions/security-definitions'; -import { LoadingBar } from './LoadingBar/loading-bar'; -import { RedocSearch } from './Search/redoc-search'; -import { ExternalDocs } from './ExternalDocs/external-docs'; -import { EndpointLink } from './EndpointLink/endpoint-link'; - -import { Redoc } from './Redoc/redoc'; - -export const REDOC_DIRECTIVES = [ - ApiInfo, ApiLogo, JsonSchema, JsonSchemaLazy, ParamsList, RequestSamples, ResponsesList, - ResponsesSamples, SchemaSample, SideMenu, OperationsList, Operation, Warnings, Redoc, SecurityDefinitions, - LoadingBar, SideMenuItems, RedocSearch, ExternalDocs, EndpointLink -]; - -export { ApiInfo, ApiLogo, JsonSchema, JsonSchemaLazy, ParamsList, RequestSamples, ResponsesList, -ResponsesSamples, SchemaSample, SideMenu, OperationsList, Operation, Warnings, Redoc, SecurityDefinitions, -LoadingBar, SideMenuItems, ExternalDocs, EndpointLink }; diff --git a/lib/index.ts b/lib/index.ts deleted file mode 100644 index beccc25b..00000000 --- a/lib/index.ts +++ /dev/null @@ -1,60 +0,0 @@ -'use strict'; -import './components/Redoc/redoc-initial-styles.css'; - -import { enableProdMode } from '@angular/core'; -import { Redoc } from './components/index'; -import { BrowserDomAdapter as DOM } from './utils/browser-adapter'; -import { disableDebugTools } from '@angular/platform-browser'; -import { isString } from './utils/helpers'; - -var bootstrapRedoc; -if (AOT) { - bootstrapRedoc = require('./bootstrap').bootstrapRedoc; -} else { - bootstrapRedoc = require('./bootstrap.dev').bootstrapRedoc; -} - -if (IS_PRODUCTION) { - enableProdMode(); -} - -export const version = LIB_VERSION; - -var moduleRef; -export function init(specUrlOrSpec:string|any, options:any = {}) { - if (moduleRef) { - destroy(); - } - - Redoc._preOptions = options; - options.specUrl = options.specUrl || (isString(specUrlOrSpec) ? specUrlOrSpec : ''); - if (!isString(specUrlOrSpec)) { - options.spec = specUrlOrSpec; - } - return bootstrapRedoc() - .then(appRef => { - moduleRef = appRef; - if (IS_PRODUCTION) disableDebugTools(); - console.log('ReDoc initialized!'); - }).catch(err => { - throw err; - }); -}; - -export function destroy() { - moduleRef.destroy(); - moduleRef = null; -}; - - -function autoInit() { - const specUrlAttributeName = 'spec-url'; - let redocEl = DOM.query('redoc'); - if (!redocEl) return; - if (DOM.hasAttribute(redocEl, specUrlAttributeName)) { - let url = DOM.getAttribute(redocEl, specUrlAttributeName); - init(url); - } -}; - -autoInit(); diff --git a/lib/polyfills.ts b/lib/polyfills.ts deleted file mode 100644 index e3f69e14..00000000 --- a/lib/polyfills.ts +++ /dev/null @@ -1,30 +0,0 @@ -import 'core-js/es7/reflect'; -import 'zone.js/dist/zone'; - -import 'core-js/es6/symbol'; -import 'core-js/es6/object'; -import 'core-js/es6/function'; -import 'core-js/es6/parse-int'; -import 'core-js/es6/parse-float'; -import 'core-js/es6/number'; -import 'core-js/es6/math'; -import 'core-js/es6/string'; -import 'core-js/es6/date'; -import 'core-js/es6/array'; -import 'core-js/es6/regexp'; -import 'core-js/es6/map'; -import 'core-js/es6/set'; -import 'core-js/es6/weak-map'; -import 'core-js/es6/weak-set'; -import 'core-js/es6/typed'; -import 'core-js/es6/reflect'; -// see issue https://github.com/AngularClass/angular2-webpack-starter/issues/709 -// import 'core-js/es6/promise'; - -// Typescript emit helpers polyfill -import 'ts-helpers'; - -if (!IS_PRODUCTION) { - Error.stackTraceLimit = Infinity; - require('zone.js/dist/long-stack-trace-zone'); -} diff --git a/lib/redoc.module.ts b/lib/redoc.module.ts deleted file mode 100644 index 8fd0372a..00000000 --- a/lib/redoc.module.ts +++ /dev/null @@ -1,65 +0,0 @@ -import './vendor'; - -import { NgModule, ErrorHandler, APP_ID } from '@angular/core'; -import { CommonModule } from '@angular/common'; - -import { SpecManager } from './utils/spec-manager'; - -import { Redoc, SecurityDefinitions, Operation, REDOC_DIRECTIVES } from './components/index'; -import { REDOC_COMMON_DIRECTIVES, DynamicNg2Wrapper, DropDown } from './shared/components/index'; -import { REDOC_PIPES } from './utils/pipes'; -import { CustomErrorHandler } from './utils/' -import { LazyTasksService } from './shared/components/LazyFor/lazy-for'; - -import { - OptionsService, - Options, - MenuService, - ScrollService, - Hash, - WarningsService, - AppStateService, - ComponentParser, - ContentProjector, - Marker, - SchemaHelper, - SearchService, - MenuItem, - COMPONENT_PARSER_ALLOWED } from './services/'; - -@NgModule({ - imports: [ CommonModule ], - declarations: [ REDOC_DIRECTIVES, REDOC_COMMON_DIRECTIVES, REDOC_PIPES ], - bootstrap: [ Redoc ], - entryComponents: [ SecurityDefinitions, DynamicNg2Wrapper, Operation ], - providers: [ - ScrollService, - Hash, - WarningsService, - OptionsService, - AppStateService, - ComponentParser, - ContentProjector, - { provide: APP_ID, useValue: 'redoc' }, - { provide: ErrorHandler, useClass: CustomErrorHandler }, - { provide: COMPONENT_PARSER_ALLOWED, useValue: { 'security-definitions': SecurityDefinitions} } - ], - exports: [Redoc, REDOC_DIRECTIVES, REDOC_COMMON_DIRECTIVES, REDOC_PIPES] -}) -export class RedocModule { -} - -export { Redoc, SpecManager, ScrollService, -Hash, -WarningsService, -OptionsService, -Options, -AppStateService, -ComponentParser, -ContentProjector, -MenuService, -SearchService, -SchemaHelper, -LazyTasksService, -MenuItem, -Marker, DropDown }; diff --git a/lib/services/app-state.service.ts b/lib/services/app-state.service.ts deleted file mode 100644 index 24eb85bd..00000000 --- a/lib/services/app-state.service.ts +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -import { Injectable } from '@angular/core'; -import { Subject } from 'rxjs/Subject'; -import { BehaviorSubject } from 'rxjs/BehaviorSubject'; - -@Injectable() -export class AppStateService { - samplesLanguage = new Subject(); - error = new BehaviorSubject(null); - loading = new Subject(); - initialized = new BehaviorSubject(false); - rightPanelHidden = new BehaviorSubject(false); - - searchContainingPointers = new BehaviorSubject([]); - - startLoading() { - this.loading.next(true); - } - - stopLoading() { - this.loading.next(false); - } -} diff --git a/lib/services/clipboard.service.spec.ts b/lib/services/clipboard.service.spec.ts deleted file mode 100644 index acde50b2..00000000 --- a/lib/services/clipboard.service.spec.ts +++ /dev/null @@ -1,56 +0,0 @@ -'use strict'; - -import { Clipboard } from './clipboard.service'; - -describe('Clipboard Service', () => { - let el:Node; - let copiedText = null; - - function createEl(html) { - let tmpDiv = document.createElement('div'); - tmpDiv.innerHTML = html; - document.body.appendChild(tmpDiv); - return tmpDiv.lastChild; - } - - beforeEach(() => { - spyOn(Clipboard, 'copySelected').and.callFake(() => { - copiedText = window.getSelection().toString(); - return true; - }); - }); - - afterEach(() => { - copiedText = null; - if (el && el.parentNode) el.parentNode.removeChild(el); - (Clipboard.copySelected).and.callThrough(); - }); - - it('selectElement should select element text', () => { - el = createEl('
Test
'); - Clipboard.selectElement(el); - let selected = window.getSelection().toString(); - selected.should.be.equal('Test'); - }); - - it('deselect should clear selection', () => { - el = createEl('
Test
'); - Clipboard.selectElement(el); - let selected = window.getSelection().toString(); - selected.should.be.equal('Test'); - Clipboard.deselect(); - window.getSelection().toString().should.be.equal(''); - }); - - it('copyElement should copy and deselect', () => { - el = createEl('
Test
'); - Clipboard.copyElement(el); - copiedText.should.be.equal('Test'); - window.getSelection().toString().should.be.equal(''); - }); - - it('copyCustom should copy custom text', () => { - Clipboard.copyCustom('Custom text'); - copiedText.should.be.equal('Custom text'); - }); -}); diff --git a/lib/services/component-parser.service.ts b/lib/services/component-parser.service.ts deleted file mode 100644 index 9fbc3cae..00000000 --- a/lib/services/component-parser.service.ts +++ /dev/null @@ -1,88 +0,0 @@ -'use strict'; - -import { - Injectable, - Renderer, - ComponentRef, - Type, - Injector, - Inject, - ComponentFactoryResolver -} from '@angular/core'; - -export type NodesOrComponents = HTMLElement | ComponentRef; -export const COMPONENT_PARSER_ALLOWED = 'COMPONENT_PARSER_ALLOWED'; - -const COMPONENT_REGEXP = '^\\s*\\s*$'; - -@Injectable() -export class ComponentParser { - private renderer: Renderer; - private allowedComponents: any; - - static contains(content: string, componentSelector: string) { - let regexp = new RegExp(COMPONENT_REGEXP.replace('{component}', `<${componentSelector}.*>`), 'mi'); - return regexp.test(content); - } - - static build(componentSelector) { - return ``; - } - - constructor( - private resolver: ComponentFactoryResolver, - @Inject(COMPONENT_PARSER_ALLOWED) allowedComponents - ) { - this.allowedComponents = allowedComponents; - } - - setRenderer(_renderer: Renderer) { - this.renderer = _renderer; - } - - splitIntoNodesOrComponents(content: string, injector: Injector):NodesOrComponents[] { - let componentDefs = []; - let match; - let anyCompRegexp = new RegExp(COMPONENT_REGEXP.replace('{component}', '(.*?)'), 'gmi'); - while (match = anyCompRegexp.exec(content)) { - componentDefs.push(match[1]); - } - - let splitCompRegexp = new RegExp(COMPONENT_REGEXP.replace('{component}', '.*?'), 'mi'); - let htmlParts = content.split(splitCompRegexp); - let res = []; - for (let i = 0; i < htmlParts.length; i++) { - let node = this.renderer.createElement(null, 'div'); - this.renderer.setElementProperty(node, 'innerHTML', htmlParts[i]); - if (htmlParts[i]) res.push(node); - if (componentDefs[i]) { - let componentRef = this.createComponentByHtml(componentDefs[i], injector); - res.push(componentRef); - } - } - return res; - } - - createComponentByHtml(htmlTag: string, injector:Injector):ComponentRef| null { - let { componentType } = this._parseHtml(htmlTag); - if (!componentType) return null; - - let factory = this.resolver.resolveComponentFactory(componentType); - return factory.create(injector); - } - - private _parseHtml(htmlTag: string):{componentType: Type | null, options: any} { - // TODO: for now only primitive parsing by tagname - let match = /<([\w_-]+).*?>/.exec(htmlTag); - if (match.length <= 1) return { componentType: null, options: null }; - let componentName = match[1]; - - let componentType = this.allowedComponents[componentName]; - // TODO parse options - let options = {}; - return { - componentType, - options - }; - } -} diff --git a/lib/services/content-projector.service.ts b/lib/services/content-projector.service.ts deleted file mode 100644 index 327e8aa6..00000000 --- a/lib/services/content-projector.service.ts +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -import { - Injectable, - ComponentFactory, - ComponentRef, - ViewContainerRef -} from '@angular/core'; - -@Injectable() -export class ContentProjector { - instantiateAndProject(componentFactory: ComponentFactory, - parentView:ViewContainerRef, projectedNodesOrComponents: any[]):ComponentRef { - let contextInjector = parentView.parentInjector; - - let projectedNodes = []; - let componentRefs:ComponentRef[] = []; - - for (let i=0; i < projectedNodesOrComponents.length; i++) { - let nodeOrCompRef = projectedNodesOrComponents[i]; - if (nodeOrCompRef instanceof ComponentRef) { - projectedNodes.push(nodeOrCompRef.location.nativeElement); - componentRefs.push(nodeOrCompRef); - } else { - projectedNodes.push(nodeOrCompRef); - } - } - - let parentCompRef = parentView.createComponent(componentFactory, null, contextInjector, [projectedNodes]); - - // using private property to get view instance - let viewContainer = (parentView)._view; - let viewData = (parentView)._data; - viewData.viewContainer._embeddedViews = viewData.viewContainer.embeddedViews || []; - for (let i=0; i < componentRefs.length; i++) { - let compRef = componentRefs[i]; - // attach view to containter change detector - viewData.viewContainer._embeddedViews.push((compRef.hostView)._view); - (compRef.hostView).attachToViewContainerRef(viewContainer); - } - return parentCompRef; - } -} diff --git a/lib/services/hash.service.spec.ts b/lib/services/hash.service.spec.ts deleted file mode 100644 index 98cfabfc..00000000 --- a/lib/services/hash.service.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; -import { - inject -} from '@angular/core/testing'; - -import { Hash } from './hash.service'; - -describe('Hash Service', () => { - let hashService; - - beforeEach(inject([Hash], (_hash) => { - hashService = _hash; - })); - - it('should trigger changed event when method `start` is called', () => { - spyOn(hashService.value, 'next').and.stub(); - hashService.start(); - expect(hashService.value.next).toHaveBeenCalled(); - hashService.value.next.and.callThrough(); - }); -}); diff --git a/lib/services/hash.service.ts b/lib/services/hash.service.ts deleted file mode 100644 index df381559..00000000 --- a/lib/services/hash.service.ts +++ /dev/null @@ -1,52 +0,0 @@ -'use strict'; -import { Injectable } from '@angular/core'; -import { PlatformLocation } from '@angular/common'; -import { BehaviorSubject } from 'rxjs/BehaviorSubject'; - -import { debounce } from '../utils/'; - -@Injectable() -export class Hash { - public value = new BehaviorSubject(null); - private noEmit:boolean = false; - private debouncedUpdate: (hash:string, rewrite: boolean) => void; - - constructor(private location: PlatformLocation) { - this.bind(); - - this.debouncedUpdate = debounce(this._update.bind(this), 100); - } - - start() { - this.value.next(this.hash); - } - - get hash() { - return this.location.hash; - } - - bind() { - this.location.onHashChange(() => { - if (this.noEmit) return; - this.value.next(this.hash); - }); - } - - update(hash: string|null, rewriteHistory:boolean = false) { - this.debouncedUpdate(hash, rewriteHistory); - } - - private _update(hash: string|null, rewriteHistory:boolean = false) { - if (hash == undefined) return; - if (rewriteHistory) { - window.history.replaceState(null, '', window.location.href.split('#')[0] + '#' + hash); - return; - } - this.noEmit = true; - window.location.hash = hash; - setTimeout(() => { - this.noEmit = false; - }); - } - -} diff --git a/lib/services/index.ts b/lib/services/index.ts deleted file mode 100644 index 7e26d9eb..00000000 --- a/lib/services/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -export * from './app-state.service'; -export * from './options.service'; -export * from './menu.service'; -export * from './scroll.service'; -export * from './hash.service'; -export * from './schema-normalizer.service'; -export * from './schema-helper.service'; -export * from './warnings.service'; -export * from './search.service'; - -export * from './component-parser.service'; -export * from './content-projector.service'; -export * from './marker.service'; diff --git a/lib/services/marker.service.ts b/lib/services/marker.service.ts deleted file mode 100644 index d853ddd2..00000000 --- a/lib/services/marker.service.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Injectable } from '@angular/core'; -import * as Mark from 'mark.js'; -import { MenuService } from './menu.service'; - -const ROLL_LEN = 5; -@Injectable() -export class Marker { - permInstances = []; - rolledInstances = new Array(ROLL_LEN); - term: string; - - currIdx = -1; - - constructor(private menu: MenuService) { - menu.changedActiveItem.subscribe(() => { - this.roll(); - }); - } - - addElement(el: Element) { - this.permInstances.push(new Mark(el)); - } - - newMarkerAtMenuItem(idx:number) { - let context = this.menu.getEl(idx); - - if (this.menu.isTagOrGroupItem(idx)) { - context = this.menu.getTagInfoEl(idx); - } - let newInst = context && new Mark(context); - if (newInst && this.term) { - newInst.mark(this.term); - } - return newInst; - } - - roll() { - let newIdx = this.menu.activeIdx; - let diff = newIdx - this.currIdx; - this.currIdx = newIdx; - if (diff < 0) { - diff = - diff; - for (let i=0; i < Math.min(diff, ROLL_LEN); i++) { - let prevInst = this.rolledInstances.pop(); - if(prevInst) prevInst.unmark(); - - let idx = newIdx - Math.floor(ROLL_LEN/2) + i; - let newMark = this.newMarkerAtMenuItem(idx); - this.rolledInstances.unshift(newMark); - } - } else { - for (let i=0; i < Math.min(diff, ROLL_LEN); i++) { - let oldInst = this.rolledInstances.shift(); - if (oldInst) oldInst.unmark(); - - let idx = newIdx + Math.floor(ROLL_LEN/2) - i; - let newMark = this.newMarkerAtMenuItem(idx); - this.rolledInstances.push(newMark); - } - } - } - - mark(term: string) { - this.term = term || null; - this.remark(); - } - - remark() { - for (let marker of this.permInstances) { - if (marker) { - marker.unmark(); - if (this.term) marker.mark(this.term); - } - } - for (let marker of this.rolledInstances) { - if (marker) { - marker.unmark(); - if (this.term) marker.mark(this.term); - } - } - } - - unmark() { - this.term = null; - this.remark(); - } -} diff --git a/lib/services/menu.service.spec.ts b/lib/services/menu.service.spec.ts deleted file mode 100644 index 5e87824a..00000000 --- a/lib/services/menu.service.spec.ts +++ /dev/null @@ -1,193 +0,0 @@ -'use strict'; -import { Component } from '@angular/core'; -import { - inject, - TestBed -} from '@angular/core/testing'; - -import { OperationsList } from '../components/OperationsList/operations-list'; -import { MenuService, MenuItem } from './menu.service'; -import { Hash } from './hash.service'; -import { LazyTasksService } from '../shared/components/LazyFor/lazy-for'; -import { ScrollService } from './scroll.service'; -import { SchemaHelper } from './schema-helper.service'; -import { SpecManager } from '../utils/spec-manager'; - -describe('Menu service', () => { - beforeEach(() => { - TestBed.configureTestingModule({ declarations: [ TestAppComponent, OperationsList ] }); - }); - - let menu:MenuService, hashService, scroll, tasks; - let specMgr; - - beforeEach(inject([SpecManager, Hash, ScrollService, LazyTasksService], - ( _specMgr, _hash, _scroll, _tasks) => { - hashService = _hash; - spyOn(hashService, 'update').and.stub(); - scroll = _scroll; - tasks = _tasks; - specMgr = _specMgr; - SchemaHelper.setSpecManager(specMgr); - })); - - beforeEach(done => { - specMgr.load('/tests/schemas/extended-petstore.yml').then(done, done.fail); - }); - - beforeEach(() => { - menu = TestBed.get(MenuService); - let fixture = TestBed.createComponent(TestAppComponent); - fixture.detectChanges(); - }); - - it('should scroll to operation when location hash is present [jp]', (done) => { - let hash = '#tag/pet/paths/~1pet~1findByStatus/get'; - spyOn(menu, 'scrollToActive').and.callThrough(); - spyOn(window, 'scrollTo').and.stub(); - hashService.value.subscribe((hash) => { - if (!hash) return; - expect(menu.scrollToActive).toHaveBeenCalled(); - let scrollY = (window.scrollTo).calls.argsFor(0)[1]; - expect(scrollY).toBeGreaterThan(0); - (window.scrollTo).and.callThrough(); - done(); - }); - hashService.value.next(hash); - }); - // - it('should scroll to operation when location hash is present [operation]', (done) => { - let hash = '#operation/getPetById'; - spyOn(menu, 'scrollToActive').and.callThrough(); - spyOn(window, 'scrollTo').and.stub(); - hashService.value.subscribe((hash) => { - if (!hash) return; - expect(menu.scrollToActive).toHaveBeenCalled(); - let scrollY = (window.scrollTo).calls.argsFor(0)[1]; - expect(scrollY).toBeGreaterThan(0); - done(); - }); - hashService.value.next(hash); - }); - - it('should select next/prev menu item when scrolled down/up', () => { - // enable all items - menu.items.forEach(item => item.ready = true); - - scroll.$scrollParent = document.querySelector('#parent'); - menu.activeIdx.should.be.equal(-1); - - let nextElTop = menu.getEl(1).getBoundingClientRect().top; - scroll.$scrollParent.scrollTop = nextElTop + 1; - - //simulate scroll down - spyOn(scroll, 'scrollY').and.returnValue(nextElTop + 10); - menu.onScroll(true); - menu.activeIdx.should.be.equal(1); - - scroll.scrollY.and.returnValue(nextElTop - 2); - scroll.$scrollParent.scrollTop = nextElTop - 1; - menu.onScroll(false); - menu.activeIdx.should.be.equal(0); - }); - - describe('buildMenu method', () => { - let suitSchema = { - tags: [ - {name: 'tag1', description: 'info1', 'x-traitTag': true}, - {name: 'tag2', description: 'info2'}, - {name: 'tag4', description: 'info2', 'x-displayName': 'Tag Four'} - ], - paths: { - test: { - put: { - tags: ['tag1', 'tag3'], - summary: 'test put' - }, - get: { - tags: ['tag1', 'tag2'], - summary: 'test get' - }, - delete: { - tags: ['tag4'], - summary: 'test delete' - }, - // no tags - post: { - summary: 'test post' - } - } - } - }; - - let items:MenuItem[]; - beforeEach(() => { - menu.items = null; - specMgr._schema = suitSchema; - menu.buildMenu(); - items = menu.items; - }); - - it('should return instance of Array', () => { - items.should.be.instanceof(Array); - }); - - it('should return Array with correct number of items', () => { - // 3 - defined tags, 1 - tag3 and 1 operation item for operation without tag - items.length.should.be.equal(3 + 1 + 1); - }); - - it('should append not defined tags to the end of list', () => { - let item = items[3]; - item.name.should.be.equal('tag3'); - item.items.length.should.be.equal(1); - item.items[0].name.should.be.equal('test put'); - }); - - it('should append operation items without tags to the end of list', () => { - let operationItem = items[4]; - operationItem.name.should.be.equal('test post'); - operationItem.metadata.type.should.be.equal('operation'); - should.not.exist(operationItem.items); - }); - - it('should map x-traitTag to empty operation list', () => { - let item = items[0]; - should.not.exist(item.items); - }); - - it('operations for tag should contain valid pointer and name', () => { - for (let item of items) { - item.should.be.an.Object(); - if (item.items) { - for (let subItem of item.items) { - subItem.should.have.properties(['metadata']); - let pointer = subItem.metadata.pointer; - let methSchema = specMgr.byPointer(pointer); - should.exist(methSchema); - if (methSchema.summary) { - methSchema.summary.should.be.equal(subItem.name); - } - } - } - } - }); - - it('should use x-displayName to set custom names', () => { - let info = items[2]; - info.id.should.be.equal('tag/tag4'); - info.name.should.be.equal('Tag Four'); - }); - }); -}); - -@Component({ - selector: 'test-app', - template: - `
- - -
` -}) -class TestAppComponent { -} diff --git a/lib/services/menu.service.ts b/lib/services/menu.service.ts deleted file mode 100644 index b0873b27..00000000 --- a/lib/services/menu.service.ts +++ /dev/null @@ -1,488 +0,0 @@ -'use strict'; -import { Injectable, EventEmitter } from '@angular/core'; -import { Subscription } from 'rxjs/Subscription'; -import { BehaviorSubject } from 'rxjs/BehaviorSubject'; -import { ScrollService, INVIEW_POSITION } from './scroll.service'; -import { WarningsService } from './warnings.service'; -import { Hash } from './hash.service'; -import { SpecManager } from '../utils/spec-manager'; -import { SchemaHelper } from './schema-helper.service'; -import { AppStateService } from './app-state.service'; -import { LazyTasksService } from '../shared/components/LazyFor/lazy-for'; -import { JsonPointer, MarkdownHeading, StringMap } from '../utils/'; -import * as slugify from 'slugify'; - - -const CHANGE = { - NEXT : 1, - BACK : -1, -}; - -export interface TagGroup { - name: string; - tags: string[]; -} - -export interface MenuItem { - id: string; - - name: string; - description?: string; - - items?: Array; - parent?: MenuItem; - - active?: boolean; - ready?: boolean; - - depth?: string|number; - flatIdx?: number; - - metadata?: any; - isGroup?: boolean; -} - -@Injectable() -export class MenuService { - changed: EventEmitter = new EventEmitter(); - changedActiveItem: EventEmitter = new EventEmitter(); - - items: MenuItem[]; - activeIdx: number = -1; - - public domRoot: Document | Element = document; - - private _flatItems: MenuItem[]; - private _hashSubscription: Subscription; - private _scrollSubscription: Subscription; - private _progressSubscription: Subscription; - private _tagsWithOperations: any; - - constructor( - private hash:Hash, - private tasks: LazyTasksService, - private scrollService: ScrollService, - private appState: AppStateService, - private specMgr:SpecManager - ) { - this.hash = hash; - - this.specMgr.spec.subscribe(spec => { - if (!spec) return; - this.buildMenu(); - }); - - this.subscribe(); - } - - subscribe() { - this._scrollSubscription = this.scrollService.scroll.subscribe((evt) => { - this.onScroll(evt.isScrolledDown); - }); - - this._hashSubscription = this.hash.value.subscribe((hash) => { - this.onHashChange(hash); - }); - - this._progressSubscription = this.tasks.loadProgress.subscribe(progress => { - if (progress === 100) { - this.makeSureLastItemsEnabled(); - } - }); - } - - get flatItems():MenuItem[] { - if (!this._flatItems) { - this._flatItems = this.flatMenu(); - } - return this._flatItems; - } - - enableItem(idx) { - let item = this.flatItems[idx]; - item.ready = true; - if (item.parent) { - item.parent.ready = true; - idx = item.parent.flatIdx; - } - - // check if previous items§ can be enabled - let prevItem = this.flatItems[idx -= 1]; - while(prevItem && (!prevItem.metadata || prevItem.metadata.type === 'heading' || !prevItem.items)) { - prevItem.ready = true; - prevItem = this.flatItems[idx -= 1]; - } - - this.changed.next(); - } - - makeSureLastItemsEnabled() { - let lastIdx = this.flatItems.length - 1; - let item = this.flatItems[lastIdx]; - while(item && (!item.metadata || !item.items)) { - item.ready = true; - item = this.flatItems[lastIdx -= 1]; - } - } - - onScroll(isScrolledDown) { - let stable = false; - while(!stable) { - if(isScrolledDown) { - let $nextEl = this.getEl(this.activeIdx + 1); - if (!$nextEl) return; - let nextInViewPos = this.scrollService.getElementPos($nextEl, true); - if (nextInViewPos === INVIEW_POSITION.ABOVE) { - stable = this.changeActive(CHANGE.NEXT); - continue; - } - } - let $currentEl = this.getCurrentEl(); - if (!$currentEl) return; - var elementInViewPos = this.scrollService.getElementPos($currentEl); - if(!isScrolledDown && elementInViewPos === INVIEW_POSITION.ABOVE ) { - stable = this.changeActive(CHANGE.BACK); - continue; - } - stable = true; - } - } - - onHashChange(hash?: string) { - if (hash == undefined) return; - let activated = this.activateByHash(hash); - if (!this.tasks.processed) { - this.tasks.start(this.activeIdx, this); - this.scrollService.setStickElement(this.getCurrentEl()); - if (activated) this.scrollToActive(); - this.appState.stopLoading(); - } else { - if (activated) this.scrollToActive(); - } - } - - getEl(flatIdx:number):Element { - if (flatIdx < 0) return null; - if (flatIdx > this.flatItems.length - 1) return null; - let currentItem = this.flatItems[flatIdx]; - if (!currentItem) return; - if (currentItem.isGroup) currentItem = this.flatItems[flatIdx + 1]; - - let selector = ''; - while(currentItem) { - if (currentItem.id) { - selector = `[section="${currentItem.id}"] ` + selector; - // We only need to go up the chain for operations that - // might have multiple tags. For headers/subheaders - // we need to siply early terminate. - if (!currentItem.metadata || currentItem.metadata.type === 'heading') { - break; - } - } - currentItem = currentItem.parent; - } - selector = selector.trim(); - return selector ? this.domRoot.querySelector(selector) : null; - } - - isTagOrGroupItem(flatIdx: number):boolean { - let item = this.flatItems[flatIdx]; - return item && (item.isGroup || (item.metadata && item.metadata.type === 'tag')); - } - - getTagInfoEl(flatIdx: number):Element { - if (!this.isTagOrGroupItem(flatIdx)) return null; - - let el = this.getEl(flatIdx); - return el && el.querySelector('.tag-info'); - } - - getCurrentEl():Element { - return this.getEl(this.activeIdx); - } - - deactivate(idx) { - if (idx < 0) return; - - let item = this.flatItems[idx]; - item.active = false; - while (item.parent) { - item.parent.active = false; - item = item.parent; - } - } - - activate(item:MenuItem, force = false, replaceState = false) { - if (!force && item && !item.ready) return; - - this.deactivate(this.activeIdx); - this.activeIdx = item ? item.flatIdx : -1; - if (this.activeIdx < 0) { - this.hash.update('', replaceState); - return; - } - - item.active = true; - - let cItem = item; - while (cItem.parent) { - cItem.parent.active = true; - cItem = cItem.parent; - } - this.hash.update(this.hashFor(item.id, item.metadata, item.parent && item.parent.id), replaceState); - this.changedActiveItem.next(item); - } - - activateByIdx(idx:number, force = false, replaceState = false) { - let item = this.flatItems[idx]; - this.activate(item, force, replaceState); - } - - changeActive(offset = 1):boolean { - let noChange = (this.activeIdx <= 0 && offset === -1) || - (this.activeIdx === this.flatItems.length - 1 && offset === 1); - this.activateByIdx(this.activeIdx + offset, false, true); - return noChange; - } - - scrollToActive() { - let $el = this.getCurrentEl(); - if ($el) this.scrollService.scrollTo($el); - } - - activateByHash(hash):boolean { - if (!hash) return; - let idx = 0; - hash = hash.substr(1); - let namespace = hash.split('/')[0]; - let ptr = decodeURIComponent(hash.substr(namespace.length + 1)); - if (namespace === 'section' || namespace === 'tag') { - let sectionId = ptr.split('/')[0]; - ptr = ptr.substr(sectionId.length) || null; - - let searchId; - if (namespace === 'section') { - searchId = hash; - } else { - searchId = ptr || (namespace + '/' + sectionId); - } - - idx = this.flatItems.findIndex(item => item.id === searchId); - if (idx < 0) { - this.tryScrollToId(searchId); - return false; - } - } else if (namespace === 'operation') { - idx = this.flatItems.findIndex(item => { - return item.metadata && item.metadata.operationId === ptr; - }); - } - this.activateByIdx(idx, true); - return idx >= 0; - } - - tryScrollToId(id) { - let $el = this.domRoot.querySelector(`[section="${id}"]`); - if ($el) this.scrollService.scrollTo($el); - } - - addMarkdownItems() { - let schema = this.specMgr.schema; - let headings:StringMap = schema.info && schema.info['x-redoc-markdown-headers'] || {}; - Object.keys(headings).forEach(h => { - let heading = headings[h]; - let id = 'section/' + heading.id; - let item = { - name: heading.title, - id: id, - items: null, - metadata: { - type: 'heading' - } - }; - item.items = this.getMarkdownSubheaders(item, heading); - - this.items.push(item); - }); - } - - getMarkdownSubheaders(parent: MenuItem, parentHeading: MarkdownHeading):MenuItem[] { - let res = []; - - Object.keys(parentHeading.children || {}).forEach(h => { - let heading = parentHeading.children[h]; - let id = 'section/' + heading.id; - - let subItem = { - name: heading.title, - id: id, - parent: parent, - metadata: { - type: 'heading' - } - }; - res.push(subItem); - }); - - return res; - } - - getOperationsItems(parent: MenuItem, tag:any):MenuItem[] { - if (!tag.operations || !tag.operations.length) return null; - - let res = []; - for (let operationInfo of tag.operations) { - let subItem = { - name: SchemaHelper.operationSummary(operationInfo), - id: operationInfo._pointer, - description: operationInfo.description, - metadata: { - type: 'operation', - pointer: operationInfo._pointer, - operationId: operationInfo.operationId, - operation: operationInfo.operation, - deprecated: !!operationInfo.deprecated - }, - parent: parent - }; - res.push(subItem); - } - return res; - } - - hashFor( - id: string|null, itemMeta: - {operationId?: string, type: string, pointer?: string}, - parentId?: string - ) { - if (!id) return null; - if (itemMeta && itemMeta.type === 'operation') { - if (itemMeta.operationId) { - return 'operation/' + encodeURIComponent(itemMeta.operationId); - } else { - return parentId + encodeURIComponent(itemMeta.pointer); - } - } else { - return id; - } - } - - getTagsItems(parent: MenuItem, tagGroup:TagGroup = null):MenuItem[] { - let schema = this.specMgr.schema; - - let tags; - if (!tagGroup) { - // all tags - tags = Object.keys(this._tagsWithOperations); - } else { - tags = tagGroup.tags; - } - - tags = tags.map(k => { - if (!this._tagsWithOperations[k]) { - WarningsService.warn(`Non-existing tag "${k}" is added to the group "${tagGroup.name}"`); - return null; - } - this._tagsWithOperations[k].used = true; - return this._tagsWithOperations[k]; - }); - - let res = []; - for (let tag of tags || []) { - if (!tag) continue; - let id = 'tag/' + slugify(tag.name); - let item: MenuItem; - - // don't put empty tag into menu, instead put their operations - if (tag.name === '') { - let items = this.getOperationsItems(null, tag); - res.push(...items); - continue; - } - - item = { - name: tag['x-displayName'] || tag.name, - id: id, - description: tag.description, - metadata: { type: 'tag', externalDocs: tag.externalDocs }, - parent: parent, - items: null - }; - item.items = this.getOperationsItems(item, tag); - - res.push(item); - } - return res; - } - - getTagGroupsItems(parent: MenuItem, groups: TagGroup[]):MenuItem[] { - let res = []; - for (let group of groups) { - let item; - item = { - name: group.name, - id: null, - description: '', - parent: parent, - isGroup: true, - items: null - }; - item.items = this.getTagsItems(item, group); - res.push(item); - } - this.checkAllTagsUsedInGroups(); - return res; - } - - checkAllTagsUsedInGroups() { - for (let tag of Object.keys(this._tagsWithOperations)) { - if (!this._tagsWithOperations[tag].used) { - WarningsService.warn(`Tag "${tag}" is not added to any group`); - } - } - } - - buildMenu() { - this._tagsWithOperations = SchemaHelper.getTagsWithOperations(this.specMgr.schema); - - this.items = this.items || []; - this.addMarkdownItems(); - if (this.specMgr.schema['x-tagGroups']) { - this.items.push(...this.getTagGroupsItems(null, this.specMgr.schema['x-tagGroups'])); - } else { - this.items.push(...this.getTagsItems(null)); - } - } - - flatMenu():MenuItem[] { - let menu = this.items; - if (!menu) return; - let res = []; - let curDepth = 1; - - let recursive = (items) => { - for (let item of items) { - res.push(item); - item.depth = item.isGroup ? 0 : curDepth; - item.flatIdx = res.length - 1; - if (item.items) { - if (!item.isGroup) curDepth++; - recursive(item.items); - if (!item.isGroup) curDepth--; - } - } - }; - recursive(menu); - return res; - } - - getItemById(id: string):MenuItem { - return this.flatItems.find(item => item.id === id || item.id === `section/${id}`); - } - - destroy() { - this._hashSubscription.unsubscribe(); - this._scrollSubscription.unsubscribe(); - this._progressSubscription.unsubscribe(); - } -} diff --git a/lib/services/options.service.spec.ts b/lib/services/options.service.spec.ts deleted file mode 100644 index 1b887f92..00000000 --- a/lib/services/options.service.spec.ts +++ /dev/null @@ -1,65 +0,0 @@ -'use strict'; - -import { OptionsService } from './options.service'; - -describe('Options Service', () => { - let tmpDiv; - let optionsService; - - function build(html) { - tmpDiv = document.createElement('div'); - tmpDiv.innerHTML = html; - document.body.appendChild(tmpDiv); - return tmpDiv.lastChild; - } - - afterEach(() => { - if (tmpDiv) document.body.removeChild(tmpDiv); - tmpDiv = false; - }); - - beforeEach(() => { - optionsService = new OptionsService(); - }); - - it('should parse numeric scrollYOffset', () => { - var elem = build(``); - optionsService.parseOptions(elem); - optionsService.options.scrollYOffset().should.be.equal(50); - }); - - it('should parse selector scrollYOffset', () => { - var elem = build(`
- `); - optionsService.parseOptions(elem); - optionsService.options.scrollYOffset().should.be.equal(50); - }); - - it('should return 0 for incorrect selector scrollYOffset', () => { - var elem = build(`
- `); - optionsService.parseOptions(elem); - optionsService.options.scrollYOffset().should.be.equal(0); - }); - - it('should handle function scrollYOffset', () => { - optionsService.options = { scrollYOffset: () => 123 }; - var elem = build(``); - optionsService.parseOptions(elem); - optionsService.options.scrollYOffset().should.be.equal(123); - }); - - it('should convert expandResponses options to Set', () => { - optionsService.options = { expandResponses: '200,300' }; - optionsService._normalizeOptions(); - optionsService.options.expandResponses.should.be.instanceof(Set); - Array.from(optionsService.options.expandResponses.values()).should.deepEqual(['200', '300']); - }); - - it('should preserve special value "all" as string', () => { - optionsService.options = { expandResponses: 'all' }; - optionsService._normalizeOptions(); - optionsService.options.expandResponses.should.be.of.type('string'); - optionsService.options.expandResponses.should.be.equal('all'); - }); -}); diff --git a/lib/services/options.service.ts b/lib/services/options.service.ts deleted file mode 100644 index 97d099d3..00000000 --- a/lib/services/options.service.ts +++ /dev/null @@ -1,122 +0,0 @@ -'use strict'; -import { Injectable } from '@angular/core'; -import { isFunction, isString } from '../utils/helpers'; -import { BrowserDomAdapter as DOM } from '../utils/browser-adapter'; - -const defaults = { - scrollYOffset: 0, - disableLazySchemas: false -}; - -const OPTION_NAMES = new Set([ - 'scrollYOffset', - 'disableLazySchemas', - 'specUrl', - 'suppressWarnings', - 'hideHostname', - 'lazyRendering', - 'expandResponses', - 'requiredPropsFirst', - 'noAutoAuth', - 'pathInMiddlePanel', - 'untrustedSpec', - 'hideLoading', - 'ignoredHeaderParameters', - 'nativeScrollbars', -]); - -export interface Options { - scrollYOffset?: any; - disableLazySchemas?: boolean; - specUrl?: string; - suppressWarnings?: boolean; - hideHostname?: boolean; - lazyRendering?: boolean; - expandResponses?: Set | 'all'; - $scrollParent?: HTMLElement | Window; - requiredPropsFirst?: boolean; - noAutoAuth?: boolean; - pathInMiddlePanel?: boolean; - untrustedSpec?: boolean; - hideLoading?: boolean; - spec?: any; - ignoredHeaderParameters?: string[]; - nativeScrollbars?: boolean; -} - -@Injectable() -export class OptionsService { - private _options: Options; - - constructor() { - this._options = defaults; - this._normalizeOptions(); - } - - get options(): Options { - return this._options; - } - - set options(opts:Options) { - this._options = Object.assign(this._options, opts); - } - - parseOptions(el:HTMLElement):void { - let parsedOpts; - let attributesMap = DOM.attributeMap(el); - parsedOpts = {}; - Array.from(attributesMap.keys()) - //camelCasify - .map(k => ({ - attrName: k, - name: k.replace(/-(.)/g, (_, $1) => $1.toUpperCase()) - }) - ) - .filter(option => OPTION_NAMES.has(option.name)) - .forEach(option => { - parsedOpts[option.name] = attributesMap.get(option.attrName); - }); - - this.options = parsedOpts; - this._normalizeOptions(); - } - - _normalizeOptions(): void { - // modify scrollYOffset to always be a function - if (!isFunction(this._options.scrollYOffset)) { - if (isFinite(this._options.scrollYOffset)) { - // if number specified create function that returns this value - let numberOffset = parseFloat(this._options.scrollYOffset); - this.options.scrollYOffset = () => numberOffset; - } else { - // if selector or node function that returns bottom offset of this node - let el = this._options.scrollYOffset; - if (!(el instanceof Node)) { - el = DOM.query(el); - } - if (!el) { - this._options.scrollYOffset = () => 0; - } else { - this._options.scrollYOffset = () => el.offsetTop + el.offsetHeight; - } - } - } - - if (isString(this._options.disableLazySchemas)) this._options.disableLazySchemas = true; - if (isString(this._options.suppressWarnings)) this._options.suppressWarnings = true; - if (isString(this._options.hideHostname)) this._options.hideHostname = true; - if (isString(this._options.lazyRendering)) this._options.lazyRendering = true; - if (isString(this._options.requiredPropsFirst)) this._options.requiredPropsFirst = true; - if (isString(this._options.noAutoAuth)) this._options.noAutoAuth = true; - if (isString(this._options.pathInMiddlePanel)) this._options.pathInMiddlePanel = true; - if (isString(this._options.untrustedSpec)) this._options.untrustedSpec = true; - if (isString(this._options.hideLoading)) this._options.hideLoading = true; - if (isString(this._options.nativeScrollbars)) - this._options.nativeScrollbars = true; - if (isString(this._options.expandResponses)) { - let str = this._options.expandResponses as string; - if (str === 'all') return; - this._options.expandResponses = new Set(str.split(',')); - } - } -} diff --git a/lib/services/schema-helper.service.spec.ts b/lib/services/schema-helper.service.spec.ts deleted file mode 100644 index 29554065..00000000 --- a/lib/services/schema-helper.service.spec.ts +++ /dev/null @@ -1,134 +0,0 @@ -'use strict'; -import { SchemaHelper } from './schema-helper.service'; -import { SpecManager } from '../utils/spec-manager'; - -describe('Spec Helper', () => { - describe('injectors', () => { - it('should autodetect type if not-specified', () => { - spyOn(console, 'warn').and.stub(); - let schema = { - type: undefined, - properties: {} - }; - - SchemaHelper.runInjectors(schema, schema, '#/'); - schema.type.should.be.equal('object'); - expect(console.warn).toHaveBeenCalled(); - (console.warn).and.callThrough(); - }); - - describe('string', () => { - it('should calculate range for string with maxLength', () => { - let schema:any = { - type: 'string', - maxLength: 3 - }; - - SchemaHelper.runInjectors(schema, schema, '#/'); - schema._range.should.be.equal('<= 3 characters'); - }); - - it('should calculate range for string with minLength', () => { - let schema:any = { - type: 'string', - minLength: 3, - }; - - SchemaHelper.runInjectors(schema, schema, '#/'); - schema._range.should.be.equal('>= 3 characters'); - }); - - it('should calculate range for string with both max and minLength', () => { - let schema:any = { - type: 'string', - minLength: 3, - maxLength: 5 - }; - - SchemaHelper.runInjectors(schema, schema, '#/'); - schema._range.should.be.equal('[ 3 .. 5 ] characters'); - }); - - it('should calculate range for string with equal max and minLength', () => { - let schema:any = { - type: 'string', - minLength: 5, - maxLength: 5 - }; - - SchemaHelper.runInjectors(schema, schema, '#/'); - schema._range.should.be.equal('5 characters'); - }); - - it('should show range as non-empty for minLength == 1', () => { - let schema:any = { - type: 'string', - minLength: 1 - }; - - SchemaHelper.runInjectors(schema, schema, '#/'); - schema._range.should.be.equal('non-empty'); - }); - }); - }); - - describe('preprocessProperties', () => { - it('should not throw when type array and items are not defined', () => { - let schema = { - type: 'object', - properties: { - prop1: { - type: 'array' - } - } - }; - - (() => SchemaHelper.preprocessProperties(schema, '#/', {})).should.not.throw(); - }); - }); - - describe('moveRequiredPropsFirst', () => { - it('should move required props to the top', () => { - let props = [{ - name: 'prop2', - type: 'string' - }, - { - name: 'prop1', - type: 'number', - _required: true - }]; - - let required = ['prop1']; - - SchemaHelper.moveRequiredPropsFirst(props, required); - props[0].name.should.be.equal('prop1'); - props[1].name.should.be.equal('prop2'); - }); - - it('should sort required props by the order or required', () => { - var props = [{ - name: 'prop2', - type: 'string' - }, - { - name: 'prop1', - type: 'number', - _required: true - }, - { - name: 'prop3', - type: 'number', - _required: true - } - ]; - - let required = ['prop3', 'prop1']; - - SchemaHelper.moveRequiredPropsFirst(props, required); - props[0].name.should.be.equal('prop3'); - props[1].name.should.be.equal('prop1'); - props[2].name.should.be.equal('prop2'); - }); - }); -}); diff --git a/lib/services/schema-helper.service.ts b/lib/services/schema-helper.service.ts deleted file mode 100644 index d8579d8b..00000000 --- a/lib/services/schema-helper.service.ts +++ /dev/null @@ -1,349 +0,0 @@ -'use strict'; -import { JsonPointer } from '../utils/JsonPointer'; -import { operations as swaggerOperations, keywordTypes } from '../utils/swagger-defs'; -import { WarningsService } from './warnings.service'; -import * as slugify from 'slugify'; - -export interface PropertyPreprocessOptions { - childFor?: string; - skipReadOnly?: boolean; - discriminator?: string; -} - -// global var for this module -var specMgrInstance; - -const injectors = { - notype: { - check: (propertySchema) => !propertySchema.type, - inject: (injectTo, propertySchema, pointer) => { - injectTo.type = SchemaHelper.detectType(propertySchema); - propertySchema.type = injectTo.type; - if (injectTo.type) { - let message = `No "type" specified at "${pointer}". Automatically detected: "${injectTo.type}"`; - WarningsService.warn(message); - } - } - }, - general: { - check: () => true, - inject: (injectTo, propertySchema, pointer) => { - injectTo._pointer = propertySchema._pointer || pointer; - injectTo._displayType = propertySchema.type; - if (propertySchema.format) injectTo._displayFormat = `<${propertySchema.format}>`; - if (propertySchema.enum) { - injectTo.enum = propertySchema.enum.map((value) => { - return {val: value, type: typeof value}; - }); - if (injectTo.enum && injectTo.enum.length === 1) { - injectTo._enumItem = injectTo.enum[0]; - injectTo.enum = null; - } - } - } - }, - discriminator: { - check: (propertySchema) => propertySchema.discriminator || propertySchema['x-extendedDiscriminator'], - inject: (injectTo, propertySchema = injectTo) => { - injectTo.discriminator = propertySchema.discriminator; - injectTo['x-extendedDiscriminator'] = propertySchema['x-extendedDiscriminator']; - } - }, - simpleArray: { - check: (propertySchema) => { - return propertySchema.type === 'array' && !Array.isArray(propertySchema.items); - }, - inject: (injectTo, propertySchema = injectTo, propPointer) => { - if (!propertySchema.items) propertySchema.items = {}; - if (!(SchemaHelper.detectType(propertySchema.items) === 'object')) { - injectTo._isArray = true; - injectTo._pointer = propertySchema.items._pointer - || JsonPointer.join(propertySchema._pointer || propPointer, ['items']); - - SchemaHelper.runInjectors(injectTo, propertySchema.items, propPointer); - } else { - injectors.object.inject(injectTo, propertySchema.items); - } - if (!injectTo.description) injectTo.description = propertySchema.items.description; - injectTo._widgetType = 'array'; - } - }, - tuple: { - check: (propertySchema) => { - return propertySchema.type === 'array' && Array.isArray(propertySchema.items); - }, - inject: (injectTo, propertySchema = injectTo, propPointer) => { - injectTo._isTuple = true; - injectTo._displayType = ''; - let itemsPtr = JsonPointer.join(propertySchema._pointer || propPointer, ['items']); - for (let i=0; i < propertySchema.items.length; i++) { - let itemSchema = propertySchema.items[i]; - itemSchema._pointer = itemSchema._pointer || JsonPointer.join(itemsPtr, [i.toString()]); - } - injectTo._widgetType = 'tuple'; - } - }, - object: { - check: (propertySchema) => { - return propertySchema.type === 'object' && (propertySchema.properties || - typeof propertySchema.additionalProperties === 'object'); - }, - inject: (injectTo, propertySchema = injectTo) => { - let baseName = propertySchema._pointer && JsonPointer.baseName(propertySchema._pointer); - injectTo._displayType = propertySchema.title || baseName || 'object'; - injectTo._widgetType = 'object'; - } - }, - noType: { - check: (propertySchema) => !propertySchema.type, - inject: (injectTo) => { - injectTo._displayType = '< anything >'; - injectTo._displayTypeHint = 'This field may contain data of any type'; - injectTo.isTrivial = true; - injectTo._widgetType = 'trivial'; - injectTo._pointer = undefined; - } - }, - simpleType: { - check: (propertySchema) => { - if (propertySchema.type === 'object') { - return (!propertySchema.properties || !Object.keys(propertySchema.properties).length) - && (typeof propertySchema.additionalProperties !== 'object'); - } - return (propertySchema.type !== 'array') && propertySchema.type; - }, - inject: (injectTo, propertySchema = injectTo) => { - injectTo.isTrivial = true; - if (injectTo._pointer) { - injectTo._pointer = undefined; - injectTo._displayType = propertySchema.title ? - `${propertySchema.title} (${propertySchema.type})` : propertySchema.type; - } - if (injectTo['x-example'] && !propertySchema.example) { - injectTo.example = propertySchema['x-example']; - } - injectTo._widgetType = 'trivial'; - } - }, - integer: { - check: (propertySchema) => (propertySchema.type === 'integer' || propertySchema.type === 'number'), - inject: (injectTo, propertySchema = injectTo) => { - var range = ''; - if (propertySchema.minimum != undefined && propertySchema.maximum != undefined) { - range += propertySchema.exclusiveMinimum ? '( ' : '[ '; - range += propertySchema.minimum; - range += ' .. '; - range += propertySchema.maximum; - range += propertySchema.exclusiveMaximum ? ' )' : ' ]'; - } else if (propertySchema.maximum != undefined) { - range += propertySchema.exclusiveMaximum? '< ' : '<= '; - range += propertySchema.maximum; - } else if (propertySchema.minimum != undefined) { - range += propertySchema.exclusiveMinimum ? '> ' : '>= '; - range += propertySchema.minimum; - } - - if (range) { - injectTo._range = range; - } - } - }, - string: { - check: propertySchema => (propertySchema.type === 'string'), - inject: (injectTo, propertySchema = injectTo) => { - var range; - if (propertySchema.minLength != undefined && propertySchema.maxLength != undefined) { - if (propertySchema.minLength === propertySchema.maxLength) { - range = `${propertySchema.minLength} characters`; - } else { - range = `[ ${propertySchema.minLength} .. ${propertySchema.maxLength} ] characters`; - } - } else if (propertySchema.maxLength != undefined) { - range = `<= ${propertySchema.maxLength} characters`; - } else if (propertySchema.minLength != undefined) { - if (propertySchema.minLength === 1) { - range = 'non-empty'; - } else { - range = `>= ${propertySchema.minLength} characters`; - } - } - - injectTo._range = range; - } - }, - file: { - check: propertySchema => (propertySchema.type === 'file'), - inject: (injectTo, propertySchema = injectTo, _, hostPointer) => { - injectTo.isFile = true; - let parentPtr; - if (propertySchema.in === 'formData') { - parentPtr = JsonPointer.dirName(hostPointer, 1); - } else { - parentPtr = JsonPointer.dirName(hostPointer, 3); - } - - let parentParam = specMgrInstance.byPointer(parentPtr); - let root =specMgrInstance.schema; - injectTo._produces = parentParam && parentParam.produces || root.produces; - injectTo._consumes = parentParam && parentParam.consumes || root.consumes; - injectTo._widgetType = 'file'; - } - } -}; - -export class SchemaHelper { - static setSpecManager(specMgr) { - specMgrInstance = specMgr; - } - - static preprocess(schema, pointer, hostPointer?) { - //propertySchema = Object.assign({}, propertySchema); - if (schema['x-redoc-schema-precompiled']) { - return schema; - } - SchemaHelper.runInjectors(schema, schema, pointer, hostPointer); - schema['x-redoc-schema-precompiled'] = true; - return schema; - } - - static runInjectors(injectTo, schema, pointer, hostPointer?) { - for (var injName of Object.keys(injectors)) { - let injector = injectors[injName]; - if (injector.check(schema)) { - injector.inject(injectTo, schema, pointer, hostPointer); - } - } - } - - static preprocessProperties(schema:any, pointer:string, opts: PropertyPreprocessOptions) { - let requiredMap = {}; - if (schema.required) { - if (Array.isArray(schema.required)) { - schema.required.forEach(prop => requiredMap[prop] = true); - } else { - WarningsService.warn(`required must be an array: "${typeof schema.required}" found at ${pointer}`); - } - } - - let props = schema.properties && Object.keys(schema.properties).map(propName => { - let propertySchema = Object.assign({}, schema.properties[propName]); - let propPointer = propertySchema._pointer || - JsonPointer.join(pointer, ['properties', propName]); - propertySchema = SchemaHelper.preprocess(propertySchema, propPointer); - propertySchema.name = propName; - // stop endless discriminator recursion - if (propertySchema._pointer === opts.childFor) { - propertySchema._pointer = null; - } - propertySchema._required = !!requiredMap[propName]; - propertySchema.isDiscriminator = opts.discriminator === propName; - return propertySchema; - }); - - props = props || []; - - if (schema.additionalProperties && (typeof schema.additionalProperties === 'object')) { - let propsSchema = SchemaHelper.preprocessAdditionalProperties(schema, pointer); - propsSchema._additional = true; - props.push(propsSchema); - } - - // filter readOnly props for request schemas - if (opts.skipReadOnly) { - props = props.filter(prop => !prop.readOnly); - } - schema._properties = props; - } - - static preprocessAdditionalProperties(schema:any, pointer:string) { - var addProps = schema.additionalProperties; - let ptr = addProps._pointer || JsonPointer.join(pointer, ['additionalProperties']); - let res = SchemaHelper.preprocess(addProps, ptr); - res.name = ' *'; - return res; - } - - static unwrapArray(schema, pointer) { - var res = schema; - if (schema && schema.type === 'array' && !Array.isArray(schema.items)) { - let items = schema.items = schema.items || {}; - let ptr = items._pointer || JsonPointer.join(pointer, ['items']); - res = Object.assign({}, items); - res._isArray = true; - res._pointer = ptr; - res = SchemaHelper.unwrapArray(res, ptr); - } - return res; - } - - static operationSummary(operation) { - return operation.summary || operation.operationId || - (operation.description && operation.description.substring(0, 50)) || ''; - } - - static detectType(schema) { - if (schema.type) return schema.type; - let keywords = Object.keys(keywordTypes); - for (var i=0; i < keywords.length; i++) { - let keyword = keywords[i]; - let type = keywordTypes[keyword]; - if (schema[keyword]) { - return type; - } - } - } - - static getTagsWithOperations(schema) { - let tags = {}; - for (let tag of schema.tags || []) { - tags[tag.name] = tag; - tag.operations = []; - } - - let paths = schema.paths; - for (let path of Object.keys(paths)) { - let operations = Object.keys(paths[path]).filter((k) => swaggerOperations.has(k)); - for (let operation of operations) { - let operationInfo = paths[path][operation]; - let operationTags = operationInfo.tags; - - // empty tag - if (!(operationTags && operationTags.length)) { - operationTags = ['']; - } - let operationPointer = JsonPointer.compile(['paths', path, operation]); - for (let tagName of operationTags) { - let tag = tags[tagName]; - if (!tag) { - tag = { - name: tagName, - }; - tags[tagName] = tag; - } - if (tag['x-traitTag']) continue; - if (!tag.operations) tag.operations = []; - tag.operations.push(operationInfo); - operationInfo._pointer = operationPointer; - operationInfo.operation = operation; - } - } - } - - return tags; - } - - static moveRequiredPropsFirst(properties: any[], _required: string[]|null) { - let required = _required || []; - properties.sort((a, b) => { - if ((!a._required && b._required)) { - return 1; - } else if (a._required && !b._required) { - return -1; - } else if (a._required && b._required) { - return required.indexOf(a.name) > required.indexOf(b.name) ? 1 : -1; - } else { - return 0; - } - }); - } -} diff --git a/lib/services/schema-normalizer.service.spec.ts b/lib/services/schema-normalizer.service.spec.ts deleted file mode 100644 index 57c64a0e..00000000 --- a/lib/services/schema-normalizer.service.spec.ts +++ /dev/null @@ -1,257 +0,0 @@ -'use strict'; -import { SchemaNormalizer } from './schema-normalizer.service'; -import { SpecManager } from '../utils/spec-manager'; -import { OptionsService } from '../services/options.service'; - - -describe('Spec Helper', () => { - let specMgr:SpecManager = new SpecManager(new OptionsService()); - let normalizer = new SchemaNormalizer(specMgr); - - describe('Dereference', () => { - beforeAll(done => { - specMgr.load('/tests/schemas/base-component-dereference.json').then( - () => done() - ); - }); - - describe('simple dereference', () => { - let resolved; - let pointer; - beforeAll(() => { - pointer = '/paths/test1/get/parameters/0'; - resolved = normalizer.normalize(specMgr.byPointer(pointer), pointer); - }); - - it('should not contain $ref property', () => { - expect(resolved.$ref).toBeUndefined(); - }); - - it('should inject Title if not exist based on reference', () => { - resolved.title.should.be.equal('Simple'); - }); - - it('should inject pointer', () => { - resolved._pointer.should.be.equal('#/definitions/Simple'); - }); - - it('should insert correct definition instead of reference', () => { - delete resolved.title; - delete resolved._pointer; - resolved.should.be.deepEqual(specMgr.schema.definitions.Simple); - }); - }); - - describe('nested dereference', () => { - let resolved; - beforeAll(() => { - let pointer = '/paths/test2/get/parameters/0'; - resolved = normalizer.normalize(specMgr.byPointer(pointer), pointer); - }); - - it('should not touch title if exist', () => { - resolved.title.should.be.equal('NesteTitle'); - }); - - it('should resolve nested schema', () => { - expect(resolved.properties.subschema.$ref).toBeUndefined(); - resolved._pointer.should.be.equal('#/definitions/Nested'); - resolved.properties.subschema._pointer.should.be.equal('#/definitions/Simple'); - resolved.properties.subschema.type.should.be.equal('object'); - }); - }); - - describe('array schema dereference', () => { - let resolved; - beforeAll(() => { - let pointer = '/paths/test3/get/parameters/0'; - resolved = normalizer.normalize(specMgr.byPointer(pointer), pointer); - }); - - it('should resolve array schema', () => { - expect(resolved.$ref).toBeUndefined(); - expect(resolved.items.$ref).toBeUndefined(); - resolved.type.should.be.equal('array'); - resolved._pointer.should.be.equal('#/definitions/ArrayOfSimple'); - resolved.items._pointer.should.be.equal('#/definitions/Simple'); - resolved.items.type.should.be.equal('object'); - }); - }); - - describe('circular dereference', () => { - let resolved; - beforeAll(() => { - let pointer = '/paths/test4/get/parameters/0'; - resolved = normalizer.normalize(specMgr.byPointer(pointer), pointer); - }); - - it('should resolve circular schema', () => { - expect(resolved.$ref).toBeUndefined(); - expect(resolved.items.$ref).toBeUndefined(); - resolved.type.should.be.equal('array'); - resolved._pointer.should.be.equal('#/definitions/Circular'); - }); - - it('should remove _pointer when detect circularity', () => { - expect(resolved.items._pointer).toBeUndefined(); - resolved.items.title.should.be.equal('Circular'); - }); - - it('should resolve transitive circular ref', () => { - let pointer = '/paths/test6/get/parameters/0'; - resolved = normalizer.normalize(specMgr.byPointer(pointer), pointer); - expect(resolved.additionalProperties.$ref).toBeUndefined(); - expect(resolved.additionalProperties.items.additionalProperties.$ref).toBeUndefined(); - resolved.additionalProperties.type.should.be.equal('array'); - resolved.additionalProperties._pointer.should.be.equal('#/definitions/CircularTransitive2'); - expect(resolved.additionalProperties.items.additionalProperties._pointer).toBeUndefined(); - resolved.additionalProperties.items.additionalProperties.title.should.be.equal('CircularTransitive'); - }); - }); - - describe('$ref with other fields on the same level', () => { - let resolved; - beforeAll(() => { - let pointer = '/paths/test5/get/parameters/0'; - spyOn(console, 'warn').and.stub(); - resolved = normalizer.normalize(specMgr.byPointer(pointer), pointer); - }); - - afterAll(() => { - (console.warn).and.callThrough(); - }); - - it('should print warning to console', () => { - expect(console.warn).toHaveBeenCalled(); - }); - - it('should skip other fields', () => { - expect(resolved.$ref).toBeUndefined(); - expect(resolved.title).toBeDefined(); - resolved.title.should.be.equal('Simple'); - }); - - it('should preserve description field', () => { - expect(resolved.$ref).toBeUndefined(); - expect(resolved.description).toBeDefined(); - resolved.description.should.be.equal('test'); - }); - }); - }); - - describe('mergeAllOf', () => { - beforeAll((done) => { - specMgr.load('tests/schemas/base-component-joinallof.json').then(() => done()); - }); - - describe('Simple allOf merge', () => { - let joined; - beforeAll(() => { - let pointer = '/definitions/SimpleAllOf'; - joined = normalizer.normalize(specMgr.byPointer(pointer), pointer); - }); - - it('should remove $allOf field', () => { - expect(joined.allOf).toBeNull(); - }); - - it('should set type object', () => { - joined.type.should.be.equal('object'); - }); - - it('should merge properties', () => { - Object.keys(joined.properties).length.should.be.equal(3); - Object.keys(joined.properties).should.be.deepEqual(['prop1', 'prop2', 'prop3']); - }); - - it('should merge required', () => { - joined.required.length.should.be.equal(2); - joined.required.should.be.deepEqual(['prop1', 'prop3']); - }); - }); - - describe('AllOf with refrence', () => { - let joined; - beforeAll(() => { - let pointer = '/definitions/AllOfWithRef'; - joined = normalizer.normalize(specMgr.byPointer(pointer), pointer); - }); - - it('should remove $allOf field', () => { - expect(joined.allOf).toBeNull(); - }); - - it('should set type object', () => { - joined.type.should.be.equal('object'); - }); - - it('should merge properties', () => { - Object.keys(joined.properties).length.should.be.equal(2); - Object.keys(joined.properties).should.be.deepEqual(['id', 'prop3']); - }); - - it('should merge required', () => { - joined.required.length.should.be.equal(2); - joined.required.should.be.deepEqual(['id', 'prop3']); - }); - }); - - describe('AllOf with other properties on the allOf level', () => { - let joined; - beforeAll(() => { - let pointer = '/definitions/AllOfWithOther'; - joined = normalizer.normalize(specMgr.byPointer(pointer), pointer); - }); - - it('should remove $allOf field', () => { - expect(joined.allOf).toBeNull(); - }); - - it('should set type object', () => { - joined.type.should.be.equal('object'); - }); - - it('should merge properties', () => { - Object.keys(joined.properties).length.should.be.equal(1); - Object.keys(joined.properties).should.be.deepEqual(['id']); - }); - - it('should merge required', () => { - joined.required.length.should.be.equal(1); - joined.required.should.be.deepEqual(['id']); - }); - - it('should preserve parent properties', () => { - joined.description.should.be.equal('Test'); - joined.readOnly.should.be.equal(true); - }); - }); - - describe('allOf edgecases', () => { - it('should merge properties and required when defined on allOf level', () => { - let pointer = '/definitions/PropertiesOnAllOfLevel'; - let joined; - (() => joined = normalizer.normalize(specMgr.byPointer(pointer), pointer)).should.not.throw(); - Object.keys(joined.properties).length.should.be.equal(3); - }); - - it('should throw when merging schemas with different types', () => { - let pointer = '/definitions/BadAllOf1'; - (() => normalizer.normalize(specMgr.byPointer(pointer), pointer)).should.throw(); - }); - - it('should handle nested allOF', () => { - let pointer = '/definitions/NestedAllOf'; - let joined; - (() => joined = normalizer.normalize(specMgr.byPointer(pointer), pointer)).should.not.throw(); - Object.keys(joined.properties).length.should.be.equal(4); - Object.keys(joined.properties).should.be.deepEqual(['prop1', 'prop2', 'prop3', 'prop4']); - joined.required.should.be.deepEqual(['prop1', 'prop3']); - }); - }); - - xdescribe('Merge array allOf', () => { - //emtpy - }); - }); -}); diff --git a/lib/services/schema-normalizer.service.ts b/lib/services/schema-normalizer.service.ts deleted file mode 100644 index f599a73a..00000000 --- a/lib/services/schema-normalizer.service.ts +++ /dev/null @@ -1,229 +0,0 @@ -'use strict'; -import { Injectable } from '@angular/core'; -import { SpecManager } from '../utils/spec-manager'; -import { JsonPointer } from '../utils/JsonPointer'; -import { defaults } from '../utils/helpers'; -import { WarningsService } from './warnings.service'; - -export interface Reference { - $ref: string; - description: string; -} - -export interface Schema { - properties: any; - allOf: any; - items: any; - additionalProperties: any; -} - -export class SchemaNormalizer { - _dereferencer:SchemaDereferencer; - constructor(_schema:any) { - this._dereferencer = new SchemaDereferencer(_schema, this); - } - normalize(schema, ptr, opts:any ={}) { - let hasPtr = !!schema.$ref; - if (opts.resolved && !hasPtr) this._dereferencer.visit(ptr); - - if (opts.childFor) this._dereferencer.visit(opts.childFor); - if (schema['x-redoc-normalized']) return schema; - let res = SchemaWalker.walk(schema, ptr, (subSchema, ptr) => { - let resolved = this._dereferencer.dereference(subSchema, ptr); - if (resolved.allOf) { - resolved._pointer = resolved._pointer || ptr; - resolved = Object.assign({}, resolved); - AllOfMerger.merge(resolved, resolved.allOf); - } - return resolved; - }); - if (opts.resolved && !hasPtr) this._dereferencer.exit(ptr); - if (opts.childFor) this._dereferencer.exit(opts.childFor); - res['x-redoc-normalized'] = true; - return res; - } - - reset() { - this._dereferencer.reset(); - } -} - -class SchemaWalker { - static walk(obj:Schema, pointer:string, visitor:Function) { - if (obj == undefined || typeof(obj) !== 'object') { - return; - } - if (obj.properties) { - let ptr = JsonPointer.join(pointer, ['properties']); - SchemaWalker.walkEach(obj.properties, ptr, visitor); - } - - if (obj.additionalProperties) { - let ptr = JsonPointer.join(pointer, ['additionalProperties']); - if (Array.isArray(obj.additionalProperties)) { - SchemaWalker.walkEach(obj.additionalProperties, ptr, visitor); - } else { - let res = SchemaWalker.walk(obj.additionalProperties, ptr, visitor); - if (res) obj.additionalProperties = res; - } - } - - if (obj.allOf) { - let ptr = JsonPointer.join(pointer, ['allOf']); - SchemaWalker.walkEach(obj.allOf, ptr, visitor); - } - - if (obj.items) { - let ptr = JsonPointer.join(pointer, ['items']); - if (Array.isArray(obj.items)) { - SchemaWalker.walkEach(obj.items, ptr, visitor); - } else { - let res = SchemaWalker.walk(obj.items, ptr, visitor); - if (res) obj.items = res; - } - } - - return visitor(obj, pointer); - } - - private static walkEach(obj:Object, pointer:string, visitor:Function) { - for(let key of Object.keys(obj)) { - let ptr = JsonPointer.join(pointer, [key]); - let res = SchemaWalker.walk(obj[key], ptr, visitor); - if (res) obj[key] = res; - } - } -} - -export class AllOfMerger { - static merge(into, schemas) { - into['x-derived-from'] = []; - let hadDiscriminator = !!into.discriminator; - for (let i=0; i < schemas.length; i++) { - let subSchema = schemas[i]; - into['x-derived-from'].push(subSchema._pointer); - - AllOfMerger.checkCanMerge(subSchema, into); - - into.type = into.type || subSchema.type; - if (into.type === 'object') { - AllOfMerger.mergeObject(into, subSchema, i); - } - // don't merge _pointer - let tmpPtr = subSchema._pointer; - subSchema._pointer = null; - defaults(into, subSchema); - subSchema._pointer = tmpPtr; - } - if (!hadDiscriminator) into.discriminator = null; - into.allOf = null; - } - - private static mergeObject(into, subSchema, allOfNumber) { - if (subSchema.properties) { - into.properties = Object.assign({}, into.properties || {}); - Object.assign(into.properties, subSchema.properties); - Object.keys(subSchema.properties).forEach(propName => { - let prop = subSchema.properties[propName]; - if (!prop._pointer) { - let schemaPtr = subSchema._pointer || JsonPointer.join(into._pointer, ['allOf', allOfNumber]); - prop._pointer = prop._pointer || JsonPointer.join(schemaPtr, ['properties', propName]); - } - }); - } - if (subSchema.required) { - if (!into.required) into.required = []; - into.required.push(...subSchema.required); - } - } - - private static checkCanMerge(subSchema, into) { - // TODO: add support for merge array schemas - if (typeof subSchema !== 'object') { - let errMessage = `Items of allOf should be Object: ${typeof subSchema} found ` + - `${subSchema} at "#${into._pointer}"`; - throw new Error(errMessage); - } - - if (into.type && subSchema.type && into.type !== subSchema.type) { - let errMessage = `allOf merging error: schemas with different types can't be merged: ` + - `"${into.type}" and "${subSchema.type}" at "#${into._pointer}"`; - throw new Error(errMessage); - } - - if (into.type === 'array') { - WarningsService.warn('allOf: subschemas with type "array" are not supported yet'); - } - // TODO: add check if can be merged correctly (no different properties with the same name) - // TODO: merge properties - } -} - -class RefCounter { - private _counter = {}; - - reset():void { - this._counter = {}; - } - - visit(ref:string):void { - this._counter[ref] = this._counter[ref] ? this._counter[ref] + 1 : 1; - } - - exit(ref:string):void { - this._counter[ref] = this._counter[ref] && this._counter[ref] - 1; - } - - visited(ref:string):boolean { - return !!this._counter[ref]; - } -} - - -export class SchemaDereferencer { - private _refCouner = new RefCounter(); - - constructor(private _spec: SpecManager, private normalizator: SchemaNormalizer) { - } - reset() { - this._refCouner.reset(); - } - - visit($ref) { - this._refCouner.visit($ref); - } - - exit($ref) { - this._refCouner.exit($ref); - } - - dereference(schema: Reference, pointer:string):any { - if (!schema || !schema.$ref) return schema; - let $ref = schema.$ref; - let resolved = this._spec.byPointer($ref); - if (!this._refCouner.visited($ref)) { - resolved._pointer = $ref; - } else { - // for circular referenced save only title and type - resolved = { - title: resolved.title, - type: resolved.type - }; - } - this._refCouner.visit($ref); - // if resolved schema doesn't have title use name from ref - resolved.title = resolved.title || JsonPointer.baseName($ref); - - let keysCount = Object.keys(schema).filter(key => !key.startsWith('x-redoc')).length; - - if ( keysCount > 2 || (keysCount === 2 && !schema.description) ) { - WarningsService.warn(`Other properties are defined at the same level as $ref at "#${pointer}". ` + - 'They are IGNORED according to the JsonSchema spec'); - resolved.description = resolved.description || schema.description; - } - - resolved = this.normalizator.normalize(resolved, $ref); - this._refCouner.exit($ref); - return resolved; - } -} diff --git a/lib/services/scroll.service.ts b/lib/services/scroll.service.ts deleted file mode 100644 index 94cbdd06..00000000 --- a/lib/services/scroll.service.ts +++ /dev/null @@ -1,106 +0,0 @@ -'use strict'; -import { Injectable, EventEmitter } from '@angular/core'; -import { BrowserDomAdapter as DOM } from '../utils/browser-adapter'; -import { OptionsService } from './options.service'; -import { throttle } from '../utils/helpers'; - -export const INVIEW_POSITION = { - ABOVE : 1, - BELLOW: -1, - INVIEW: 0 -}; - -@Injectable() -export class ScrollService { - scrollYOffset: any; - $scrollParent: any; - scroll = new EventEmitter(); - private prevOffsetY: number; - private _cancel:any; - private _savedPosition:number; - private _stickElement: HTMLElement; - constructor(optionsService:OptionsService) { - this.scrollYOffset = () => optionsService.options.scrollYOffset(); - this.$scrollParent = optionsService.options.$scrollParent || window; - this.scroll = new EventEmitter(); - this.bind(); - if ('scrollRestoration' in history) { - history.scrollRestoration = 'manual'; - } - } - - scrollY() { - return (this.$scrollParent.pageYOffset != undefined) ? this.$scrollParent.pageYOffset : this.$scrollParent.scrollTop; - } - - /* returns 1 if element if above the view, 0 if in view and -1 below the view */ - getElementPos($el, inverted=false) { - let scrollYOffset = this.scrollYOffset(); - let mul = inverted ? -1 : 1; - if (mul*Math.floor($el.getBoundingClientRect().top) > mul*scrollYOffset) { - return INVIEW_POSITION.ABOVE; - } - - if (mul*$el.getBoundingClientRect().bottom <= mul*scrollYOffset) { - return INVIEW_POSITION.BELLOW; - } - return INVIEW_POSITION.INVIEW; - } - - scrollToPos(posY: number) { - if (this.$scrollParent.scrollTo) { - this.$scrollParent.scrollTo(0, Math.floor(posY)); - } else { - this.$scrollParent.scrollTop = posY; - } - } - scrollTo($el, offset:number = 0) { - if (!$el) return; - // TODO: rewrite this to use offsetTop as more reliable solution - let subjRect = $el.getBoundingClientRect(); - let posY = this.scrollY() + subjRect.top - this.scrollYOffset() + offset + 1; - this.scrollToPos(posY); - return posY; - } - - saveScroll() { - let $el = this._stickElement; - if (!$el) return; - let offsetParent = $el.offsetParent; - this._savedPosition = $el.offsetTop + (offsetParent).offsetTop; - } - - setStickElement($el) { - this._stickElement = $el; - } - - restoreScroll() { - let $el = this._stickElement; - if (!$el) return; - let offsetParent = $el.offsetParent; - let currentPosition = $el.offsetTop + (offsetParent).offsetTop; - let newY = this.scrollY() + (currentPosition - this._savedPosition); - this.scrollToPos(newY); - } - - relativeScrollPos($el) { - let subjRect = $el.getBoundingClientRect(); - return -subjRect.top + this.scrollYOffset() - 1; - } - - scrollHandler(evt) { - let isScrolledDown = (this.scrollY() - this.prevOffsetY > 0); - this.prevOffsetY = this.scrollY(); - this.scroll.next({isScrolledDown, evt}); - } - - bind() { - this.prevOffsetY = this.scrollY(); - this._cancel = DOM.onAndCancel(this.$scrollParent, 'scroll', - throttle((evt) => { this.scrollHandler(evt); }, 100, this)); - } - - unbind() { - this._cancel(); - } -} diff --git a/lib/services/search.service.ts b/lib/services/search.service.ts deleted file mode 100644 index 92d105cf..00000000 --- a/lib/services/search.service.ts +++ /dev/null @@ -1,232 +0,0 @@ -import { Injectable } from '@angular/core'; -import { AppStateService } from './app-state.service'; -import { SchemaNormalizer } from './schema-normalizer.service'; -import { JsonPointer, groupBy, SpecManager, StringMap, snapshot, MarkdownHeading } from '../utils/'; -import { operations as swaggerOperations } from '../utils/swagger-defs'; -import * as slugify from 'slugify'; - -import { - SwaggerSpec, - SwaggerOperation, - SwaggerSchema, - SwaggerBodyParameter, - SwaggerResponse -} from '../utils/swagger-typings'; - -import * as lunr from 'lunr'; - -export interface IndexElement { - menuId: string; - title: string; - body: string; - pointer: string; -} - -const index = lunr(function () { - this.field('title', {boost: 1.5}); - this.field('body'); - this.ref('pointer'); -}); - -const store:StringMap = {}; - -@Injectable() -export class SearchService { - normalizer: SchemaNormalizer; - constructor(private app: AppStateService, private spec: SpecManager) { - this.normalizer = new SchemaNormalizer(spec); - } - - ensureSearchVisible(containingPointers: string|null[]) { - this.app.searchContainingPointers.next(containingPointers); - } - - indexAll() { - console.time('Indexing'); - this.indexPaths(this.spec.schema); - this.indexTags(this.spec.schema); - this.indexDescriptionHeadings(this.spec.schema.info['x-redoc-markdown-headers']); - console.time('Indexing end'); - } - - search(q):StringMap { - var items = {}; - const res:IndexElement[] = index.search(q).map(res => { - items[res.menuId] = res; - return store[res.ref]; - }); - const grouped = groupBy(res, 'menuId'); - return grouped; - } - - index(element: IndexElement) { - // don't reindex same pointers (for discriminator) - if (store[element.pointer]) return; - index.add(element); - store[element.pointer] = element; - } - - indexDescriptionHeadings(headings:StringMap) { - if (!headings) return; - Object.keys(headings).forEach(k => { - let heading = headings[k]; - this.index({ - menuId: heading.id, - title: heading.title, - body: heading.content, - pointer: '/heading/' + heading.id - }); - - this.indexDescriptionHeadings(heading.children); - }); - } - - indexTags(swagger:SwaggerSpec) { - let tags = swagger.tags; - if (!tags) return; - for (let tag of tags) { - if (tag['x-traitTag']) continue; - let id = `tag/${slugify(tag.name)}`; - this.index({ - menuId: id, - title: tag.name, - body: tag.description, - pointer: id - }); - } - } - - indexPaths(swagger:SwaggerSpec) { - const paths = swagger.paths; - const basePtr = '#/paths'; - Object.keys(paths).forEach(path => { - let opearations = paths[path]; - Object.keys(opearations).forEach(verb => { - if (!swaggerOperations.has(verb)) return; - const opearation = opearations[verb]; - const ptr = JsonPointer.join(basePtr, [path, verb]); - - this.indexOperation(opearation, ptr); - }); - }); - } - - indexOperation(operation:SwaggerOperation, operationPointer:string) { - this.index({ - pointer: operationPointer, - menuId: operationPointer, - title: operation.summary, - body: operation.description - }); - this.indexOperationResponses(operation, operationPointer); - this.indexOperationParameters(operation, operationPointer); - } - - indexOperationParameters(operation: SwaggerOperation, operationPointer: string) { - const parameters = this.spec.getOperationParams(operationPointer); - if (!parameters) return; - for (let i=0; iparam).schema, - '', JsonPointer.join(paramPointer, ['schema']), operationPointer); - } - } - } - - indexOperationResponses(operation:SwaggerOperation, operationPtr:string) { - const responses = operation.responses; - if (!responses) return; - Object.keys(responses).forEach(code => { - const resp = responses[code]; - const respPtr = JsonPointer.join(operationPtr, ['responses', code]); - this.index({ - pointer: respPtr, - menuId: operationPtr, - title: code, - body: resp.description - }); - - if (resp.schema) { - this.normalizer.reset(); - this.indexSchema(resp.schema, '', JsonPointer.join(respPtr, 'schema'), operationPtr); - } - if (resp.headers) { - this.indexOperationResponseHeaders(resp, respPtr, operationPtr); - } - }); - } - - indexOperationResponseHeaders(response: SwaggerResponse, responsePtr: string, operationPtr: string, ) { - let headers = response.headers || []; - Object.keys(headers).forEach(headerName => { - let header = headers[headerName]; - this.index({ - pointer: `${responsePtr}/${headerName}`, - menuId: operationPtr, - title: headerName, - body: header.description - }); - }); - } - - indexSchema(_schema:SwaggerSchema, name: string, absolutePointer: string, - menuPointer: string, parent?: string) { - if (!_schema) return; - let schema = _schema; - let title = name; - schema = this.normalizer.normalize(schema, schema._pointer || absolutePointer, { childFor: parent }); - - // prevent endless discriminator recursion - if (schema._pointer && schema._pointer === parent) return; - - let body = schema.description; // TODO: defaults, examples, etc... - - if (schema.type === 'array') { - if (Array.isArray(schema.items)) { - schema.items.map((itemSchema, idx) => { - this.indexSchema(itemSchema, title, JsonPointer.join(absolutePointer, ['items', idx]), menuPointer, parent); - }); - } else { - this.indexSchema(schema.items, title, JsonPointer.join(absolutePointer, ['items']), menuPointer, parent); - } - return; - } - - if (schema.discriminator) { - let derived = this.spec.findDerivedDefinitions(schema._pointer, schema); - for (let defInfo of derived ) { - let subSpec = this.spec.getDescendant(defInfo, schema); - this.indexSchema(snapshot(subSpec), '', absolutePointer, menuPointer, schema._pointer); - } - } - - if (schema.type === 'string' && schema.enum) { - body += ' ' + schema.enum.join(' '); - } - - this.index({ - pointer: absolutePointer, - menuId: menuPointer, - title, - body - }); - - if (schema.properties) { - Object.keys(schema.properties).forEach(propName => { - let propPtr = JsonPointer.join(absolutePointer, ['properties', propName]); - let prop:SwaggerSchema = schema.properties[propName]; - this.indexSchema(prop, propName, propPtr, menuPointer, parent); - }); - } - } -} diff --git a/lib/services/warnings.service.ts b/lib/services/warnings.service.ts deleted file mode 100644 index 265c2e3e..00000000 --- a/lib/services/warnings.service.ts +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; -import { Injectable } from '@angular/core'; -import { BehaviorSubject } from 'rxjs/BehaviorSubject'; - -@Injectable() -export class WarningsService { - public static warnings = new BehaviorSubject>([]); - - private static _warnings: Array = []; - - static hasWarnings() { - return !!WarningsService._warnings.length; - } - - static warn(message:string) { - WarningsService._warnings.push(message); - WarningsService.warnings.next(WarningsService._warnings); - console.warn(message); - } -} diff --git a/lib/shared/components/CopyButton/copy-button.directive.ts b/lib/shared/components/CopyButton/copy-button.directive.ts deleted file mode 100644 index 60384a14..00000000 --- a/lib/shared/components/CopyButton/copy-button.directive.ts +++ /dev/null @@ -1,55 +0,0 @@ -'use strict'; - -import { Directive, Input, HostListener, Renderer, ElementRef, OnInit} from '@angular/core'; -import { Clipboard } from '../../../services/clipboard.service'; - -@Directive({ - selector: '[copy-button]' -}) -export class CopyButton implements OnInit { - $element: any; - cancelScrollBinding: any; - $redocEl: any; - @Input() copyText: string; - @Input() copyElement:any; - @Input() hintElement:any; - - constructor(private renderer: Renderer, private element: ElementRef) {} - - ngOnInit () { - if (!Clipboard.isSupported()) { - this.element.nativeElement.parentNode.removeChild(this.element.nativeElement); - } - this.renderer.setElementAttribute(this.element.nativeElement, 'data-hint', 'Copy to Clipboard!'); - } - - @HostListener('click') - onClick() { - let copied; - if (this.copyText) { - const text = (typeof this.copyText === 'string') - ? this.copyText - : JSON.stringify(this.copyText, null, 2); - copied = Clipboard.copyCustom(text); - } else { - copied = Clipboard.copyElement(this.copyElement); - } - - if (copied) { - this.renderer.setElementAttribute(this.element.nativeElement, 'data-hint', 'Copied!'); - } else { - let hintElem = this.hintElement || this.copyElement; - if (!hintElem) return; - this.renderer.setElementAttribute(hintElem, 'data-hint', 'Press "ctrl + c" to copy'); - this.renderer.setElementClass(hintElem, 'hint--top', true); - this.renderer.setElementClass(hintElem, 'hint--always', true); - } - } - - @HostListener('mouseleave') - onLeave() { - setTimeout(() => { - this.renderer.setElementAttribute(this.element.nativeElement, 'data-hint', 'Copy to Clipboard'); - }, 500); - } -} diff --git a/lib/shared/components/DropDown/drop-down.html b/lib/shared/components/DropDown/drop-down.html deleted file mode 100644 index e8f62bef..00000000 --- a/lib/shared/components/DropDown/drop-down.html +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/lib/shared/components/DropDown/drop-down.scss b/lib/shared/components/DropDown/drop-down.scss deleted file mode 100644 index 151bbf4e..00000000 --- a/lib/shared/components/DropDown/drop-down.scss +++ /dev/null @@ -1,74 +0,0 @@ -@import '../../styles/variables'; - -:host /deep/ { - .dk-select { - max-width: 100%; - font-family: $headers-font, $headers-font-family; - font-size: .929em; - min-width: 100px; - width: auto; - } - - .dk-selected:after { - display: none; - } - - // button - .dk-selected { - color: $secondary-color; - border-color: rgba($secondary-color, .5); - padding: 0.15em 1.5em 0.2em 0.5em; - border-radius: $border-radius; - } - - .dk-select-open-down .dk-selected, .dk-selected:focus, .dk-selected:hover { - border-color: $primary-color; - color: $primary-color; - } - - // tick - .dk-selected:before { - border-top-color: $secondary-color; - border-width: .35em .35em 0; - } - - .dk-select-open-down .dk-selected:before, - .dk-select-open-up .dk-selected:before { - border-bottom-color: $primary-color; - } - - // items - .dk-select-multi:focus .dk-select-options, - .dk-select-open-down .dk-select-options, - .dk-select-open-up .dk-select-options { - border-color: rgba($secondary-color, .2); - } - - .dk-select-options .dk-option-highlight { - background: #ffffff; - } - - .dk-select-options { - margin-top: 0.2em; - padding: 0; - border-radius: $border-radius; - box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12), 0px 2px 10px 0px rgba(34, 36, 38, 0.08) !important; - right: auto; - min-width: 100%; - } - - .dk-option { - color: $secondary-color; - padding: 0.16em 0.6em 0.2em 0.5em; - &:hover { - background-color: rgba($secondary-color, .12); - } - &:focus { - background-color: rgba($secondary-color, .12); - } - } - - .dk-option-selected { - background-color: rgba(0, 0, 0, 0.05)!important; - } -} diff --git a/lib/shared/components/DropDown/drop-down.ts b/lib/shared/components/DropDown/drop-down.ts deleted file mode 100644 index a89f3eba..00000000 --- a/lib/shared/components/DropDown/drop-down.ts +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -import { Component, EventEmitter, ElementRef, Output, Input, AfterContentInit, OnChanges } from '@angular/core'; -import * as DropKick from 'dropkickjs'; - -@Component({ - selector: 'drop-down', - templateUrl: 'drop-down.html', - styleUrls: ['./drop-down.css'] -}) -export class DropDown implements AfterContentInit, OnChanges { - @Output() change = new EventEmitter(); - @Input() active: string; - elem: any; - inst: any; - constructor(elem:ElementRef) { - this.elem = elem.nativeElement; - } - - ngAfterContentInit() { - this.inst = new DropKick(this.elem.firstElementChild, {autoWidth: true}); - } - - onChange(value) { - this.change.next(value); - } - - ngOnChanges(ch) { - if (ch.active.currentValue) { - this.inst && this.inst.select(ch.active.currentValue); - } - } - - destroy() { - this.inst.dispose(); - } -} diff --git a/lib/shared/components/DynamicNg2Viewer/dynamic-ng2-viewer.component.ts b/lib/shared/components/DynamicNg2Viewer/dynamic-ng2-viewer.component.ts deleted file mode 100644 index a8cc1147..00000000 --- a/lib/shared/components/DynamicNg2Viewer/dynamic-ng2-viewer.component.ts +++ /dev/null @@ -1,45 +0,0 @@ -'use strict'; - -import { - Component, - Input, - OnInit, - ViewContainerRef, - ComponentFactoryResolver, - Renderer -} from '@angular/core'; - -import { - ComponentParser, - ContentProjector -} from '../../../services/'; - -@Component({ - selector: 'dynamic-ng2-viewer', - template: '' -}) -export class DynamicNg2Viewer implements OnInit { - @Input() html: string; - - constructor( - private view: ViewContainerRef, - private projector: ContentProjector, - private parser: ComponentParser, - private resolver: ComponentFactoryResolver, - private renderer: Renderer) { - } - - ngOnInit() { - this.parser.setRenderer(this.renderer); - let nodesOrComponents = this.parser.splitIntoNodesOrComponents(this.html, this.view.injector); - let wrapperFactory = this.resolver.resolveComponentFactory(DynamicNg2Wrapper); - let ref = this.projector.instantiateAndProject(wrapperFactory, this.view, nodesOrComponents); - ref.changeDetectorRef.markForCheck(); - } -} - -@Component({ - selector: 'dynamic-ng2-wrapper', - template: '' -}) -export class DynamicNg2Wrapper {} diff --git a/lib/shared/components/LazyFor/lazy-for.ts b/lib/shared/components/LazyFor/lazy-for.ts deleted file mode 100644 index 772ba8ca..00000000 --- a/lib/shared/components/LazyFor/lazy-for.ts +++ /dev/null @@ -1,172 +0,0 @@ -'use strict'; - -import { - Directive, - Input, - TemplateRef, - ChangeDetectorRef, - ViewContainerRef, - Injectable -} from '@angular/core'; - -import { BehaviorSubject } from 'rxjs/BehaviorSubject'; - -import { ScrollService } from '../../../services/scroll.service'; -import { OptionsService } from '../../../services/options.service'; - -import { isSafari } from '../../../utils/helpers'; - -export class LazyForRow { - constructor(public $implicit: any, public index: number, public ready: boolean) {} - - get first(): boolean { return this.index === 0; } - - get even(): boolean { return this.index % 2 === 0; } - - get odd(): boolean { return !this.even; } -} - -@Injectable() -export class LazyTasksService { - private _tasks = []; - private _current: number = 0; - private _syncCount: number = 0; - private _emptyProcessed = false; - private menuService; - - public loadProgress = new BehaviorSubject(0); - public allSync = false; - constructor(public optionsService: OptionsService) { - } - - get processed() { - let res = this._tasks.length && (this._current >= this._tasks.length) || this._emptyProcessed; - if (!this._tasks.length) this._emptyProcessed = true; - return res; - } - - set syncCount(n: number) { - this._syncCount = n; - } - - set lazy(sync:boolean) { - this.allSync = sync; - } - - addTasks(tasks:any[], callback:Function) { - tasks.forEach((task, idx) => { - let taskCopy = Object.assign({_callback: callback, idx: idx}, task); - this._tasks.push(taskCopy); - }); - } - - nextTaskSync() { - let task = this._tasks[this._current]; - if (!task) return; - task._callback(task.idx, true); - this._current++; - this.menuService.enableItem(task.flatIdx); - this.loadProgress.next(this._current / this._tasks.length * 100); - } - - nextTask() { - requestAnimationFrame(() => { - let task = this._tasks[this._current]; - if (!task) return; - task._callback(task.idx, false).then(() => { - this._current++; - this.menuService.enableItem(task.flatIdx); - setTimeout(()=> this.nextTask()); - this.loadProgress.next(this._current / this._tasks.length * 100); - }).catch(err => console.error(err)); - }); - } - - sortTasks(center) { - let idxMap = {}; - this._tasks.sort((a, b) => { - return Math.abs(a.flatIdx - center) - Math.abs(b.flatIdx - center); - }) - } - - start(idx, menuService) { - this.menuService = menuService; - let syncCount = 5; - // I know this is a bad practice to detect browsers but there is an issue in Safari only - // http://stackoverflow.com/questions/40692365/maintaining-scroll-position-while-inserting-elements-above-glitching-only-in-sa - if (isSafari && this.optionsService.options.$scrollParent === window) { - syncCount = this._tasks.findIndex(task => task.flatIdx === idx); - syncCount += 1; - } else { - this.sortTasks(idx); - } - syncCount = Math.min(syncCount, this._tasks.length); - if (this.allSync) syncCount = this._tasks.length; - for (var i = this._current; i < syncCount; i++) { - this.nextTaskSync(); - } - - if (!this._tasks.length) { - this.loadProgress.next(100); - return; - } - - this.nextTask(); - } -} - -@Injectable() -export class LazyTasksServiceSync extends LazyTasksService { - constructor(optionsService: OptionsService) { - super(optionsService); - this.allSync = true; - } -} - - -@Directive({ - selector: '[lazyFor][lazyForOf]' -}) -export class LazyFor { - @Input() lazyForOf: any; - - prevIdx = null; - - constructor( - public _template: TemplateRef, - public cdr: ChangeDetectorRef, - public _viewContainer: ViewContainerRef, - public lazyTasks: LazyTasksService, - public scroll: ScrollService - ){ - } - - nextIteration(idx: number, sync: boolean):Promise { - const view = this._viewContainer.createEmbeddedView(this._template, - new LazyForRow(this.lazyForOf[idx], idx, sync), idx < this.prevIdx ? 0 : undefined); - this.prevIdx = idx; - view.context.index = idx; - (view as ChangeDetectorRef).markForCheck(); - (view as ChangeDetectorRef).detectChanges(); - if (sync) { - return Promise.resolve(); - } - return new Promise(resolve => { - requestAnimationFrame(() => { - this.scroll.saveScroll(); - - view.context.ready = true; - (view as ChangeDetectorRef).markForCheck(); - (view as ChangeDetectorRef).detectChanges(); - - this.scroll.restoreScroll(); - resolve(); - }); - }); - } - - ngOnInit() { - if (!this.lazyForOf) return; - this.lazyTasks.addTasks(this.lazyForOf, this.nextIteration.bind(this)) - } -} diff --git a/lib/shared/components/PerfectScrollbar/perfect-scrollbar.ts b/lib/shared/components/PerfectScrollbar/perfect-scrollbar.ts deleted file mode 100644 index 7ceafb45..00000000 --- a/lib/shared/components/PerfectScrollbar/perfect-scrollbar.ts +++ /dev/null @@ -1,49 +0,0 @@ -import 'perfect-scrollbar/dist/css/perfect-scrollbar.css'; - -import { Directive, ElementRef, OnDestroy, OnInit } from '@angular/core'; -import * as PS from 'perfect-scrollbar'; - -import { OptionsService } from '../../../services/options.service'; - -@Directive({ - selector: '[perfect-scrollbar]', -}) -export class PerfectScrollbar implements OnInit, OnDestroy { - $element: any; - subscription: any; - enabled: boolean = true; - - constructor(elementRef: ElementRef, optionsService: OptionsService) { - this.$element = elementRef.nativeElement; - this.enabled = !optionsService.options.nativeScrollbars; - } - - update() { - if (!this.enabled) return; - PS.update(this.$element); - } - - ngOnInit() { - if (!this.enabled) return; - requestAnimationFrame(() => - PS.initialize(this.$element, { - wheelSpeed: 2, - handlers: [ - 'click-rail', - 'drag-scrollbar', - 'keyboard', - 'wheel', - 'touch', - ], - wheelPropagation: true, - minScrollbarLength: 20, - suppressScrollX: true, - } as any), - ); - } - - ngOnDestroy() { - if (!this.enabled) return; - PS.destroy(this.$element); - } -} diff --git a/lib/shared/components/SelectOnClick/select-on-click.directive.ts b/lib/shared/components/SelectOnClick/select-on-click.directive.ts deleted file mode 100644 index 1b651f82..00000000 --- a/lib/shared/components/SelectOnClick/select-on-click.directive.ts +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -import { Directive, HostListener, ElementRef} from '@angular/core'; -import { Clipboard } from '../../../services/clipboard.service'; - -@Directive({ - selector: '[select-on-click]' -}) -export class SelectOnClick { - $element: any; - constructor(private element: ElementRef) {} - - @HostListener('click') - onClick() { - Clipboard.selectElement(this.element.nativeElement); - } -} diff --git a/lib/shared/components/StickySidebar/sticky-sidebar.spec.ts b/lib/shared/components/StickySidebar/sticky-sidebar.spec.ts deleted file mode 100644 index a5216d3c..00000000 --- a/lib/shared/components/StickySidebar/sticky-sidebar.spec.ts +++ /dev/null @@ -1,91 +0,0 @@ -'use strict'; - -import { getChildDebugElementByType } from '../../../../tests/helpers'; -import { Component } from '@angular/core'; -import { - inject, - TestBed -} from '@angular/core/testing'; - -import { StickySidebar } from '../index'; - -describe('Common components', () => { - beforeEach(() => { - TestBed.configureTestingModule({ declarations: [ TestApp ] }); - }); - describe('StickySidebar Component', () => { - let builder; - let component: StickySidebar; - let fixture; - - beforeEach(() => { - - fixture = TestBed.createComponent(TestApp); - let debugEl = getChildDebugElementByType(fixture.debugElement, StickySidebar); - component = debugEl.injector.get(StickySidebar); - }); - - - it('should init component', () => { - expect(component).not.toBeNull(); - }); - - it('should start unsticked', () => { - component.disable = true; - spyOn(component, 'stick').and.callThrough(); - spyOn(component, 'stickBottom').and.callThrough(); - fixture.detectChanges(); - expect(component.stick).not.toHaveBeenCalled(); - expect(component.stickBottom).not.toHaveBeenCalled(); - }); - - it('should stick to the top on the next animation frame', (done) => { - spyOn(component, 'stick').and.callThrough(); - spyOn(component, 'stickBottom').and.callThrough(); - component.disable = true; - fixture.detectChanges(); - component.disable = false; - requestAnimationFrame(() => { - expect(component.stick).toHaveBeenCalled(); - expect(component.stickBottom).toHaveBeenCalled(); - done(); - }); - }); - - it('should stick if scrolled more than scrollYOffset', () => { - spyOn(component, 'stick').and.callThrough(); - fixture.detectChanges(); - component.scrollParent = { - pageYOffset: 40 - }; - component.updatePosition(); - expect(component.stick).toHaveBeenCalled(); - }); - - // TODO: add tests for stickBottom - }); -}); - - -/** Test component that contains an ApiInfo. */ -@Component({ - selector: 'test-app', - template: - `
-
-
-
-
-
-
` - -}) -class TestApp { - options: any; - scrollParent: Window; - constructor() { - this.options = {}; - this.scrollParent = window; - this.options.scrollYOffset = () => 20; - } -} diff --git a/lib/shared/components/StickySidebar/sticky-sidebar.ts b/lib/shared/components/StickySidebar/sticky-sidebar.ts deleted file mode 100644 index 2cb9eea4..00000000 --- a/lib/shared/components/StickySidebar/sticky-sidebar.ts +++ /dev/null @@ -1,100 +0,0 @@ -'use strict'; - -import { Directive, ElementRef, Input, OnInit, OnDestroy, OnChanges} from '@angular/core'; -import { BrowserDomAdapter as DOM } from '../../../utils/browser-adapter'; - -@Directive({ - selector: '[sticky-sidebar]' -}) -export class StickySidebar implements OnInit, OnDestroy, OnChanges { - $element: any; - cancelScrollBinding: any; - $redocEl: any; - @Input() scrollParent:any; - @Input() scrollYOffset:any; - @Input() disable:any; - - constructor(elementRef:ElementRef) { - this.$element = elementRef.nativeElement; - - // initial styling - DOM.setStyle(this.$element, 'position', 'absolute'); - DOM.setStyle(this.$element, 'top', '0'); - DOM.setStyle(this.$element, 'bottom', '0'); - DOM.setStyle(this.$element, 'max-height', '100%'); - } - - bind() { - this.cancelScrollBinding = DOM.onAndCancel(this.scrollParent, 'scroll', () => { this.updatePosition(); }); - } - - unbind() { - if (this.cancelScrollBinding) this.cancelScrollBinding(); - } - - updatePosition() { - var stuck = false; - if ( this.scrollY + this.scrollYOffset() >= this.$redocEl.offsetTop && !this.disable) { - this.stick(); - stuck = true; - } else { - this.unstick(); - } - - - if ( this.scrollY + window.innerHeight - this.scrollYOffset() - >= this.$redocEl.scrollHeight && !this.disable) { - this.stickBottom(); - stuck = true; - } else { - this.unstickBottom(); - } - - if (!stuck) { - DOM.setStyle(this.$element, 'position', 'absolute'); - } - } - - stick() { - DOM.setStyle(this.$element, 'position', 'fixed'); - DOM.setStyle(this.$element, 'top', this.scrollYOffset() + 'px'); - } - - unstick() { - DOM.setStyle(this.$element, 'top', '0'); - } - - stickBottom() { - DOM.setStyle(this.$element, 'position', 'fixed'); - var offset = this.scrollY + this.scrollParentHeight - (this.$redocEl.scrollHeight + this.$redocEl.offsetTop); - DOM.setStyle(this.$element, 'bottom', offset + 'px'); - } - - unstickBottom() { - DOM.setStyle(this.$element, 'bottom', '0'); - } - - get scrollY() { - return (this.scrollParent.pageYOffset != undefined) ? this.scrollParent.pageYOffset : this.scrollParent.scrollTop; - } - - get scrollParentHeight() { - return (this.scrollParent.innerHeight != undefined) ? this.scrollParent.innerHeight : this.scrollParent.clientHeight; - } - - ngOnInit() { - // FIXME use more reliable code - this.$redocEl = this.$element.offsetParent.parentNode || DOM.defaultDoc().body; - this.bind(); - requestAnimationFrame(() => this.updatePosition()); - } - - ngOnChanges() { - if (!this.$redocEl || this.disable) return; - this.updatePosition(); - } - - ngOnDestroy() { - this.unbind(); - } -} diff --git a/lib/shared/components/Tabs/tab.html b/lib/shared/components/Tabs/tab.html deleted file mode 100644 index 3b0afa5c..00000000 --- a/lib/shared/components/Tabs/tab.html +++ /dev/null @@ -1,3 +0,0 @@ -
- -
diff --git a/lib/shared/components/Tabs/tab.scss b/lib/shared/components/Tabs/tab.scss deleted file mode 100644 index 8791f601..00000000 --- a/lib/shared/components/Tabs/tab.scss +++ /dev/null @@ -1,10 +0,0 @@ -:host { - display: block; -} -.tab-wrap { - display: none; -} - -.tab-wrap.active { - display: block; -} diff --git a/lib/shared/components/Tabs/tabs.html b/lib/shared/components/Tabs/tabs.html deleted file mode 100644 index d059b9a7..00000000 --- a/lib/shared/components/Tabs/tabs.html +++ /dev/null @@ -1,5 +0,0 @@ -
    -
  • -
- diff --git a/lib/shared/components/Tabs/tabs.scss b/lib/shared/components/Tabs/tabs.scss deleted file mode 100644 index abd0bd0d..00000000 --- a/lib/shared/components/Tabs/tabs.scss +++ /dev/null @@ -1,54 +0,0 @@ -@import '../../styles/variables'; - -:host { - display: block; -} - -ul { - display: block; - margin: 0; - padding: 0; -} - -li { - list-style: none; - display: inline-block; - cursor: pointer; -} - -li /deep/ .redoc-markdown-block p { - display: inline; -} - -.tab-success, .tab-error, .tab-redirect, .tab-info { - &:before { - content: ""; - display: inline-block; - position: relative; - top: -2px; - height: 4px; - width: 4px; - border-radius: 50%; - margin-right: 0.5em; - } -} - -.tab-success:before { - box-shadow: 0 0 3px 0 $green; - background-color: $green; -} - -.tab-error:before { - box-shadow: 0 0 3px 0 $red; - background-color: $red; -} - -.tab-redirect:before { - box-shadow: 0 0 3px 0 $yellow; - background-color: $yellow; -} - -.tab-info:before { - box-shadow: 0 0 3px 0 $primary-color; - background-color: $primary-color; -} diff --git a/lib/shared/components/Tabs/tabs.spec.ts b/lib/shared/components/Tabs/tabs.spec.ts deleted file mode 100644 index 44a13219..00000000 --- a/lib/shared/components/Tabs/tabs.spec.ts +++ /dev/null @@ -1,151 +0,0 @@ -'use strict'; - -import { getChildDebugElement, getChildDebugElementAll } from '../../../../tests/helpers'; -import { Component } from '@angular/core'; - -import { - inject, - TestBed -} from '@angular/core/testing'; - -import {Tabs, Tab} from '../index'; - -describe('Common components', () => { - beforeEach(() => { - TestBed.configureTestingModule({ declarations: [ TestApp ] }); - }); - describe('Tabs Component', () => { - let builder; - let component; - let childDebugEls; - let debugEl; - let fixture; - let hostComponent; - - beforeEach(() => { - - fixture = TestBed.createComponent(TestApp); - hostComponent = fixture.debugElement.componentInstance; - debugEl = getChildDebugElement(fixture.debugElement, 'tabs'); - childDebugEls = getChildDebugElementAll(debugEl, 'tab'); - component = debugEl.componentInstance; - }); - - it('should init component', () => { - expect(component).not.toBeNull(); - }); - - it('should handle inner tabs', () => { - component.tabs.should.have.lengthOf(2); - childDebugEls.should.have.lengthOf(2); - }); - - it('should activate first tab by default', () => { - let tabs = childDebugEls.map(debugEl => debugEl.componentInstance); - let [tab1, tab2] = tabs; - - tab1.active.should.be.true(); - tab2.active.should.be.false(); - }); - - it('should change active tab on click', () => { - fixture.detectChanges(); - //let headerEls = nativeElement.querySelectorAll('li'); - let tabs = childDebugEls.map(debugEl => debugEl.componentInstance); - let [tab1, tab2] = tabs; - - let tabsInst = debugEl.componentInstance; - tabsInst.selectTab(tab2); - tab1.active.should.be.false(); - tab2.active.should.be.true(); - }); - - it('should change tab by title and not emit Event', (done) => { - fixture.detectChanges(); - let tabs = childDebugEls.map(debugEl => debugEl.componentInstance); - let [tab1, tab2] = tabs; - let tab2Title = 'Tab2'; - - let tabsInst = debugEl.componentInstance; - tabsInst.selectyByTitle(tab2Title); - tab1.active.should.be.false(); - tab2.active.should.be.true(); - - setTimeout(() => { - hostComponent.eventLog.should.be.deepEqual([]); - done(); - }); - }); - - - it('should emit event on tab Change', (done) => { - fixture.detectChanges(); - let tabs = childDebugEls.map(debugEl => debugEl.componentInstance); - let tab2 = tabs[1]; - let tabsInst = debugEl.componentInstance; - tabsInst.selectTab(tab2, true); - - setTimeout(() => { - hostComponent.eventLog.should.be.deepEqual(['Tab2']); - done(); - }); - }); - - it('should emit event on tab change by Title with notify true', (done) => { - fixture.detectChanges(); - let tab2Title = 'Tab2'; - - let tabsInst = debugEl.componentInstance; - tabsInst.selectyByTitle(tab2Title, true); - - setTimeout(() => { - hostComponent.eventLog.should.be.deepEqual(['Tab2']); - done(); - }); - }); - - it('should not emit event on tab change with notify false', (done) => { - fixture.detectChanges(); - let tabs = childDebugEls.map(debugEl => debugEl.componentInstance); - let tab2 = tabs[1]; - let tabsInst = debugEl.componentInstance; - tabsInst.selectTab(tab2, false); - - setTimeout(() => { - hostComponent.eventLog.should.be.deepEqual([]); - done(); - }); - }); - - it('should leave current tab active if selectyByTitle non existing title', () => { - fixture.detectChanges(); - let tabs = childDebugEls.map(debugEl => debugEl.componentInstance); - let [tab1, tab2] = tabs; - - let tabsInst = debugEl.componentInstance; - tabsInst.selectyByTitle('badTitle'); - tab1.active.should.be.true(); - tab2.active.should.be.false(); - }); - }); -}); - - -/** Test component that contains an ApiInfo. */ -@Component({ - selector: 'test-app', - template: - ` - Test - Test - ` -}) -class TestApp { - eventLog: any; - constructor() { - this.eventLog = []; - } - onEvent(event) { - this.eventLog.push(event); - } -} diff --git a/lib/shared/components/Tabs/tabs.ts b/lib/shared/components/Tabs/tabs.ts deleted file mode 100644 index a99b9288..00000000 --- a/lib/shared/components/Tabs/tabs.ts +++ /dev/null @@ -1,70 +0,0 @@ -'use strict'; - -import { Component, EventEmitter, Input, Output, OnInit } from '@angular/core'; -import { ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'tabs', - templateUrl: 'tabs.html', - styleUrls: ['tabs.css'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class Tabs implements OnInit { - @Input() selected: any; - @Output() change = new EventEmitter(); - tabs: Tab[] = []; - constructor(private changeDetector:ChangeDetectorRef) {} - - selectTab(tab, notify = true) { - if (tab.active) return; - this.tabs.forEach((tab) => { - tab.active = false; - }); - tab.active = true; - if (notify) this.change.next(tab.tabTitle); - } - - selectyByTitle(tabTitle, notify = false) { - let prevActive; - let newActive; - this.tabs.forEach((tab) => { - if (tab.active) prevActive = tab; - tab.active = false; - if (tab.tabTitle === tabTitle) { - newActive = tab; - } - }); - if (newActive) { - newActive.active = true; - } else { - prevActive.active = true; - } - if (notify) this.change.next(tabTitle); - this.changeDetector.markForCheck(); - } - - addTab(tab) { - if (this.tabs.length === 0) { - tab.active = true; - } - this.tabs.push(tab); - } - - ngOnInit() { - if (this.selected) this.selected.subscribe(title => this.selectyByTitle(title)); - } -} - -@Component({ - selector: 'tab', - templateUrl: 'tab.html', - styleUrls: ['tab.css'] -}) -export class Tab { - @Input() active: boolean = false; - @Input() tabTitle: string; - @Input() tabStatus: string; - constructor(tabs: Tabs) { - tabs.addTab(this); - } -} diff --git a/lib/shared/components/Zippy/zippy.html b/lib/shared/components/Zippy/zippy.html deleted file mode 100644 index 80ada597..00000000 --- a/lib/shared/components/Zippy/zippy.html +++ /dev/null @@ -1,13 +0,0 @@ -
-
- - - - - - -
-
- -
-
diff --git a/lib/shared/components/Zippy/zippy.scss b/lib/shared/components/Zippy/zippy.scss deleted file mode 100644 index f5b824f2..00000000 --- a/lib/shared/components/Zippy/zippy.scss +++ /dev/null @@ -1,119 +0,0 @@ -$zippy-success-color: #00aa13; -$zippy-success-bg-color: rgba($zippy-success-color, .08); - -$zippy-error-color: #e53935; -$zippy-error-bg-color: rgba($zippy-error-color, .06); - -$zippy-info-color: #0033a0; -$zippy-info-bg-color: rgba($zippy-info-color, .08); - -$zippy-redirect-color: #263238; -$zippy-redirect-bg-color: rgba($zippy-redirect-color, .08); - -:host { - // performance optimization - // transform: translate3d(0, 0, 0); - // backface-visibility: hidden; - overflow: hidden; - display: block; -} - -.zippy-title { - padding: 10px; - border-radius: 2px; - margin-bottom: 4px; - line-height: 1.5em; - background-color: #f2f2f2; - cursor: pointer; - - .zippy-success > & { - color: $zippy-success-color; - background-color: $zippy-success-bg-color; - } - - .zippy-error > & { - color: $zippy-error-color; - background-color: $zippy-error-bg-color; - } - - .zippy-redirect > & { - color: $zippy-redirect-color; - background-color: $zippy-redirect-bg-color; - } - - .zippy-info > & { - color: $zippy-info-color; - background-color: $zippy-info-bg-color; - } - - /deep/ p { - font-weight: normal; - } -} - -.zippy-indicator svg { - height: 1.2em; - width: 1.2em; - vertical-align: top; - transition: all 0.3s ease; - transform: rotateZ(-180deg); -} - -.zippy-hidden > .zippy-title svg { - transform: rotateZ(0); -} - - -.zippy-title polygon { - .zippy-success > & { - fill: $zippy-success-color; - } - .zippy-error > & { - fill: $zippy-error-color; - } - - .zippy-redirect > & { - fill: $zippy-redirect-color; - } - - .zippy-info > & { - fill: $zippy-info-color; - } -} - -span.zippy-indicator { - width: 1em; - font-size: 1.2em; - text-align: center; - display: inline-block; - float: left; - margin-right: 5px; -} - -.zippy-content { - padding: 15px 0; -} - -.zippy-empty { - .zippy-title { - cursor: default; - } - - .zippy-indicator { - svg { - display: none; - } - &:before { - content: "—"; - font-weight: bold; - } - } - - .zippy-content { - display: none; - } -} - -.zippy-hidden > .zippy-content { - display: none; -} diff --git a/lib/shared/components/Zippy/zippy.spec.ts b/lib/shared/components/Zippy/zippy.spec.ts deleted file mode 100644 index db246662..00000000 --- a/lib/shared/components/Zippy/zippy.spec.ts +++ /dev/null @@ -1,111 +0,0 @@ -'use strict'; - -import { getChildDebugElement, mouseclick } from '../../../../tests/helpers'; - -import { Component } from '@angular/core'; -import { - inject, - TestBed -} from '@angular/core/testing'; - -import { Zippy } from '../index'; - -describe('Common components', () => { - beforeEach(() => { - TestBed.configureTestingModule({ declarations: [ TestApp ] }); - }); - describe('Zippy Component', () => { - let builder; - let component: Zippy; - let nativeElement; - let fixture; - - beforeEach(() => { - fixture = TestBed.createComponent(TestApp); - let debugEl = getChildDebugElement(fixture.debugElement, 'zippy'); - component = debugEl.componentInstance; - nativeElement = debugEl.nativeElement; - }); - - it('should init component', () => { - expect(component).not.toBeNull(); - }); - - it('should init component defaults', () => { - component.empty.should.be.false(); - component.open.should.be.false(); - component.type.should.be.equal('general'); - }); - - it('should init properties from dom params', () => { - fixture.detectChanges(); - component.open.should.be.true(); - component.empty.should.be.true(); - component.title.should.be.equal('Zippy'); - component.type.should.be.equal('test'); - }); - - it('project inner content', () => { - fixture.detectChanges(); - let contentEl = nativeElement.querySelector('.zippy-content'); - expect(contentEl.innerText).toMatch('test'); - }); - - it('should open and close zippy', (done) => { - fixture.detectChanges(); - component.empty = false; - component.open = true; - fixture.detectChanges(); - - let testComponent = fixture.debugElement.componentInstance; - - let titleEl = nativeElement.querySelector('.zippy-title'); - mouseclick(titleEl); - fixture.detectChanges(); - component.open.should.be.false(); - testComponent.opened.should.be.false(); - - mouseclick(titleEl); - fixture.detectChanges(); - setTimeout(() => { - component.open.should.be.true(); - testComponent.opened.should.be.true(); - testComponent.clickCount.should.be.equal(2); - done(); - }); - }); - - it('should disable empty zippy', () => { - fixture.detectChanges(); - component.empty = true; - fixture.detectChanges(); - - let testComponent = fixture.debugElement.componentInstance; - - let titleEl = nativeElement.querySelector('.zippy-title'); - mouseclick(titleEl); - fixture.detectChanges(); - testComponent.clickCount.should.be.equal(0); - }); - }); -}); - - -/** Test component that contains an ApiInfo. */ -@Component({ - selector: 'test-app', - template: - `test` -}) -class TestApp { - opened: boolean; - clickCount: number; - constructor() { - this.opened = false; - this.clickCount = -1; // initial change detection - } - open(val) { - this.opened = val; - this.clickCount++; - } -} diff --git a/lib/shared/components/Zippy/zippy.ts b/lib/shared/components/Zippy/zippy.ts deleted file mode 100644 index a2d851f2..00000000 --- a/lib/shared/components/Zippy/zippy.ts +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -import { Component, EventEmitter, Output, Input, OnChanges } from '@angular/core'; -@Component({ - selector: 'zippy', - templateUrl: './zippy.html', - styleUrls: ['./zippy.css'] -}) -export class Zippy implements OnChanges { - @Input() type = 'general'; - @Input() empty = false; - @Input() title; - @Input() headless: boolean = false; - @Input() open = false; - @Output() openChange = new EventEmitter(); - - - toggle() { - this.open = !this.open; - if (this.empty) return; - this.openChange.emit(this.open); - } - - ngOnChanges(ch) { - if (ch.open.currentValue === true) { - this.openChange.emit(ch.open.currentValue); - } - } -} diff --git a/lib/shared/components/index.ts b/lib/shared/components/index.ts deleted file mode 100644 index e208d16d..00000000 --- a/lib/shared/components/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; -import { DropDown } from './DropDown/drop-down'; -import { StickySidebar } from './StickySidebar/sticky-sidebar'; -import { Tabs, Tab } from './Tabs/tabs'; -import { Zippy } from './Zippy/zippy'; -import { CopyButton } from './CopyButton/copy-button.directive'; -import { SelectOnClick } from './SelectOnClick/select-on-click.directive'; -import { DynamicNg2Viewer, DynamicNg2Wrapper } from './DynamicNg2Viewer/dynamic-ng2-viewer.component'; -import { LazyFor, LazyTasksService, LazyTasksServiceSync } from './LazyFor/lazy-for'; -import { PerfectScrollbar } from './PerfectScrollbar/perfect-scrollbar'; - -export const REDOC_COMMON_DIRECTIVES = [ - PerfectScrollbar, DropDown, StickySidebar, Tabs, Tab, Zippy, CopyButton, SelectOnClick, DynamicNg2Viewer, DynamicNg2Wrapper, LazyFor -]; - -export { DropDown, StickySidebar, Tabs, Tab, Zippy, CopyButton, SelectOnClick, DynamicNg2Viewer, DynamicNg2Wrapper, LazyFor } -export { LazyTasksService, LazyTasksServiceSync, PerfectScrollbar } diff --git a/lib/shared/styles/_share-link.scss b/lib/shared/styles/_share-link.scss deleted file mode 100644 index a3ad5415..00000000 --- a/lib/shared/styles/_share-link.scss +++ /dev/null @@ -1,23 +0,0 @@ -.share-link { - cursor: pointer; - margin-left: -15px; - padding: 0; - line-height: 1; - width: 15px; - display: inline-block; -} -.share-link:before { - content: ""; - width: 15px; - height: 15px; - background-size: contain; - background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg=='); - opacity: 0.5; - visibility: hidden; - display: inline-block; - vertical-align: middle; -} - -.sharable-header:hover .share-link:before, .share-link:hover:before { - visibility: visible; -} diff --git a/lib/shared/styles/_variables.scss b/lib/shared/styles/_variables.scss deleted file mode 100644 index 67f49c18..00000000 --- a/lib/shared/styles/_variables.scss +++ /dev/null @@ -1,83 +0,0 @@ -// Colors -// --------------------------- -$primary-color: #0033a0; -$secondary-color: #263238; -$black: #263238; -$green: #00aa13; -$yellow: #f1c400; -$red: #e53935; -$background-color: #fff; -$border-color: #ccc; - -$em-size: 14px; - -// Font weights -// --------------------------- -$light: 300; -$regular: 400; -$bold: 700; - -// Base Font -// --------------------------- -$base-font: Roboto; -$base-font-family: sans-serif; -$base-font-weight: $light; -$base-font-size: 1em; -$base-line-height: 1.5em; -$text-color: $black; - -// Heading Font -// --------------------------- -$headers-font: Montserrat; -$headers-font-family: sans-serif; -$headers-font-weight: $regular; -$headers-color: $primary-color; -$h1: 1.85714285714286em; -$h2: 1.5714285714285714em; -$h3: 1.2857142857142858em; -$h4: 1.1428571428571428em; -$h5: 0.929em; - -// spacings -$section-spacing: 40px; - -// Side Bar -// --------------------------- -$side-bar-width: 260px; -$side-bar-bg-color: #fafafa; -$side-menu-item-color: #384248; -$side-menu-even-bg-color: #f0f0f0; -$side-menu-active-bg-color: #f0f0f0; -$side-menu-item-hpadding: 20px; -$side-menu-item-vpadding: 5px; - -// Sample Panel -// --------------------------- -$samples-panel-bg-color: $black; -$samples-panel-width: 40%; -$sample-panel-headers-color: lighten($black, 50%); -$sample-panel-color: lighten($black, 80%); - -$tree-lines-color: rgba($primary-color, 0.5); - -$side-menu-mobile-breakpoint: 1000px; -$right-panel-squash-breakpoint: 1100px; - -// Border Radius -// --------------------------- -$border-radius: 2px; - -// texts -$array-text: 'Array of '; -$tuple-text: 'Tuple '; - -// HTTP Verb colors -$get-color: #6bbd5b; -$post-color: #248fb2; -$put-color: #9b708b; -$options-color: #d3ca12; -$patch-color: #e09d43; -$head-color: #c167e4; -$delete-color: #e27a7a; -$basic-color: #999; -$link-color: #31bbb6; diff --git a/lib/utils/browser-adapter.ts b/lib/utils/browser-adapter.ts deleted file mode 100644 index 6cf835fa..00000000 --- a/lib/utils/browser-adapter.ts +++ /dev/null @@ -1,53 +0,0 @@ -export class BrowserDomAdapter { - static query(selector: string): any { return document.querySelector(selector); } - - static querySelector(el: any /** TODO #9100 */, selector: string): HTMLElement { - return el.querySelector(selector); - } - - static onAndCancel( - el: any /** TODO #9100 */, evt: any /** TODO #9100 */, - listener: any /** TODO #9100 */): Function { - el.addEventListener(evt, listener, false); - // Needed to follow Dart's subscription semantic, until fix of - // https://code.google.com/p/dart/issues/detail?id=17406 - return () => { el.removeEventListener(evt, listener, false); }; - } - - static attributeMap(element: any /** TODO #9100 */): Map { - var res = new Map(); - var elAttrs = element.attributes; - for (var i = 0; i < elAttrs.length; i++) { - var attrib = elAttrs[i]; - res.set(attrib.name, attrib.value); - } - return res; - } - - static setStyle(element: any /** TODO #9100 */, styleName: string, styleValue: string) { - element.style[styleName] = styleValue; - } - - static removeStyle(element: any /** TODO #9100 */, stylename: string) { - element.style[stylename] = null; - } - - static getStyle(element: any /** TODO #9100 */, stylename: string): string { - return element.style[stylename]; - } - - static hasStyle(element: any /** TODO #9100 */, styleName: string, styleValue: string = null): boolean { - var value = this.getStyle(element, styleName) || ''; - return styleValue ? value === styleValue : value.length > 0; - } - - static hasAttribute(element: any /** TODO #9100 */, attribute: string): boolean { - return element.hasAttribute(attribute); - } - - static getAttribute(element: any /** TODO #9100 */, attribute: string): string { - return element.getAttribute(attribute); - } - - static defaultDoc(): HTMLDocument { return document; } -} diff --git a/lib/utils/custom-error-handler.ts b/lib/utils/custom-error-handler.ts deleted file mode 100644 index db7684c7..00000000 --- a/lib/utils/custom-error-handler.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ErrorHandler, Injectable } from '@angular/core'; -import { AppStateService } from '../services/app-state.service'; - -@Injectable() -export class CustomErrorHandler extends ErrorHandler { - constructor(private appState: AppStateService) { - super(); - } - handleError(error) { - this.appState.error.next(error && error.rejection || error); - super.handleError(error); - } -} diff --git a/lib/utils/helpers.ts b/lib/utils/helpers.ts deleted file mode 100644 index 44f8e4df..00000000 --- a/lib/utils/helpers.ts +++ /dev/null @@ -1,183 +0,0 @@ -'use strict'; - -export interface StringMap { - [key: string]: T; -} - -export function stringify(obj:any) { - return JSON.stringify(obj); -} - -export function isString(str:any):str is String { - return typeof str === 'string'; -} - -export function isFunction(func: any) { - return typeof func === 'function'; -} - -export function isBlank(obj: any): boolean { - return obj == undefined; -} - -export function stripTrailingSlash(path:string):string { - return path.endsWith('/') ? path.substring(0, path.length - 1) : path; -} - -const hasOwnProperty = Object.prototype.hasOwnProperty; -export function groupBy(array: T[], key:string):StringMap { - return array.reduce>(function(res, value) { - if (hasOwnProperty.call(res, value[key])) { - res[value[key]].push(value); - } else { - res[value[key]] = [value]; - } - return res; - }, {}); -} - -export function statusCodeType(statusCode, defaultAsError = false) { - if (statusCode === 'default') { - return defaultAsError ? 'error' : 'success'; - } - - if (statusCode < 100 || statusCode > 599) { - throw new Error('invalid HTTP code'); - } - let res = 'success'; - if (statusCode >= 300 && statusCode < 400) { - res = 'redirect'; - } else if (statusCode >= 400) { - res = 'error'; - } else if (statusCode < 200) { - res = 'info'; - } - return res; -} - -export function defaults(target, src) { - var props = Object.keys(src); - - var index = -1, - length = props.length; - - while (++index < length) { - var key = props[index]; - if (target[key] === undefined) { - target[key] = src[key]; - } - } - return target; -} - -export function safePush(obj, prop, val) { - if (!obj[prop]) obj[prop] = []; - obj[prop].push(val); -} - -// credits https://remysharp.com/2010/07/21/throttling-function-calls -export function throttle(fn, threshhold, scope) { - threshhold = threshhold || 250; - var last, - deferTimer; - return function () { - var context = scope || this; - - var now = +new Date, - args = arguments; - if (last && now < last + threshhold) { - // hold on to it - clearTimeout(deferTimer); - deferTimer = setTimeout(function () { - last = now; - fn.apply(context, args); - }, threshhold); - } else { - last = now; - fn.apply(context, args); - } - }; -} - -export function debounce(func, wait, immediate = false) { - var timeout; - return function() { - var context = this, args = arguments; - var later = function() { - timeout = null; - if (!immediate) func.apply(context, args); - }; - var callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) func.apply(context, args); - }; -} - -export const isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0 - || (function (p) { return p.toString() === '[object SafariRemoteNotification]'; })(!window['safari'] - || safari.pushNotification); - -// works only for plain objects (JSON) -export function snapshot(obj) { - if(obj == undefined || typeof(obj) !== 'object') { - return obj; - } - - if(obj instanceof Date) { - return new Date(obj.getTime()); - } - - var temp = Array.isArray(obj) ? [] : {}; - - for(var key in obj) { - if (obj.hasOwnProperty(key)) { - temp[key] = snapshot(obj[key]); - } - } - - return temp; -} - -export function isJsonLike(contentType: string): boolean { - return contentType.search(/json/i) !== -1; -} - -export function isXmlLike(contentType: string): boolean { - return contentType.search(/xml/i) !== -1; -} - -export function isTextLike(contentType: string): boolean { - return contentType.search(/text\/plain/i) !== -1; -} - -export function getJsonLikeSample(samples: Object = {}) { - const jsonLikeKeys = Object.keys(samples).filter(isJsonLike); - - if (!jsonLikeKeys.length) { - return false; - } - - return samples[jsonLikeKeys[0]]; -} - -export function getXmlLikeSample(samples: Object = {}) { - const xmlLikeKeys = Object.keys(samples).filter(isXmlLike); - - if (!xmlLikeKeys.length) { - return false; - } - - return samples[xmlLikeKeys[0]]; -} - - -export function getTextLikeSample(samples: Object = {}) { - const textLikeKeys = Object.keys(samples).filter(isTextLike); - - if (!textLikeKeys.length) { - return false; - } - - return samples[textLikeKeys[0]]; -} diff --git a/lib/utils/index.ts b/lib/utils/index.ts deleted file mode 100644 index 06c03491..00000000 --- a/lib/utils/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './custom-error-handler'; -export * from './helpers'; -export * from './md-renderer'; -export * from './spec-manager'; - -export { default as JsonPointer } from './JsonPointer'; diff --git a/lib/utils/md-renderer.spec.ts b/lib/utils/md-renderer.spec.ts deleted file mode 100644 index 9f944fba..00000000 --- a/lib/utils/md-renderer.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -import { MdRenderer } from '../../lib/utils/md-renderer'; - -describe('Utils', () => { - describe('Markdown renderer', () => { - let mdRender: MdRenderer; - beforeEach(() => { - mdRender = new MdRenderer(); - }); - it('should return a level-1 heading even though only level-2 is present', () => { - mdRender.renderMd('## Sub Intro'); - Object.keys(mdRender.headings).length.should.be.equal(1); - should.exist(mdRender.headings['Sub-Intro']); - }); - it('should return a level-2 heading as a child of level-1', () => { - mdRender.renderMd('# Introduction \n ## Sub Intro'); - Object.keys(mdRender.headings).length.should.be.equal(1); - should.exist(mdRender.headings['Introduction']); - should.exist(mdRender.headings['Introduction'].children); - Object.keys(mdRender.headings['Introduction'].children).length.should.be.equal(1); - }); - }); -}); diff --git a/lib/utils/md-renderer.ts b/lib/utils/md-renderer.ts deleted file mode 100644 index 9e2c890c..00000000 --- a/lib/utils/md-renderer.ts +++ /dev/null @@ -1,158 +0,0 @@ -'use strict'; - -import { Injectable } from '@angular/core'; -import * as slugify from 'slugify'; -import * as Remarkable from 'remarkable'; -import { StringMap } from './'; - -declare var Prism: any; -const md = new Remarkable({ - html: true, - linkify: true, - breaks: false, - typographer: false, - highlight: (str, lang) => { - if (lang === 'json') lang = 'js'; - let grammar = Prism.languages[lang]; - // fallback to click - if (!grammar) return str; - return Prism.highlight(str, grammar); - } -}); - -export interface MarkdownHeading { - title?: string; - id: string; - slug?: string; - content?: string; - children?: StringMap; -} - -export class MdRenderer { - public headings: StringMap = {}; - currentTopHeading: MarkdownHeading; - - private _origRules:any = {}; - private _preProcessors:Function[] = []; - - constructor(private raw: boolean = false) { - } - - addPreprocessor(p: Function) { - this._preProcessors.push(p); - } - - saveOrigRules() { - this._origRules.open = md.renderer.rules.heading_open; - this._origRules.close = md.renderer.rules.heading_close; - } - - restoreOrigRules() { - md.renderer.rules.heading_open = this._origRules.open; - md.renderer.rules.heading_close = this._origRules.close; - } - - saveHeading(title: string, parent:MarkdownHeading = {id:null, children: this.headings}) :MarkdownHeading { - // if title contains some non-ASCII characters (e.g. chinese) slugify returns empty string - let slug = slugify(title) || title; - let id = slug; - if (parent && parent.id) id = `${parent.id}/${id}`; - parent.children = parent.children || {}; - parent.children[id] = { - title, - id, - slug - }; - return parent.children[id]; - } - - flattenHeadings(container: StringMap): MarkdownHeading[] { - if (!container) return []; - let res = []; - Object.keys(container).forEach(k => { - let heading = container[k]; - res.push(heading); - res.push(...this.flattenHeadings(heading.children)); - }); - return res; - } - - attachHeadingsContent(rawText:string) { - const buildRegexp = heading => new RegExp( - `` - ); - - const tmpEl = document.createElement('DIV'); - - const html2Str = html => { - tmpEl.innerHTML = html; - return tmpEl.innerText; - }; - - let flatHeadings = this.flattenHeadings(this.headings); - if (flatHeadings.length < 1) return; - let prevHeading = flatHeadings[0]; - - let prevPos = rawText.search(buildRegexp(prevHeading)); - for (let i=1; i < flatHeadings.length; i++) { - let heading = flatHeadings[i]; - let currentPos = rawText.substr(prevPos + 1).search(buildRegexp(heading)) + prevPos + 1; - prevHeading.content = html2Str(rawText.substring(prevPos, currentPos)); - - prevHeading = heading; - prevPos = currentPos; - } - prevHeading.content = html2Str(rawText.substring(prevPos)); - } - - headingOpenRule(tokens, idx) { - if (tokens[idx].hLevel > 2 ) { - return this._origRules.open(tokens, idx); - } else { - let content = tokens[idx + 1].content; - if (tokens[idx].hLevel === 1 ) { - this.currentTopHeading = this.saveHeading(content); - let id = this.currentTopHeading.id; - return `` + - `` + - ``; - } else if (tokens[idx].hLevel === 2 ) { - let heading = this.saveHeading(content, this.currentTopHeading); - let contentSlug = `${heading.id}`; - return `` + - `` + - ``; - } - } - } - - headingCloseRule(tokens, idx) { - if (tokens[idx].hLevel > 2 ) { - return this._origRules.close(tokens, idx); - } else { - return `\n`; - } - } - - renderMd(rawText:string) { - if (!this.raw) { - this.saveOrigRules(); - md.renderer.rules.heading_open = this.headingOpenRule.bind(this); - md.renderer.rules.heading_close = this.headingCloseRule.bind(this); - } - let text = rawText; - - for (let i=0; i${this.renderer.renderMd(value)}`; - return this.unstrustedSpec ? res : this.sanitizer.bypassSecurityTrustHtml(res); - } -} - -@Pipe({ name: 'safe' }) -export class SafePipe implements PipeTransform { - constructor(private sanitizer: DomSanitizer) {} - transform(value:string|SafeHtml):SafeHtml { - if (isBlank(value)) return value; - if (!isString(value)) { - return value; - } - - return this.sanitizer.bypassSecurityTrustHtml(value as string); - } -} - -const langMap = { - 'c++': 'cpp', - 'c#': 'csharp', - 'objective-c': 'objectivec', - 'shell': 'bash', - 'viml': 'vim' -}; - -@Pipe({ name: 'prism' }) -export class PrismPipe implements PipeTransform { - constructor(private sanitizer: DomSanitizer) {} - transform(value, args) { - if (isBlank(args) || args.length === 0) { - throw new BaseException('Prism pipe requires one argument'); - } - if (isBlank(value)) return value; - if (!isString(value)) { - throw new InvalidPipeArgumentException(PrismPipe, value); - } - let lang = args[0].toString().trim().toLowerCase(); - if (langMap[lang]) lang = langMap[lang]; - - let grammar = Prism.languages[lang]; - //fallback to clike - if (!grammar) grammar = Prism.languages.clike; - return this.sanitizer.bypassSecurityTrustHtml(Prism.highlight(value, grammar)); - } -} - -@Pipe({ name: 'encodeURIComponent' }) -export class EncodeURIComponentPipe implements PipeTransform { - transform(value:string) { - if (isBlank(value)) return value; - if (!isString(value)) { - throw new InvalidPipeArgumentException(EncodeURIComponentPipe, value); - } - return encodeURIComponent(value); - } -} - -const COLLECTION_FORMATS = { - csv: 'Comma Separated', - ssv: 'Space Separated', - tsv: 'Tab Separated', - pipes: 'Pipe Separated' -}; - -@Pipe({ name: 'collectionFormat' }) -export class CollectionFormatPipe implements PipeTransform { - transform(param:any) { - let format = param.collectionFormat; - if (!format) format = 'csv'; - if (format === 'multi') { - return 'Multiple ' + param.in + ' params of'; - } - return COLLECTION_FORMATS[format]; - } -} - -export const REDOC_PIPES = [ - MarkedPipe, SafePipe, PrismPipe, EncodeURIComponentPipe, JsonFormatter, KeysPipe, CollectionFormatPipe -]; diff --git a/lib/utils/spec-manager.ts b/lib/utils/spec-manager.ts deleted file mode 100644 index 6a399da4..00000000 --- a/lib/utils/spec-manager.ts +++ /dev/null @@ -1,281 +0,0 @@ -'use strict'; -import { Injectable } from '@angular/core'; -import * as JsonSchemaRefParser from 'json-schema-ref-parser'; -import { JsonPointer } from './JsonPointer'; -import { parse as urlParse, resolve as urlResolve } from 'url'; -import { BehaviorSubject } from 'rxjs/BehaviorSubject'; - -import { MdRenderer } from './md-renderer'; - -import { SwaggerOperation, SwaggerParameter } from './swagger-typings'; -import { snapshot } from './helpers'; -import { OptionsService, Options } from '../services/options.service'; -import { WarningsService } from '../services/warnings.service'; - -function getDiscriminator(obj) { - return obj.discriminator || obj['x-extendedDiscriminator']; -} - -export interface DescendantInfo { - $ref: string; - name: string; - active?: boolean; - idx?: number; -} - -@Injectable() -export class SpecManager { - public _schema: any = {}; - public rawSpec: any; - public apiUrl: string; - public apiProtocol: string; - public swagger: string; - public basePath: string; - - public spec = new BehaviorSubject(null); - public specUrl: string; - private parser: any; - private options: Options; - - constructor(optionsService: OptionsService) { - this.options = optionsService.options; - } - - load(urlOrObject: string|Object) { - let promise = new Promise((resolve, reject) => { - this.parser = new JsonSchemaRefParser(); - this.parser.bundle(urlOrObject, {http: {withCredentials: false}}) - .then(schema => { - if (typeof urlOrObject === 'string') { - this.specUrl = urlOrObject; - } - this.rawSpec = schema; - this._schema = snapshot(schema); - try { - this.init(); - this.spec.next(this._schema); - resolve(this._schema); - } catch(err) { - reject(err); - } - }, err => reject(err)); - }); - - return promise; - } - - /* calculate common used values */ - init() { - let urlParts = this.specUrl ? urlParse(urlResolve(window.location.href, this.specUrl)) : {}; - let schemes = this._schema.schemes; - let protocol; - if (!schemes || !schemes.length) { - // url parser incudles ':' in protocol so remove it - protocol = urlParts.protocol ? urlParts.protocol.slice(0, -1) : 'http'; - } else { - protocol = schemes[0]; - if (protocol === 'http' && schemes.indexOf('https') >= 0) { - protocol = 'https'; - } - } - - let host = this._schema.host || urlParts.host; - this.basePath = this._schema.basePath || ''; - this.apiUrl = protocol + '://' + host + this.basePath; - this.apiProtocol = protocol; - if (this.apiUrl.endsWith('/')) { - this.apiUrl = this.apiUrl.substr(0, this.apiUrl.length - 1); - } - - this.preprocess(); - } - - preprocess() { - let mdRender = new MdRenderer(); - if (!this._schema.info) { - throw Error('Specification Error: Required field "info" is not specified at the top level of the specification'); - } - if (!this._schema.info.description) this._schema.info.description = ''; - if (this._schema.securityDefinitions && !this.options.noAutoAuth) { - let SecurityDefinitions = - require('../components/SecurityDefinitions/security-definitions').SecurityDefinitions; - mdRender.addPreprocessor(SecurityDefinitions.insertTagIntoDescription); - } - this._schema.info['x-redoc-html-description'] = mdRender.renderMd(this._schema.info.description); - this._schema.info['x-redoc-markdown-headers'] = mdRender.headings; - } - - get schema() { - return this._schema; - } - - set schema(val:any) { - this._schema = val; - this.spec.next(this._schema); - } - - byPointer(pointer) { - let res = null; - if (pointer == undefined) return null; - try { - res = JsonPointer.get(this._schema, decodeURIComponent(pointer)); - } catch(e) { - // if resolved from outer files simple jsonpointer.get fails to get correct schema - if (pointer.charAt(0) !== '#') pointer = '#' + pointer; - try { - res = this.parser.$refs.get(decodeURIComponent(pointer)); - } catch(e) { /* skip */ } - } - return res; - } - - resolveRefs(obj) { - Object.keys(obj).forEach(key => { - if (obj[key].$ref) { - let resolved = this.byPointer(obj[key].$ref); - resolved._pointer = obj[key].$ref; - obj[key] = resolved; - } - }); - return obj; - } - - getOperationParams(operationPtr:string):SwaggerParameter[] { - /* inject JsonPointer into array elements */ - function injectPointers(array:SwaggerParameter[], root) { - if (!Array.isArray(array)) { - throw new Error(`parameters must be an array. Got ${typeof array} at ${root}`); - } - return array.map((element, idx) => { - element._pointer = JsonPointer.join(root, idx); - return element; - }); - } - - // accept pointer directly to parameters as well - if (JsonPointer.baseName(operationPtr) === 'parameters') { - operationPtr = JsonPointer.dirName(operationPtr); - } - - //get path params - let pathParamsPtr = JsonPointer.join(JsonPointer.dirName(operationPtr), ['parameters']); - let pathParams:SwaggerParameter[] = this.byPointer(pathParamsPtr) || []; - - let operationParamsPtr = JsonPointer.join(operationPtr, ['parameters']); - let operationParams:SwaggerParameter[] = this.byPointer(operationParamsPtr) || []; - pathParams = injectPointers(pathParams, pathParamsPtr); - operationParams = injectPointers(operationParams, operationParamsPtr); - - // resolve references - operationParams = this.resolveRefs(operationParams); - pathParams = this.resolveRefs(pathParams); - return operationParams.concat(pathParams); - } - - getTagsMap() { - let tags = this._schema.tags || []; - var tagsMap = {}; - for (let tag of tags) { - tagsMap[tag.name] = { - description: tag.description, - 'x-traitTag': tag['x-traitTag'] || false - }; - } - - return tagsMap; - } - - findDerivedDefinitions(defPointer: string, schema?: any): DescendantInfo[] { - let definition = schema || this.byPointer(defPointer); - if (!definition) throw new Error(`Can't load schema at ${defPointer}`); - if (!definition.discriminator && !definition['x-extendedDiscriminator']) return []; - - let globalDefs = this._schema.definitions || {}; - let res:DescendantInfo[] = []; - - - // from the spec: When used, the value MUST be the name of this schema or any schema that inherits it. - // but most of people use it as an abstract class so here is workaround to allow using it other way - // check if parent definition name is in the enum of possible values - if (definition.discriminator) { - let prop = definition.properties[definition.discriminator]; - if (prop && prop.enum && prop.enum.indexOf(JsonPointer.baseName(defPointer)) > -1) { - res.push({ - name: JsonPointer.baseName(defPointer), - $ref: defPointer - }); - } - } - - let extendedDiscriminatorProp = definition['x-extendedDiscriminator']; - - let pointers; - if (definition['x-derived-from']) { - // support inherited discriminator o_O - let derivedDiscriminator = definition['x-derived-from'].filter(ptr => { - if (!ptr) return false; - let def = this.byPointer(ptr); - return def && def.discriminator; - }); - pointers = [defPointer, ...derivedDiscriminator]; - } else { - pointers = [defPointer]; - } - - - for (let defName of Object.keys(globalDefs)) { - let def = globalDefs[defName]; - if (!def.allOf && - !def['x-derived-from']) continue; - let subTypes = def['x-derived-from'] || - def.allOf.map(subType => subType._pointer || subType.$ref); - - let idx = -1; - - for (let ptr of pointers) { - idx = subTypes.findIndex(ref => ptr && ref === ptr); - if (idx >= 0) break; - } - - if (idx < 0) continue; - - let derivedName; - if (extendedDiscriminatorProp) { - let subDefs = def.allOf || []; - for (let def of subDefs) { - let prop = def.properties && def.properties[extendedDiscriminatorProp]; - if (prop && prop.enum && prop.enum.length === 1) { - derivedName = prop.enum[0]; - break; - } - } - if (derivedName == undefined) { - WarningsService.warn(`Incorrect usage of x-extendedDiscriminator at ${defPointer}: ` - + `can't find corresponding enum with single value in definition "${defName}"`); - continue; - } - } else { - derivedName = defName; - } - - res.push({name: derivedName, $ref: `#/definitions/${defName}`}); - } - return res; - } - - getDescendant(descendant:DescendantInfo, componentSchema:any) { - let res; - if (!getDiscriminator(componentSchema) && componentSchema.allOf) { - // discriminator inherited from parents - // only one discriminator and only one level of inheritence is supported at the moment - res = Object.assign({}, componentSchema); - let idx = res.allOf.findIndex(subSpec => !!getDiscriminator(subSpec)); - res.allOf[idx] = this.byPointer(descendant.$ref); - } else { - // this.pointer = activeDescendant.$ref; - res = this.byPointer(descendant.$ref); - } - return res; - } - -} diff --git a/lib/utils/swagger-defs.ts b/lib/utils/swagger-defs.ts deleted file mode 100644 index b1c76f54..00000000 --- a/lib/utils/swagger-defs.ts +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -export const operations = new Set(['get', 'put', 'post', 'delete', 'options', 'head', 'patch']); - -export const keywordTypes = { - multipleOf: 'number', - maximum: 'number', - exclusiveMaximum: 'number', - minimum: 'number', - exclusiveMinimum: 'number', - - maxLength: 'string', - minLength: 'string', - pattern: 'string', - - items: 'array', - maxItems: 'array', - minItems: 'array', - uniqueItems: 'array', - - maxProperties: 'object', - minProperties: 'object', - required: 'object', - additionalProperties: 'object', - properties: 'object' -}; diff --git a/lib/utils/swagger-typings.ts b/lib/utils/swagger-typings.ts deleted file mode 100644 index a9cd2805..00000000 --- a/lib/utils/swagger-typings.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - Operation, - Parameter, - Schema, - BodyParameter, - HeaderParameter, - QueryParameter, - FormDataParameter, - Spec, - Response -} from 'swagger-schema-official'; - -export interface RedocInjectedPointer { - _pointer?: string; -} - -export interface SwaggerOperation extends Operation, RedocInjectedPointer {} -export interface SwaggerBodyParameter extends BodyParameter, RedocInjectedPointer {} -export interface SwaggerHeaderParameter extends HeaderParameter, RedocInjectedPointer {} -export interface SwaggerQueryParameter extends QueryParameter, RedocInjectedPointer {} -export interface SwaggerFormDataParameter extends FormDataParameter, RedocInjectedPointer {} -export type SwaggerParameter = SwaggerBodyParameter | SwaggerHeaderParameter | SwaggerQueryParameter | SwaggerFormDataParameter; -export interface SwaggerSchema extends Schema, RedocInjectedPointer {} -export { Spec as SwaggerSpec, Response as SwaggerResponse }; diff --git a/lib/vendor.ts b/lib/vendor.ts deleted file mode 100644 index 2aef3234..00000000 --- a/lib/vendor.ts +++ /dev/null @@ -1,57 +0,0 @@ -import 'prismjs'; -import 'prismjs/components/prism-actionscript.js'; -import 'prismjs/components/prism-c.js'; -import 'prismjs/components/prism-cpp.js'; -import 'prismjs/components/prism-csharp.js'; -import 'prismjs/components/prism-php.js'; -import 'prismjs/components/prism-coffeescript.js'; -import 'prismjs/components/prism-go.js'; -import 'prismjs/components/prism-haskell.js'; -import 'prismjs/components/prism-java.js'; -import 'prismjs/components/prism-lua.js'; -import 'prismjs/components/prism-matlab.js'; -import 'prismjs/components/prism-perl.js'; -import 'prismjs/components/prism-python.js'; -import 'prismjs/components/prism-r.js'; -import 'prismjs/components/prism-ruby.js'; -import 'prismjs/components/prism-bash.js'; -import 'prismjs/components/prism-swift.js'; -import 'prismjs/components/prism-objectivec.js'; -import 'prismjs/components/prism-scala.js'; -import 'prismjs/components/prism-markup.js'; // xml - -import 'dropkickjs/build/css/dropkick.css'; -import 'prismjs/themes/prism-dark.css'; -import 'hint.css/hint.base.css'; - -interface Element { - scrollIntoViewIfNeeded(centerIfNeeded?: boolean): void; -}; - -if (!(Element).prototype.scrollIntoViewIfNeeded) { - (Element).prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) { - centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded; - - var parent = this.parentNode, - parentComputedStyle = window.getComputedStyle(parent, null), - parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width')), - parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width')), - overTop = this.offsetTop - parent.offsetTop < parent.scrollTop, - overBottom = (this.offsetTop - parent.offsetTop + this.clientHeight - parentBorderTopWidth) > (parent.scrollTop + parent.clientHeight), - overLeft = this.offsetLeft - parent.offsetLeft < parent.scrollLeft, - overRight = (this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + parent.clientWidth), - alignWithTop = overTop && !overBottom; - - if ((overTop || overBottom) && centerIfNeeded) { - parent.scrollTop = this.offsetTop - parent.offsetTop - parent.clientHeight / 2 - parentBorderTopWidth + this.clientHeight / 2; - } - - if ((overLeft || overRight) && centerIfNeeded) { - parent.scrollLeft = this.offsetLeft - parent.offsetLeft - parent.clientWidth / 2 - parentBorderLeftWidth + this.clientWidth / 2; - } - - if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) { - this.scrollIntoView(alignWithTop); - } - }; -} diff --git a/package.json b/package.json index 10f0d117..30412e7a 100644 --- a/package.json +++ b/package.json @@ -1,148 +1,112 @@ { + "private": true, "name": "redoc", - "description": "Swagger-generated API Reference Documentation", - "version": "1.19.1", - "repository": { - "type": "git", - "url": "git://github.com/Rebilly/ReDoc" - }, - "engines": { - "node": ">=6.9", - "npm": ">=3.0.0" - }, - "main": "dist/redoc.module.js", - "module": "dist/redoc.module.js", - "types": "dist/redoc.module.d.ts", + "version": "2.0.0", + "description": "ReDoc", + "main": "lib/index.js", "scripts": { - "start": - "webpack-dev-server --config build/webpack.dev.js --content-base demo", - "start:prod": "NODE_ENV=production npm start", - "test": "npm run lint && node ./build/run_tests.js", - "lint": "tslint -e \"lib/**/*{ngfactory|css.shim}.ts\" lib/**/*.ts", - "unit": "karma start", - "pree2e": "npm run build:prod && npm run e2e-copy", - "e2e": "run-p -r protractor e2e-server", - "protractor": "protractor", - "preprotractor": "npm run webdriver", - "e2e-server": "http-server -p 3000 tests/e2e", - "e2e-copy": "cp dist/redoc.min.js tests/e2e/", - "webdriver": "webdriver-manager update", - "deploy": - "node ./build/prepare_deploy.js && deploy-to-gh-pages --update demo", - "branch-release": "git reset --hard && branch-release", - "clean": "rimraf dist .tmp compiled lib/**/*.css", - "ngc": "ngc -p tsconfig.json", - "inline": "ng2-inline -o .tmp -r --compress \"lib/**/*.ts\"", - "build:module": - "npm run build:sass && npm run inline && ngc -p tsconfig.aot.json && npm run module:css", - "module:css": "node build/join-module-css.js", - "webpack:prod": "webpack --config build/webpack.prod.js --profile --bail", - "build:sass": "node-sass -q -o lib lib", - "build:prod": "npm run build:sass && npm run ngc && npm run webpack:prod", - "build-dist": "npm run build:prod", - "build:all": "npm run clean && npm run build:prod && npm run build:module", - "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 1", - "stats": "webpack --config build/webpack.prod.js --json > stats.json" + "start": "webpack-dev-server --hot", + "start:perf": "webpack-dev-server --env.prod --env.perf", + "start:prod": "webpack-dev-server --env.prod", + "dev": "webpack-dashboard -- webpack-dev-server --hot", + "test": "jest", + "bundle": "webpack", + "bundle:prod": "webpack -p -env.prod", + "prettier": "prettier --write \"src/**/*.{ts,tsx}\"" }, - "keywords": [ - "OpenAPI", - "OpenAPI Specification", - "Swagger", - "JSON-Schema", - "API", - "REST", - "documentation", - "Angular 2" - ], - "author": "Roman Hotsiy", + "author": "", "license": "MIT", "devDependencies": { - "@angular/common": "^4.4.3", - "@angular/compiler": "^4.4.3", - "@angular/compiler-cli": "^4.4.3", - "@angular/core": "^4.4.3", - "@angular/platform-browser": "^4.4.3", - "@angular/platform-browser-dynamic": "^4.4.3", - "@angular/platform-server": "^4.4.3", - "@types/jasmine": "^2.6.0", - "@types/requirejs": "^2.1.31", - "@types/should": "^11.2.0", - "@types/swagger-schema-official": "^2.0.6", - "@types/webpack": "^3.0.11", - "angular2-inline-template-style": "^1.1.0", - "angular2-template-loader": "^0.6.2", - "awesome-typescript-loader": "^3.2.3", - "branch-release": "^1.0.3", - "chalk": "^2.1.0", - "codelyzer": "^3.2.0", - "conventional-changelog-cli": "^1.3.3", - "css-loader": "^0.28.7", - "deploy-to-gh-pages": "^1.3.3", - "exports-loader": "^0.6.4", - "http-server": "^0.10.0", - "istanbul-instrumenter-loader": "^3.0.0", - "jasmine-core": "^2.8.0", - "jasmine-spec-reporter": "^4.2.1", - "karma": "^1.7.1", - "karma-chrome-launcher": "^2.2.0", - "karma-coverage": "^1.1.1", - "karma-coveralls": "^1.1.2", - "karma-jasmine": "^1.0.2", - "karma-mocha-reporter": "^2.2.4", - "karma-remap-coverage": "^0.1.4", - "karma-should": "^1.0.0", - "karma-sinon": "^1.0.4", - "karma-sourcemap-loader": "^0.3.7", - "karma-webpack": "^2.0.4", - "node-sass": "^4.5.3", - "npm-run-all": "^4.1.1", - "protractor": "^5.1.1", - "raw-loader": "^0.5.1", - "rimraf": "^2.6.2", - "rxjs": "^5.4.3", - "sass-loader": "^6.0.6", - "shelljs": "^0.7.7", - "should": "^13.1.0", - "sinon": "^3.3.0", + "@types/enzyme": "^2.8.10", + "@types/enzyme-to-json": "^1.5.0", + "@types/jest": "^20.0.8", + "@types/json-pointer": "^1.0.30", + "@types/prismjs": "^1.6.4", + "@types/prop-types": "^15.5.2", + "@types/react": "^16.0.9", + "@types/react-dom": "^16.0.0", + "@types/react-hot-loader": "^3.0.3", + "@types/react-tabs": "^1.0.2", + "@types/recompose": "^0.24.1", + "@types/webpack": "^3.0.5", + "@types/webpack-env": "^1.13.0", + "awesome-typescript-loader": "^3.2.2", + "enzyme": "^2.9.1", + "enzyme-to-json": "^2.0.0", + "extract-text-webpack-plugin": "^3.0.0", + "html-webpack-plugin": "^2.30.1", + "jest": "^21.1.0", + "mobx-react-devtools": "^4.2.15", + "postcss": "^6.0.8", + "postcss-cssnext": "^3.0.2", + "postcss-import": "^10.0.0", + "preact-deep-force-update": "^0.1.0", + "prettier": "^1.5.3", + "prettier-eslint": "^7.1.0", + "puppeteer": "^0.10.2", + "react-dev-utils": "^4.1.0", + "react-hot-loader": "3.0.0-beta.6", "source-map-loader": "^0.2.1", - "string-replace-webpack-plugin": "^0.1.3", "style-loader": "^0.18.2", - "swagger-schema-official": "^2.0.0-bab6bed", + "ts-jest": "^21.0.1", "tslint": "^5.7.0", - "typescript": "^2.5.2", - "webpack": "^3.6.0", - "webpack-dev-server": "^2.8.2", - "webpack-merge": "^4.1.0" - }, - "peerDependencies": { - "@angular/common": "^4.1.1", - "@angular/compiler": "^4.1.1", - "@angular/compiler-cli": "^4.1.1", - "@angular/core": "^4.1.1", - "@angular/forms": "^4.1.1", - "@angular/platform-browser": "^4.1.1", - "@angular/platform-browser-dynamic": "^4.1.1", - "@angular/platform-server": "^4.1.1", - "core-js": "^2.4.1", - "rxjs": "^5.3.1" + "typescript": "^2.4.2", + "webpack": "^3.4.1", + "webpack-dashboard": "^0.4.0", + "webpack-dev-server": "^2.6.1" }, "dependencies": { - "core-js": "^2.5.1", - "dropkickjs": "~2.1.10", - "hint.css": "^2.3.2", - "https-browserify": "^1.0.0", + "css-loader": "^0.28.7", + "decko": "^1.2.0", + "eventemitter3": "^2.0.3", + "hastscript": "^3.1.0", "json-pointer": "^0.6.0", "json-schema-ref-parser": "^3.3.1", - "lunr": "^1.0.0", - "mark.js": "github:julmot/mark.js", - "openapi-sampler": "^0.4.3", - "perfect-scrollbar": "^0.8.1", + "mobx": "^3.3.0", + "mobx-react": "^4.3.3", + "openapi-sampler": "^1.0.0-beta.1", + "postcss-loader": "^2.0.6", + "preact": "^8.2.5", + "preact-compat": "^3.17.0", "prismjs": "^1.8.1", - "remarkable": "1.7.1", - "scrollparent": "^2.0.1", + "prop-types": "^15.6.0", + "react": "^16.0.0", + "react-dom": "^16.0.0", + "react-dropdown": "^1.3.0", + "react-markdown": "^2.5.0", + "react-perfect-scrollbar": "^0.2.2", + "react-tabs": "^2.0.0", + "recompose": "^0.25.1", + "remarkable": "^1.7.1", "slugify": "^1.2.1", - "stream-http": "^2.6.1", - "ts-helpers": "^1.1.2", - "zone.js": "^0.8.17" + "styled-components": "^2.2.1" + }, + "jest": { + "mapCoverage": true, + "transform": { + "^.+\\.tsx?$": "/node_modules/ts-jest/preprocessor.js" + }, + "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", + "moduleFileExtensions": [ + "ts", + "tsx", + "js", + "jsx", + "json" + ], + "moduleNameMapper": { + "\\.(css|less)$": "/empty.js" + }, + "globals": { + "ts-jest": { + "skipBabel": true + } + } + }, + "prettier": { + "singleQuote": true, + "trailingComma": "all", + "printWidth": 100, + "parser": "typescript" } } diff --git a/perf/index.tsx b/perf/index.tsx new file mode 100644 index 00000000..6688f3fc --- /dev/null +++ b/perf/index.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import { render } from 'react-dom'; + +import { AppContainer } from 'react-hot-loader'; +import { Redoc, RedocProps } from '../src/components'; +import { AppStore } from '../src/services/AppStore'; + +const renderRoot = (Component: typeof Redoc, props: RedocProps) => + render( + + + , + document.getElementById('example'), + ); + +const props = { store: new AppStore() }; + +props.store.spec.parser.load('big-swagger.json').then(() => { + const t0 = performance.now(); + renderRoot(Redoc, props); + var t1 = performance.now(); + console.log({ time: t1 - t0 }); +}); diff --git a/perf/mount-time.js b/perf/mount-time.js new file mode 100644 index 00000000..652e604f --- /dev/null +++ b/perf/mount-time.js @@ -0,0 +1,54 @@ +const puppeteer = require('puppeteer'); +const crypto = require('crypto'); + +async function run() { + return await puppeteer + .launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] }) + .then(async browser => { + const page = await browser.newPage(); + let resolve; + const prom = new Promise(_resolve => { + resolve = _resolve; + }); + page.on('console', (obj) => { + if (obj && obj.time) { + resolve(obj.time); + } + }); + await page.goto('http://localhost:9090', { + waitUntil: 'networkidle', + }); + const res = await prom; + await browser.close() + return res; + }); +} + +function clearLine() { + process.stdout.clearLine(); + process.stdout.cursorTo(0); +} + +async function benchmark() { + const N = 5; + let sum = 0; + let max = 0; + let min = Number.MAX_SAFE_INTEGER; + for (let i = 0; i < N; i++) { + const res = await run(); + if (res > max) max = res; + if (res < min) min = res; + sum += res; + clearLine(); + process.stdout.write(`Running: ${i + 1} of ${N}`); + } + clearLine(); + const average = sum / N; + console.log('Completed ', N, 'runs'); + console.log('======================='); + console.log('Average Render Time: ', average); + console.log('Minimum Render Time: ', min); + console.log('Maximum Render Time: ', max); +} + +benchmark(); diff --git a/protractor.conf.js b/protractor.conf.js deleted file mode 100644 index 07c28f57..00000000 --- a/protractor.conf.js +++ /dev/null @@ -1,75 +0,0 @@ -'use strict'; -const loadJson = require('./tests/e2e/helpers').loadJson; -const travis = process.env.TRAVIS; - -let config = { - specs: ['./tests/e2e/**/*.e2e.js'], - baseUrl: 'http://localhost:3000', - framework: 'jasmine2', - onPrepare: function() { - var SpecReporter = require('jasmine-spec-reporter').SpecReporter; - // add jasmine spec reporter - jasmine.getEnv().addReporter(new SpecReporter({displaySpecDuration: true})); - // load APIs.guru list - return loadJson('https://api.apis.guru/v2/list.json').then((list) => { - global.apisGuruList = list; - return browser.getCapabilities().then(function (caps) { - browser.isIE = caps.get('browserName') === 'internet explorer'; - browser.isFF = caps.get('browserName') === 'firefox'; - }); - }); - }, - //directConnect: true, - useAllAngular2AppRoots: true, - allScriptsTimeout: 180000, - jasmineNodeOpts: { - showTiming: true, - showColors: true, - defaultTimeoutInterval: 180000, - print: function() {} - }, - multiCapabilities: [ - { browserName: 'chrome' } - // { browserName: 'firefox' } - ] -}; - -if (travis) { - config.sauceUser = process.env.SAUCE_USERNAME; - config.sauceKey = process.env.SAUCE_ACCESS_KEY; - config.sauceSeleniumAddres = 'localhost:4445/wd/hub'; - config.multiCapabilities = [{ - browserName: 'chrome', - 'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER, - build: process.env.TRAVIS_BUILD_NUMBER, - name: 'Redoc Chrome/Linux build ' + process.env.TRAVIS_BUILD_NUMBER - },{ - browserName: 'safari', - platform: 'OS X 10.11', - version: '9.0', - 'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER, - build: process.env.TRAVIS_BUILD_NUMBER, - name: 'Redoc Safari Latest/OSX build ' + process.env.TRAVIS_BUILD_NUMBER, - idleTimeout: 180, - maxDuration: 1800*2 - },{ - browserName: 'firefox', - platform: 'Windows 10', - version: '54.0', - 'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER, - build: process.env.TRAVIS_BUILD_NUMBER, - name: 'Redoc Firefox Latest/Win build ' + process.env.TRAVIS_BUILD_NUMBER, - maxDuration: 1800*2 - },{ - browserName: 'internet explorer', - version: '11.0', - 'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER, - build: process.env.TRAVIS_BUILD_NUMBER, - name: 'Redoc IE11/Win build ' + process.env.TRAVIS_BUILD_NUMBER, - maxDuration: 1800*2 - }]; -} else { - config.directConnect = true; -} - -exports.config = config; diff --git a/src/common-elements/dropdown.ts b/src/common-elements/dropdown.ts new file mode 100644 index 00000000..0ec99725 --- /dev/null +++ b/src/common-elements/dropdown.ts @@ -0,0 +1,112 @@ +import Dropdown from 'react-dropdown'; + +import styled from '../styled-components'; +import { withProps } from '../styled-components'; + +export type DropdownOption = { label: string; value: string }; + +export type DropdownProps = { + options: DropdownOption[]; + value: DropdownOption; + onChange: (val: DropdownOption) => void; +}; + +export const StyledDropdown = withProps(styled(Dropdown))` + min-width: 100px; + display: inline-block; + position: relative; + width: auto; + font-family: ${props => props.theme.headingsFont.family}; + + .Dropdown-control { + font-family: ${props => props.theme.headingsFont.family}; + position: relative; + font-size: .929em; + width: 100%; + line-height: 1.5em; + vertical-align: middle; + cursor: pointer; + border-color: rgba(38, 50, 56, 0.5); + color: #263238; + outline: none; + padding: 0.15em 1.5em 0.2em 0.5em; + border-radius: 2px; + border-width: 1px; + border-style: solid; + margin-top: 5px; + background: white; + + &:hover { + border-color: ${props => props.theme.colors.main}; + color: ${props => props.theme.colors.main}; + box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12); + } + } + + .Dropdown-arrow { + border-color: ${props => props.theme.colors.main} transparent transparent; + border-style: solid; + border-width: 0.35em 0.35em 0; + content: ' '; + display: block; + height: 0; + position: absolute; + right: 0.35em; + top: 50%; + margin-top: -0.125em; + width: 0; + } + + .Dropdown-menu { + position: absolute; + margin-top: 2px; + left: 0; + right: 0; + + z-index: 10; + min-width: 100px; + + background: white; + border: 1px solid rgba(38, 50, 56, 0.2); + box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12), 0px 2px 10px 0px rgba(34, 36, 38, 0.08); + } + + .Dropdown-option { + font-size: 0.9em; + color: #263238; + cursor: pointer; + padding: 0.4em; + + &.is-selected { + background-color: rgba(0, 0, 0, 0.05) + } + + &:hover { + background-color: rgba(38, 50, 56, 0.12) + } + } +` as React.ComponentClass; + +export const SimpleDropdown = styled(StyledDropdown)` + margin-left: 10px; + text-transform: none; + font-size: 0.929em; + + .Dropdown-control { + font-size: 1em; + border: none; + padding: 0 1.2em 0 0; + background: transparent; + + &:hover { + color: ${props => props.theme.colors.main}; + box-shadow: none; + } +`; + +export const MimeLabel = styled.span` + margin-left: 10px; + text-transform: none; + font-size: 0.929em; + color: black; +`; diff --git a/src/common-elements/fields-layout.ts b/src/common-elements/fields-layout.ts new file mode 100644 index 00000000..54252b2d --- /dev/null +++ b/src/common-elements/fields-layout.ts @@ -0,0 +1,156 @@ +import styled from '../styled-components'; +import { deprecatedCss } from './mixins'; +import { transparentizeHex } from '../utils/styled'; + +export const PropertiesTableCaption = styled.caption` + text-align: right; + font-size: 0.9em; + font-weight: normal; + color: ${props => transparentizeHex(props.theme.colors.text, 0.4)}; +`; + +export const PropertyCell = styled.td` + border-left: 1px solid ${props => props.theme.schemaView.linesColor}; + box-sizing: border-box; + position: relative; + padding: 10px 10px 10px 0; + + tr:first-of-type > &, + tr.last > & { + border-left-width: 0; + background-position: top left; + background-repeat: no-repeat; + background-size: 1px 100%; + } + + tr:first-of-type > & { + background-image: linear-gradient( + to bottom, + transparent 0%, + transparent 21px, + ${props => props.theme.schemaView.linesColor} 21px, + ${props => props.theme.schemaView.linesColor} 100% + ); + } + + tr.last > & { + background-image: linear-gradient( + to bottom, + ${props => props.theme.schemaView.linesColor} 0%, + ${props => props.theme.schemaView.linesColor} 21px, + transparent 21px, + transparent 100% + ); + } + + tr.last + tr > & { + border-left-color: transparent; + } + + tr:only-child > & { + background: none; + border-left-color: transparent; + } +`; + +export const PropertyCellWithInner = PropertyCell.extend` + padding: 0; +`; + +export const PropertyNameCell = PropertyCell.extend` + vertical-align: top; + line-height: 20px; + white-space: nowrap; + font-size: 0.929em; + font-weight: 300; + font-family: ${props => props.theme.headingsFont.family}; + + &.deprecated { + ${deprecatedCss}; + } +`; + +export const PropertyDetailsCell = styled.td` + border-bottom: 1px solid #9fb4be; + padding: 10px 0; + width: 75%; + box-sizing: border-box; + + tr.expanded & { + border-bottom: none; + } +`; + +export const PropertyBullet = styled.span` + color: ${props => props.theme.schemaView.linesColor}; + font-family: ${props => props.theme.code.fontFamily}; + margin-right: 10px; + + &::before { + content: ''; + display: inline-block; + vertical-align: middle; + width: 10px; + height: 1px; + background: ${props => props.theme.schemaView.linesColor}; + } + + &::after { + content: ''; + display: inline-block; + vertical-align: middle; + width: 1px; + background: ${props => props.theme.schemaView.linesColor}; + height: 7px; + } +`; + +export const InnerPropertiesWrap = styled.div` + padding: 1em; +`; + +export const PropertiesTable = styled.table` + border-collapse: collapse; + border-radius: 3px; + + border-spacing: 0; + width: 100%; + + > tr { + vertical-align: middle; + } + + & + ${InnerPropertiesWrap}, + & + ${InnerPropertiesWrap} + ${InnerPropertiesWrap} + ${InnerPropertiesWrap}, + & + ${InnerPropertiesWrap} + ${InnerPropertiesWrap} + ${InnerPropertiesWrap} + ${InnerPropertiesWrap} + ${InnerPropertiesWrap} { + margin: 1em 0 1em 1em; + background: #f0f0f0; + } + + & + ${InnerPropertiesWrap} + ${InnerPropertiesWrap}, + & + ${InnerPropertiesWrap} + ${InnerPropertiesWrap} + ${InnerPropertiesWrap} + ${InnerPropertiesWrap}, + & + ${InnerPropertiesWrap} + ${InnerPropertiesWrap} + ${InnerPropertiesWrap} + ${InnerPropertiesWrap} + ${InnerPropertiesWrap} + ${InnerPropertiesWrap} { + background: #ffffff; + } +`; diff --git a/src/common-elements/fields.ts b/src/common-elements/fields.ts new file mode 100644 index 00000000..1716b379 --- /dev/null +++ b/src/common-elements/fields.ts @@ -0,0 +1,74 @@ +import styled from 'styled-components'; +import { PropertyNameCell } from './fields-layout'; +import { transparentizeHex } from '../utils/styled'; + +export const ClickablePropertyNameCell = PropertyNameCell.extend` + cursor: pointer; + font-weight: bold; +`; + +export const FieldLabel = styled.span` + vertical-align: middle; + font-size: 0.929em; + line-height: 20px; +`; + +export const TypePrefix = styled(FieldLabel)` + color: ${props => transparentizeHex(props.theme.colors.text, 0.4)}; +`; + +export const TypeName = styled(FieldLabel)` + color: ${props => transparentizeHex(props.theme.colors.text, 0.4)}; +`; + +export const TypeFormat = TypeName; + +export const RequiredLabel = styled(FieldLabel)` + color: #e53935; + font-size: 13px; + font-weight: bold; +`; + +export const CircularLabel = styled(FieldLabel)` + color: #dd9900; + font-size: 13px; +`; + +export const NullableLabel = styled(FieldLabel)` + color: #3195a6; + font-size: 13px; +`; + +export const PatternLabel = styled(FieldLabel)` + color: #3195a6; + &::before, + &::after { + content: '/'; + font-weight: bold; + } +`; + +export const ExampleValue = styled.span` + font-family: ${props => props.theme.code.fontFamily}; + background-color: ${props => transparentizeHex(props.theme.colors.text, 0.02)}; + border: 1px solid ${props => transparentizeHex(props.theme.colors.text, 0.15)}; + margin: 0 3px; + padding: 0.4em 0.2em 0.2em; + font-size: 0.8em; + border-radius: 2px; + color: ${props => transparentizeHex(props.theme.colors.text, 0.9)}; + display: inline-block; + min-width: 20px; + text-align: center; + line-height: 1; + vertical-align: middle; +`; + +export const ConstraintItem = styled(FieldLabel)` + background-color: ${props => transparentizeHex(props.theme.colors.main, 0.15)}; + color: ${props => transparentizeHex(props.theme.colors.main, 0.6)}; + margin-right: 6px; + margin-left: 6px; + border-radius: 2px; + padding: 0 4px; +`; diff --git a/src/common-elements/headers.ts b/src/common-elements/headers.ts new file mode 100644 index 00000000..ef343b4b --- /dev/null +++ b/src/common-elements/headers.ts @@ -0,0 +1,33 @@ +import styled, { css } from '../styled-components'; + +const headerFontSize = { + '1': '1.85714em', + '2': '1.57143em', +}; + +export const headerCommonMixin = level => css` + font-family: ${props => props.theme.headingsFont.family}; + font-weight: 400; + font-size: ${headerFontSize[level]}; +`; + +export const H1 = styled.h1` + ${headerCommonMixin(1)}; + color: ${props => props.theme.colors.main}; + text-transform: capitalize; +`; + +export const H2 = styled.h2` + ${headerCommonMixin(2)}; + color: black; +`; + +export const UnderlinedHeader = styled.h5` + border-bottom: 1px solid rgba(38, 50, 56, 0.3); + margin: 1em 0 1em 0; + color: rgba(38, 50, 56, 0.5); + font-weight: normal; + text-transform: uppercase; + font-size: 0.929em; + line-height: 20px; +`; diff --git a/src/common-elements/index.ts b/src/common-elements/index.ts new file mode 100644 index 00000000..af43d104 --- /dev/null +++ b/src/common-elements/index.ts @@ -0,0 +1,9 @@ +export * from './panels'; +export * from './headers'; +export * from './linkify'; +export * from './shelfs'; +export * from './fields-layout'; +export * from './schema'; +export * from './dropdown'; +export * from './mixins'; +export * from './tabs'; diff --git a/src/common-elements/linkify.ts b/src/common-elements/linkify.ts new file mode 100644 index 00000000..43287742 --- /dev/null +++ b/src/common-elements/linkify.ts @@ -0,0 +1,31 @@ +import styled, { css } from 'styled-components'; + +export const linkifyMixin = className => css` + ${className} { + cursor: pointer; + margin-left: -20px; + padding: 0; + line-height: 1; + width: 20px; + display: inline-block; + } + ${className}:before { + content: ''; + width: 15px; + height: 15px; + background-size: contain; + background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg=='); + opacity: 0.5; + visibility: hidden; + display: inline-block; + vertical-align: middle; + } + + h1:hover > ${className}::before, h2:hover > ${className}::before, ${className}:hover::before { + visibility: visible; + } +`; + +export const ShareLink = styled.a` + ${linkifyMixin('&')}; +`; diff --git a/src/common-elements/mixins.ts b/src/common-elements/mixins.ts new file mode 100644 index 00000000..72a46801 --- /dev/null +++ b/src/common-elements/mixins.ts @@ -0,0 +1,15 @@ +import { css } from 'styled-components'; + +export const deprecatedCss = css` + text-decoration: line-through; + color: #bdccd3; +`; + +export const hoverColor = color => { + if (!color) return ''; + return css` + &:hover { + color: ${color}; + } + `; +}; diff --git a/src/common-elements/panels.ts b/src/common-elements/panels.ts new file mode 100644 index 00000000..6891ba77 --- /dev/null +++ b/src/common-elements/panels.ts @@ -0,0 +1,13 @@ +import styled from 'styled-components'; + +export const MiddlePanel = styled.div` + width: ${props => 100 - props.theme.rightPanel.width}%; + padding: ${props => props.theme.spacingUnit * 2}px; +`; + +export const RightPanel = styled.div` + width: ${props => props.theme.rightPanel.width}%; + color: #fafbfc; + bckground-color: ${props => props.theme.rightPanel.backgroundColor}; + padding: ${props => props.theme.spacingUnit * 2}px; +`; diff --git a/src/common-elements/perfect-scrollbar.ts b/src/common-elements/perfect-scrollbar.ts new file mode 100644 index 00000000..c1c460d8 --- /dev/null +++ b/src/common-elements/perfect-scrollbar.ts @@ -0,0 +1,8 @@ +import styled from '../styled-components'; +import 'perfect-scrollbar/dist/css/perfect-scrollbar.css'; + +import PerfectScrollbarOriginal from 'react-perfect-scrollbar'; + +export const PerfectScrollbar = styled(PerfectScrollbarOriginal)` + position: relative; +`; diff --git a/src/common-elements/schema.ts b/src/common-elements/schema.ts new file mode 100644 index 00000000..6ee18277 --- /dev/null +++ b/src/common-elements/schema.ts @@ -0,0 +1,56 @@ +import styled from '../styled-components'; +import { withProps } from '../styled-components'; + +export const OneOfList = styled.ul` + margin: 0; + padding: 0; + list-style: none; + display: inline-block; +`; + +export const OneOfLabel = styled.span` + font-size: 0.9em; + margin-right: 10px; + color: ${props => props.theme.colors.main}; + font-family: Montserrat; +} +`; + +export const OneOfButton = withProps<{ active: boolean }>(styled.li)` + display: inline-block; + margin-right: 10px; + font-size: 0.8em; + cursor: pointer; + border: 1px solid ${props => props.theme.colors.main}; + padding: 2px 10px; + + ${props => { + if (props.active) { + return ` + color: white; + background-color: ${props.theme.colors.main}; + `; + } else { + return ` + color: ${props.theme.colors.main}; + background-color: white; + `; + } + }} +`; + +export const ArrayOpenningLabel = styled.div` + font-size: 0.9em; + font-family: ${props => props.theme.code.fontFamily}; + &::after { + content: ' ['; + } +`; + +export const ArrayClosingLabel = styled.div` + font-size: 0.9em; + font-family: ${props => props.theme.code.fontFamily}; + &::after { + content: ']'; + } +`; diff --git a/src/common-elements/shelfs.tsx b/src/common-elements/shelfs.tsx new file mode 100644 index 00000000..496b00db --- /dev/null +++ b/src/common-elements/shelfs.tsx @@ -0,0 +1,57 @@ +import * as React from 'react'; +import styled, { withProps } from '../styled-components'; + +const directionMap = { + left: '90deg', + right: '-90deg', + up: '-180deg', + down: '0', +}; + +class _ShelfIcon extends React.PureComponent<{ + className?: string; + float?: 'left' | 'right'; + size?: string; + color?: string; + direction: 'left' | 'right' | 'up' | 'down'; + style?: React.CSSProperties; +}> { + render() { + return ( + + + + ); + } +} + +export const ShelfIcon = styled(_ShelfIcon)` + height: ${props => props.size || '18px'}; + width: ${props => props.size || '18px'}; + vertical-align: middle; + float: ${props => props.float || ''}; + transition: transform 0.2s ease-out; + transform: rotateZ(${props => directionMap[props.direction || 'down']}); + + polygon { + fill: ${props => (props.color && props.theme.colors[props.color]) || props.color}; + } +`; + +export const Badge = withProps<{ type: string }>(styled.span)` + display: inline-block; + padding: 0 5px; + margin: 0; + background-color: ${props => props.theme.colors[props.type]}; + color: white; + font-size: ${props => props.theme.code.fontSize};; + vertical-align: text-top; +`; diff --git a/src/common-elements/tabs.ts b/src/common-elements/tabs.ts new file mode 100644 index 00000000..38b7df83 --- /dev/null +++ b/src/common-elements/tabs.ts @@ -0,0 +1,88 @@ +import styled from '../styled-components'; +import { Tabs as ReactTabs } from 'react-tabs'; + +export { Tab, TabList, TabPanel } from 'react-tabs'; + +export const Tabs = styled(ReactTabs)` + > ul { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-wrap: wrap; + + > li { + padding: 5px 10px; + display: inline-block; + flex: 1; + background-color: rgba(0, 0, 0, 0.2); + cursor: pointer; + text-align: center; + outline: none; + + &.react-tabs__tab--selected { + background-color: #171e21; + } + + &:only-child { + flex: none; + min-width: 100px; + } + + &.tab-success { + color: ${props => props.theme.colors.success}; + } + + &.tab-redirect { + color: ${props => props.theme.colors.redirect}; + } + + &.tab-info { + color: ${props => props.theme.colors.info}; + } + + &.tab-error { + color: ${props => props.theme.colors.error}; + } + } + } + > .react-tabs__tab-panel { + background: #171e21; + & > div, + & > pre { + padding: 20px; + margin: 0; + } + } +`; + +export const SmallTabs = styled(Tabs)` + > ul { + display: block; + > li { + padding: 0; + margin-right: 20px; + font-size: 12px; + padding: 2px 0; + border-bottom: 1px dashed; + color: #787b7d; + backgrond: none; + + &:last-child { + margin-right: 0; + } + + &.react-tabs__tab--selected { + backgrond: none; + color: #babcbf; + } + } + } + > .react-tabs__tab-panel { + & > div, + & > pre { + padding: 10px 0; + margin: 0; + } + } +`; diff --git a/src/components/ApiInfo/ApiInfo.tsx b/src/components/ApiInfo/ApiInfo.tsx new file mode 100644 index 00000000..581a7c7c --- /dev/null +++ b/src/components/ApiInfo/ApiInfo.tsx @@ -0,0 +1,107 @@ +import * as React from 'react'; +import { observer } from 'mobx-react'; + +import { Markdown } from '../Markdown/Markdown'; +import { OpenAPIExternalDocumentation } from '../../types'; + +import { ApiInfoModel } from '../../services/models'; +import { SecurityDefs } from '../SecurityDefs/SecurityDefs'; + +import { + ApiInfoWrap, + ApiHeader, + DownloadButton, + InfoSpan, + InfoSpanBoxWrap, + InfoSpanBox, +} from './styled.elements'; + +interface ApiInfoProps { + info: ApiInfoModel; + externalDocs: OpenAPIExternalDocumentation; +} + +@observer +export class ApiInfo extends React.Component { + render() { + const { info, externalDocs } = this.props; + + const downloadFilename = info.downloadFileName; + const downloadLink = info.downloadLink; + + const license = + (info.license && ( + + License: {info.license.name} + + )) || + null; + + const website = + (info.contact && + info.contact.url && ( + + URL: {info.contact.url} + + )) || + null; + + const email = + (info.contact && + info.contact.email && ( + + {info.contact.name || 'E-mail'}:{' '} + {info.contact.email} + + )) || + null; + + const terms = + (info.termsOfService && ( + + Terms of Service + + )) || + null; + + return ( + + + {info.title} ({info.version}) + + {downloadLink && ( +

+ Download OpenAPI specification: + + Download + +

+ )} + + {((info.license || info.contact || info.termsOfService) && ( + + + {email} {website} {license} {terms} + + + )) || + null} + + {(externalDocs && ( +

+ {externalDocs.description || externalDocs.url} +

+ )) || + null} + +
+ +
+
+ ); + } +} diff --git a/src/components/ApiInfo/ApiInfoContainer.tsx b/src/components/ApiInfo/ApiInfoContainer.tsx new file mode 100644 index 00000000..2f0a03aa --- /dev/null +++ b/src/components/ApiInfo/ApiInfoContainer.tsx @@ -0,0 +1,19 @@ +import * as React from 'react'; +import { observer } from 'mobx-react'; +import * as PropTypes from 'prop-types'; +import { getContext } from 'recompose'; + +import { BaseContainerProps } from '../../types/components'; +import { ApiInfo } from './ApiInfo'; + +@observer +export class ApiInfoContainer extends React.Component { + render() { + const { info, externalDocs } = this.props.store.spec; + return ; + } +} + +export default getContext({ + store: PropTypes.object, +})(ApiInfoContainer); diff --git a/src/components/ApiInfo/styled.elements.ts b/src/components/ApiInfo/styled.elements.ts new file mode 100644 index 00000000..f0798302 --- /dev/null +++ b/src/components/ApiInfo/styled.elements.ts @@ -0,0 +1,47 @@ +import styled from '../../styled-components'; + +import { MiddlePanel, H1 } from '../../common-elements'; + +const delimiterWidth = 15; + +export const ApiInfoWrap = MiddlePanel; + +export const ApiHeader = H1.extend` + margin-top: 0; + margin-bottom: 0.5em; +`; + +export const DownloadButton = styled.a` + border: 1px solid ${props => props.theme.colors.main}; + color: ${props => props.theme.colors.main}; + font-weight: normal; + margin-left: 0.5em; + padding: 4px 8px 4px; + display: inline-block; + text-decoration: none; +`; + +export const InfoSpan = styled.span` + &::before { + content: '|'; + display: inline-block; + opacity: 0.5; + width: ${delimiterWidth}px; + text-align: center; + } + + &:last-child::after { + display: none; + } +`; + +export const InfoSpanBoxWrap = styled.div` + overflow: hidden; +`; + +export const InfoSpanBox = styled.div` + display: flex; + flex-wrap: wrap; + // hide separator on new lines: idea from https://stackoverflow.com/a/31732902/1749888 + margin-left: -${delimiterWidth}px; +`; diff --git a/src/components/ApiLogo/ApiLogo.tsx b/src/components/ApiLogo/ApiLogo.tsx new file mode 100644 index 00000000..dd65f8de --- /dev/null +++ b/src/components/ApiLogo/ApiLogo.tsx @@ -0,0 +1,28 @@ +import * as React from 'react'; +import { observer } from 'mobx-react'; +import * as PropTypes from 'prop-types'; +import { getContext } from 'recompose'; + +import { BaseContainerProps } from '../../types/components'; +import { LogoImgEl } from './styled.elements'; + +const LinkWrap = url => Component => {Component}; + +@observer +class ApiLogo extends React.Component { + render() { + const { spec } = this.props.store; + const info = spec.info!; + const logoInfo = info['x-logo']; + if (!logoInfo || !logoInfo.url) return null; + + const logo = ( + + ); + return info.contact && info.contact.url ? LinkWrap(info.contact.url)(logo) : logo; + } +} + +export default getContext({ + store: PropTypes.object, +})(ApiLogo); diff --git a/src/components/ApiLogo/styled.elements.ts b/src/components/ApiLogo/styled.elements.ts new file mode 100644 index 00000000..5089360a --- /dev/null +++ b/src/components/ApiLogo/styled.elements.ts @@ -0,0 +1,8 @@ +import styled from '../../styled-components'; + +export const LogoImgEl = styled.img` + max-height: ${props => props.theme.logo.maxHeight}; + width: auto; + display: inline-block; + max-width: 100%; +`; diff --git a/src/components/ContentItems/ContentContainer.tsx b/src/components/ContentItems/ContentContainer.tsx new file mode 100644 index 00000000..71947332 --- /dev/null +++ b/src/components/ContentItems/ContentContainer.tsx @@ -0,0 +1,19 @@ +import * as React from 'react'; +import * as PropTypes from 'prop-types'; +import { getContext } from 'recompose'; +import { observer } from 'mobx-react'; + +import { BaseContainerProps } from '../../types/components'; +import { ContentItems } from './ContentItems'; + +@observer +export class ContentContainer extends React.Component { + render() { + const items = this.props.store.menu.items; + return ; + } +} + +export default getContext({ + store: PropTypes.object, +})(ContentContainer); diff --git a/src/components/ContentItems/ContentItems.tsx b/src/components/ContentItems/ContentItems.tsx new file mode 100644 index 00000000..f0510bbd --- /dev/null +++ b/src/components/ContentItems/ContentItems.tsx @@ -0,0 +1,81 @@ +import * as React from 'react'; +import { observer } from 'mobx-react'; + +import { SECTION_ATTR } from '../../services/MenuStore'; +import { Markdown } from '../Markdown/Markdown'; + +import { H1, MiddlePanel, ShareLink } from '../../common-elements'; +import { Operation } from '../Operation/Operation'; +import { ContentItemModel } from '../../services/MenuBuilder'; +import { OperationModel } from '../../services/models'; + +@observer +export class ContentItems extends React.Component<{ + items: ContentItemModel[]; +}> { + render() { + const items = this.props.items; + if (items.length === 0) return null; + return items.map(item => ); + } +} + +type ContentItemProps = { + item: ContentItemModel; +}; + +@observer +export class ContentItem extends React.Component { + render() { + const item = this.props.item; + let content; + const { type } = item; + switch (type) { + case 'group': + content = null; + break; + case 'tag': + content = ; + break; + case 'section': + return null; + case 'operation': + content = ; + break; + default: + throw new Error('Unknown item type'); + } + + return [ +
+ {content} +
, + (item as any).items && , + ]; + } +} + +@observer +export class TagItem extends React.Component { + render() { + const { name, description } = this.props.item; + return ( + +

+ + {name} +

+ {description !== undefined && } +
+ ); + } +} + +@observer +export class OperationItem extends React.Component<{ + item: OperationModel; +}> { + render() { + return ; + } +} diff --git a/src/components/ContentRoot/ContentRoot.tsx b/src/components/ContentRoot/ContentRoot.tsx new file mode 100644 index 00000000..75770694 --- /dev/null +++ b/src/components/ContentRoot/ContentRoot.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; + +import ApiInfoContainer from '../ApiInfo/ApiInfoContainer'; +import { RedocWrap, MenuContent, ApiContent, Background } from './elements'; +import ApiLogo from '../ApiLogo/ApiLogo'; +import SideMenu from '../SideMenu/SideMenu'; +import ContentContainer from '../ContentItems/ContentContainer'; + +export class ContentRoot extends React.Component { + render() { + return ( + + + + + + +
+ + + + + + + ); + } +} diff --git a/src/components/ContentRoot/elements.tsx b/src/components/ContentRoot/elements.tsx new file mode 100644 index 00000000..4fd498da --- /dev/null +++ b/src/components/ContentRoot/elements.tsx @@ -0,0 +1,69 @@ +import { hoverColor } from '../../common-elements/mixins'; +import styled from '../../styled-components'; + +export const RedocWrap = styled.div` + overflow: hidden; + font-family: ${props => props.theme.baseFont.family}; + font-size: ${props => props.theme.baseFont.size}; + line-height: ${props => props.theme.baseFont.lineHeight}; + color: ${props => props.theme.colors.text}; + display: flex; + position: relative; + + -webkit-font-smoothing: ${props => props.theme.baseFont.smoothing}; + font-smoothing: ${props => props.theme.baseFont.smoothing}; + ${props => + (props.theme.baseFont.optimizeSpeed && 'text-rendering: optimizeSpeed !important') || ''}; + + tap-highlight-color: rgba(0, 0, 0, 0); + text-size-adjust: 100%; + + * { + box-sizing: border-box; + } + + .redoc-markdown h1 { + padding-top: ${props => props.theme.spacingUnit * 4}px; + } + + a { + text-decoration: none; + color: ${props => props.theme.links.color || props.theme.colors.main}; + ${props => hoverColor(props.theme.links.hover)}; + } +`; + +export const MenuContent = styled.div` + width: ${props => props.theme.menu.width}; + background-color: ${props => props.theme.menu.backgroundColor}; + overflow: hidden; + display: flex; + flex-direction: column; + transform: translateZ(0px); + height: 100vh; + position: fixed; +`; + +export const ApiContent = styled.div` + margin-left: ${props => props.theme.menu.width}; + z-index: 10; + position: relative; +`; + +export const Background = styled.div` + position: absolute; + left: ${props => props.theme.menu.width}; + top: 0; + bottom: 0; + right: 0; + z-index: 1; + + .redoc-background { + background-color: ${props => props.theme.rightPanel.backgroundColor}; + left: ${props => 100 - props.theme.rightPanel.width}%; + right: 0; + top: 0; + bottom: 0; + position: absolute; + } +`; diff --git a/src/components/DropdownOrLabel/DropdownOrLabel.tsx b/src/components/DropdownOrLabel/DropdownOrLabel.tsx new file mode 100644 index 00000000..91f039a0 --- /dev/null +++ b/src/components/DropdownOrLabel/DropdownOrLabel.tsx @@ -0,0 +1,16 @@ +import * as React from 'react'; + +import { MimeLabel, SimpleDropdown, DropdownProps } from '../../common-elements/dropdown'; + +export interface DropdownOrLabelProps extends DropdownProps { + Label?: React.ComponentClass; + Dropdown?: React.ComponentClass; +} + +export function DropdownOrLabel(props: DropdownOrLabelProps): JSX.Element { + const { Label = MimeLabel, Dropdown = SimpleDropdown } = props; + if (props.options.length === 1) { + return ; + } + return ; +} diff --git a/src/components/Endpoint/Endpoint.tsx b/src/components/Endpoint/Endpoint.tsx new file mode 100644 index 00000000..ced4b19e --- /dev/null +++ b/src/components/Endpoint/Endpoint.tsx @@ -0,0 +1,68 @@ +import * as React from 'react'; +import { OperationModel } from '../../services'; +import { ShelfIcon } from '../../common-elements'; +import { SelectOnClick } from '../SelectOnClick/SelectOnClick'; + +import { + OperationEndpointWrap, + EndpointInfo, + HttpVerb, + ServerRelativeURL, + ServersOverlay, + ServerItem, + ServerUrl, +} from './styled.elements'; + +export interface EndpointProps { + operation: OperationModel; +} + +export interface EndpointState { + expanded: boolean; +} + +export class Endpoint extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + expanded: false, + }; + } + + toggle = () => { + this.setState({ expanded: !this.state.expanded }); + }; + + render() { + const { operation } = this.props; + const { expanded } = this.state; + return ( + + + {operation.httpVerb}{' '} + {operation.path} + + + + {operation.servers.map(server => ( + +
{server.description}
+ + + {server.url} + {operation.path} + + +
+ ))} +
+
+ ); + } +} diff --git a/src/components/Endpoint/styled.elements.ts b/src/components/Endpoint/styled.elements.ts new file mode 100644 index 00000000..2620cce9 --- /dev/null +++ b/src/components/Endpoint/styled.elements.ts @@ -0,0 +1,72 @@ +import styled, { withProps } from '../../styled-components'; + +export const OperationEndpointWrap = styled.div` + cursor: pointer; + position: relative; +`; + +export const EndpointInfo = withProps<{ expanded?: boolean }>(styled.div)` + padding: 10px 30px 10px 20px; + border-radius: 4px 4px 0 0; + background-color: #222d32; + display: block; + font-weight: 300; + white-space: nowrap; + overflow-x: hidden; + text-overflow: ellipsis; + border: 1px solid transparent; + border-bottom-width: 0; + transition: border-color 0.25s ease; + + ${props => (props.expanded && 'border-color: #3c4448;') || ''} +`; + +export const HttpVerb = withProps<{ type: string }>(styled.span).attrs({ + className: props => `http-verb ${props.type}`, +})` + font-size: 0.929em; + line-height: 20px; + background-color: ${props => props.theme.colors.http[props.type] || '#999999'}; + color: #ffffff; + padding: 3px 10px; + text-transform: uppercase; + font-family: ${props => props.theme.headingsFont.family}; + margin: 0; +`; + +export const ServerRelativeURL = styled.span` + font-family: ${props => props.theme.headingsFont.family}; + color: #ffffff; + margin-left: 10px; +`; + +export const ServersOverlay = withProps<{ expanded: boolean }>(styled.div)` + position: absolute; + width: 100%; + z-index: 100; + background: #fafafa; + color: #263238; + box-sizing: border-box; + box-shadow: 4px 4px 6px rgba(0, 0, 0, 0.33); + overflow: hidden; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + transition: all 0.25s ease; + + ${props => (props.expanded ? '' : 'transform: translateY(-50%) scaleY(0);')} +`; + +export const ServerItem = styled.div` + padding: 10px; +`; + +export const ServerUrl = styled.div` + padding: 5px; + border: 1px solid #ccc; + background: #fff; + word-break: break-all; + color: ${props => props.theme.colors.main}; + > span { + color: ${props => props.theme.colors.text}; + } +`; diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx new file mode 100644 index 00000000..3faf492e --- /dev/null +++ b/src/components/ErrorBoundary.tsx @@ -0,0 +1,32 @@ +import * as React from 'react'; +import { Children } from 'react'; +import styled from '../styled-components'; + +const ErrorWrapper = styled.div` + padding: 20px; + color: red; +`; + +export class ErrorBoundary extends React.Component<{}, { error?: Error }> { + constructor(props) { + super(props); + this.state = { error: undefined }; + } + + componentDidCatch(error) { + this.setState({ error: error }); + return false; + } + + render() { + if (this.state.error) { + return ( + +

Something went wrong.

+ {this.state.error.message} +
+ ); + } + return Children.only(this.props.children); + } +} diff --git a/src/components/Fields/EnumValues.tsx b/src/components/Fields/EnumValues.tsx new file mode 100644 index 00000000..7b9634e2 --- /dev/null +++ b/src/components/Fields/EnumValues.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import { ExampleValue, FieldLabel } from '../../common-elements/fields'; + +export interface EnumValuesProps { + values: string[]; + type: string; +} + +export class EnumValues extends React.PureComponent { + render() { + const { values, type } = this.props; + if (!values.length) return null; + + return ( +
+ {type === 'array' ? 'Items' : ''} Enum: + {values.map((value, idx) => ( + {JSON.stringify(value)} + ))} +
+ ); + } +} diff --git a/src/components/Fields/Field.tsx b/src/components/Fields/Field.tsx new file mode 100644 index 00000000..37af1e22 --- /dev/null +++ b/src/components/Fields/Field.tsx @@ -0,0 +1,56 @@ +import { FieldDetails } from './FieldDetails'; +import * as React from 'react'; + +import { ClickablePropertyNameCell } from '../../common-elements/fields'; + +import { + PropertyBullet, + PropertyDetailsCell, + PropertyNameCell, +} from '../../common-elements/fields-layout'; + +import { ShelfIcon } from '../../common-elements/'; + +import { FieldModel } from '../../services/models'; + +export interface FieldProps { + className?: string; + onClick?: () => void; + isLast?: boolean; + showExamples?: boolean; + + field: FieldModel; + + renderDiscriminatorSwitch?: (opts: FieldProps) => JSX.Element; +} + +export class Field extends React.PureComponent { + render() { + const { className, field, isLast } = this.props; + const { name, expanded, deprecated } = field; + + const paramName = this.props.onClick ? ( + + + {name} + + + ) : ( + + + {name} + + ); + return ( + + {paramName} + + + + + ); + } +} diff --git a/src/components/Fields/FieldContstraints.tsx b/src/components/Fields/FieldContstraints.tsx new file mode 100644 index 00000000..64b3166e --- /dev/null +++ b/src/components/Fields/FieldContstraints.tsx @@ -0,0 +1,20 @@ +import * as React from 'react'; +import { ConstraintItem } from '../../common-elements/fields'; + +export interface ConstraintsViewProps { + constraints: string[]; +} + +export class ConstraintsView extends React.PureComponent { + render() { + if (this.props.constraints.length === 0) return null; + return ( + + {' '} + {this.props.constraints.map(constraint => ( + {constraint} + ))} + + ); + } +} diff --git a/src/components/Fields/FieldDetail.tsx b/src/components/Fields/FieldDetail.tsx new file mode 100644 index 00000000..655871cc --- /dev/null +++ b/src/components/Fields/FieldDetail.tsx @@ -0,0 +1,19 @@ +import { ExampleValue, FieldLabel } from '../../common-elements/fields'; +import * as React from 'react'; + +export interface FieldDetailProps { + value?: any; + label: string; +} + +export class FieldDetail extends React.PureComponent { + render() { + if (this.props.value === undefined) return null; + return ( +
+ {this.props.label} {' '} + {JSON.stringify(this.props.value)} +
+ ); + } +} diff --git a/src/components/Fields/FieldDetails.tsx b/src/components/Fields/FieldDetails.tsx new file mode 100644 index 00000000..7e4c35f6 --- /dev/null +++ b/src/components/Fields/FieldDetails.tsx @@ -0,0 +1,58 @@ +import * as React from 'react'; + +import { FieldProps } from './Field'; +import { Markdown } from '../Markdown/Markdown'; +import { EnumValues } from './EnumValues'; +import { FieldDetail } from './FieldDetail'; +import { ConstraintsView } from './FieldContstraints'; +import { + CircularLabel, + NullableLabel, + PatternLabel, + RequiredLabel, + TypeFormat, + TypeName, + TypePrefix, +} from '../../common-elements/fields'; + +import { Badge } from '../../common-elements/'; + +export class FieldDetails extends React.PureComponent { + render() { + const { showExamples, field, renderDiscriminatorSwitch } = this.props; + + const { schema, description, required, example, deprecated } = field; + + return ( +
+
+ {schema.typePrefix} + {schema.displayType} + {schema.format && ( + + {' <'} + {schema.format}> + + )} + + {schema.nullable && Nullable } + {schema.pattern && {schema.pattern}} + {required && Required } + {schema.isCircular && Circular } +
+ {deprecated && ( +
+ Deprecated +
+ )} + + {!renderDiscriminatorSwitch && }{' '} + {showExamples && } +
+ +
+ {(renderDiscriminatorSwitch && renderDiscriminatorSwitch(this.props)) || null} +
+ ); + } +} diff --git a/src/components/JsonViewer/JsonViewer.tsx b/src/components/JsonViewer/JsonViewer.tsx new file mode 100644 index 00000000..b1e9e973 --- /dev/null +++ b/src/components/JsonViewer/JsonViewer.tsx @@ -0,0 +1,49 @@ +import * as React from 'react'; +import styled from '../../styled-components'; + +import { jsonToHTML } from '../../utils/jsonToHtml'; +import { jsonStyles } from './style'; + +interface JsonProps { + data: any; + className?: string; +} + +class Json extends React.PureComponent { + node: HTMLElement | null; + + render() { + return ( +
(this.node = node)} + dangerouslySetInnerHTML={{ __html: jsonToHTML(this.props.data) }} + /> + ); + } + + clickListener = (event: MouseEvent) => { + var collapsed, + target = event.target as HTMLElement; + if (target.className === 'collapser') { + collapsed = target.parentElement!.getElementsByClassName('collapsible')[0]; + if (collapsed.parentElement.classList.contains('collapsed')) { + collapsed.parentElement.classList.remove('collapsed'); + } else { + collapsed.parentElement.classList.add('collapsed'); + } + } + }; + + componentDidMount() { + this.node!.addEventListener('click', this.clickListener); + } + + componentWillUnmount() { + this.node!.removeEventListener('click', this.clickListener); + } +} + +export const StyledJson = styled(Json)` + ${jsonStyles}; +`; diff --git a/src/components/JsonViewer/style.ts b/src/components/JsonViewer/style.ts new file mode 100644 index 00000000..3d759ca5 --- /dev/null +++ b/src/components/JsonViewer/style.ts @@ -0,0 +1,111 @@ +import { css } from '../../styled-components'; + +export const jsonStyles = css` + .redoc-json > .collapser { + display: none; + } + + font-family: Consolas, ${props => props.theme.code.fontFamily}; + font-size: ${props => props.theme.code.fontSize}; + + .type-null { + color: gray; + } + + .type-boolean { + color: firebrick; + } + + .type-number { + color: #4a8bb3; + } + + .type-string { + color: #66b16e; + & + a { + color: #66b16e; + text-decoration: underline; + } + } + + .callback-function { + color: gray; + } + + .collapser:after { + content: '-'; + cursor: pointer; + } + + .collapsed > .collapser:after { + content: '+'; + cursor: pointer; + } + + .ellipsis:after { + content: ' … '; + } + + .collapsible { + margin-left: 2em; + } + + .hoverable { + padding-top: 1px; + padding-bottom: 1px; + padding-left: 2px; + padding-right: 2px; + border-radius: 2px; + } + + .hovered { + background-color: rgba(235, 238, 249, 1); + } + + .collapser { + padding-right: 6px; + padding-left: 6px; + } + + ul { + list-style-type: none; + padding: 0px; + margin: 0px 0px 0px 26px; + } + + li { + position: relative; + display: block; + } + + .hoverable { + display: inline-block; + } + + .selected { + outline-style: solid; + outline-width: 1px; + outline-style: dotted; + } + + .collapsed > .collapsible { + display: none; + } + + .ellipsis { + display: none; + } + + .collapsed > .ellipsis { + display: inherit; + } + + .collapser { + position: absolute; + top: 1px; + left: -1.5em; + cursor: default; + user-select: none; + -webkit-user-select: none; + } +`; diff --git a/src/components/LoadingWrap/LoadingWrap.tsx b/src/components/LoadingWrap/LoadingWrap.tsx new file mode 100644 index 00000000..7c35558f --- /dev/null +++ b/src/components/LoadingWrap/LoadingWrap.tsx @@ -0,0 +1,37 @@ +import * as PropTypes from 'prop-types'; +import * as React from 'react'; +import { Children } from 'react'; +import { getContext } from 'recompose'; +import { observer } from 'mobx-react'; +import styled from '../../styled-components'; + +import { AppStore } from '../../services'; +import { Spinner } from './Spinner.svg'; + +const LoadingMessage = styled.div` + font-family: ${props => props.theme.baseFont.family}; + width: 100%; + text-align: center; + font-size: 25px; + margin: 30px 0 20px 0; + color: ${props => props.theme.colors.main}; +`; + +@observer +class LoadingWrap extends React.Component<{ store: AppStore }> { + render() { + if (this.props.store.spec.loaded) { + return Children.only(this.props.children); + } + return ( +
+ Loading ... + +
+ ); + } +} + +export default getContext<{ store: AppStore }>({ + store: PropTypes.object, +})(LoadingWrap); diff --git a/src/components/LoadingWrap/Spinner.svg.tsx b/src/components/LoadingWrap/Spinner.svg.tsx new file mode 100644 index 00000000..77cfcf0d --- /dev/null +++ b/src/components/LoadingWrap/Spinner.svg.tsx @@ -0,0 +1,36 @@ +import * as React from 'react'; +import styled, { keyframes } from '../../styled-components'; + +const _Spinner = (props: { className?: string }) => ( + + + + + + + + + + +); + +const rotate = keyframes` + 0% { + transform: rotate(0deg); } + 100% { + transform: rotate(360deg); + } +`; + +export const Spinner = styled(_Spinner)` + animation: 2s ${rotate} linear infinite; + width: 50px; + height: 50px; + content: ''; + display: inline-block; + margin-left: -25px; + + path { + fill: ${props => props.theme.colors.main}; + } +`; diff --git a/src/components/Markdown/Markdown.tsx b/src/components/Markdown/Markdown.tsx new file mode 100644 index 00000000..23a25a3a --- /dev/null +++ b/src/components/Markdown/Markdown.tsx @@ -0,0 +1,65 @@ +import * as React from 'react'; +import styled from '../../styled-components'; + +import { MarkdownRenderer } from '../../services'; + +import { markdownCss } from './styles'; + +interface MarkdownProps { + source: string; + dense?: boolean; + inline?: boolean; + className?: string; + raw?: boolean; + components?: { [name: string]: new () => React.Component }; +} + +class InternalMarkdown extends React.PureComponent { + constructor(props: MarkdownProps) { + super(props); + + if (props.components && props.inline) { + throw new Error(`Markdown Component: "inline" mode doesn't support "components"`); + } + } + + render() { + const renderer = new MarkdownRenderer(); + const { source, raw, className, components, inline, dense } = this.props; + const parts = components + ? renderer.renderMdWithComponents(source, components, raw) + : [renderer.renderMd(source, raw)]; + + if (!parts.length) return null; + + let appendClass = ' redoc-markdown'; + if (dense) appendClass += ' -dense'; + if (inline) appendClass += ' -inline'; + + if (inline) { + return ( + + ); + } + + return ( +
+ {parts.map( + (part, idx) => + typeof part === 'string' ? ( +
+ ) : ( + + ), + )} +
+ ); + } +} + +export const Markdown = styled(InternalMarkdown)` + ${markdownCss}; +`; diff --git a/src/components/Markdown/styles.ts b/src/components/Markdown/styles.ts new file mode 100644 index 00000000..179ace2e --- /dev/null +++ b/src/components/Markdown/styles.ts @@ -0,0 +1,116 @@ +import { css } from '../../styled-components'; +import { headerCommonMixin, linkifyMixin } from '../../common-elements'; + +export const markdownCss = css` + p { + &:last-of-type { + margin-bottom: 0; + } + } + + &.-dense p { + margin: 0; + } + + &.-inline p { + display: inline-block; + } + + h1 { + ${headerCommonMixin(1)}; + color: ${props => props.theme.colors.main}; + margin-top: 0; + } + + code { + color: #e53935; + background-color: rgba(38, 50, 56, 0.04); + font-family: ${props => props.theme.code.fontFamily}; + border-radius: 2px; + border: 1px solid rgba(38, 50, 56, 0.1); + padding: 0.1em 0.25em 0.2em; + font-size: ${props => props.theme.code.fontSize}; + } + + pre { + font-family: ${props => props.theme.code.fontFamily}; + white-space: pre-wrap; + background-color: #263238; + color: white; + padding: 12px 14px 15px 14px; + overflow-x: auto; + line-height: normal; + border-radius: 0px + border: 1px solid rgba(38, 50, 56, 0.1); + + code { + background-color: transparent; + color: white; + + &:before, + &:after { + content: none; + } + } + } + + blockquote { + margin: 0; + margin-bottom: 1em; + padding: 0 15px; + color: #777; + border-left: 4px solid #ddd; + } + + img { + max-width: 100%; + box-sizing: content-box; + } + + ul, + ol { + padding-left: 2em; + margin: 0; + margin-bottom: 1em; + font-family: ${props => props.theme.baseFont.family}; + font-weight: ${props => props.theme.baseFont.weight}; + line-height: ${props => props.theme.baseFont.lineHeight}; + > li { + margin: 1em 0; + } + } + + table { + display: block; + width: 100%; + overflow: auto; + word-break: normal; + word-break: keep-all; + border-collapse: collapse; + border-spacing: 0; + margin-top: 0.5em; + margin-bottom: 0.5em; + } + + table tr { + background-color: #fff; + border-top: 1px solid #ccc; + + &:nth-child(2n) { + background-color: #f8f8f8; + } + } + + table th, + table td { + padding: 6px 13px; + border: 1px solid #ddd; + } + + table th { + text-align: left; + font-weight: bold; + } + + ${linkifyMixin('.share-link')}; +`; diff --git a/src/components/MediaTypeSwitch/MediaTypesSwitch.tsx b/src/components/MediaTypeSwitch/MediaTypesSwitch.tsx new file mode 100644 index 00000000..9294d792 --- /dev/null +++ b/src/components/MediaTypeSwitch/MediaTypesSwitch.tsx @@ -0,0 +1,47 @@ +import { observer } from 'mobx-react'; +import * as React from 'react'; + +import { MediaContentModel, SchemaModel, MediaTypeModel } from '../../services/models'; +import { DropdownProps } from '../../common-elements/dropdown'; + +export interface MediaTypeChildProps { + schema: SchemaModel; + mime?: string; +} + +export interface MediaTypesSwitchProps { + content?: MediaContentModel; + renderDropdown: (props: DropdownProps) => JSX.Element; + children: (activeMime: MediaTypeModel) => JSX.Element; +} + +@observer +export class MediaTypesSwitch extends React.Component { + switchMedia = ({ value }) => { + this.props.content && this.props.content.activate(parseInt(value)); + }; + + render() { + const { content } = this.props; + if (!content || !content.mediaTypes) return null; + const activeMimeIdx = content.activeMimeIdx; + + let options = content.mediaTypes.map((mime, idx) => { + return { + label: mime.name, + value: idx.toString(), + }; + }); + + return ( +
+ {this.props.renderDropdown({ + value: options[activeMimeIdx], + options, + onChange: this.switchMedia, + })} + {this.props.children(content.active)} +
+ ); + } +} diff --git a/src/components/Operation/Operation.tsx b/src/components/Operation/Operation.tsx new file mode 100644 index 00000000..04a8cda5 --- /dev/null +++ b/src/components/Operation/Operation.tsx @@ -0,0 +1,58 @@ +import * as React from 'react'; +import styled from '../../styled-components'; + +import { observer } from 'mobx-react'; + +import { H2, MiddlePanel, RightPanel, Badge } from '../../common-elements'; + +import { Markdown } from '../Markdown/Markdown'; +import { Parameters } from '../Parameters/Parameters'; +import { ResponsesList } from '../Responses/ResponsesList'; +import { RequestSamples } from '../RequestSamples/RequestSamples'; +import { ResponseSamples } from '../ResponseSamples/ResponseSamples'; +import { ShareLink } from '../../common-elements/linkify'; +import { Endpoint } from '../Endpoint/Endpoint'; + +import { OperationModel as OperationType } from '../../services/models'; + +const OperationRow = styled.div` + border-bottom: 1px solid rgba(0, 0, 0, 0.2); + margin-bottom: 30px; + padding-bottom: 30px; + transform: translateZ(0); + display: flex; + overflow: hidden; + positioin: relative; +`; + +interface OperationProps { + operation: OperationType; +} + +@observer +export class Operation extends React.Component { + render() { + const { operation } = this.props; + + const { name: summary, description, deprecated } = operation; + + return ( + + +

+ + {summary} {deprecated && Deprecated } +

+ {description !== undefined && } + + +
+ + + + + +
+ ); + } +} diff --git a/src/components/Parameters/Parameters.tsx b/src/components/Parameters/Parameters.tsx new file mode 100644 index 00000000..924ea705 --- /dev/null +++ b/src/components/Parameters/Parameters.tsx @@ -0,0 +1,73 @@ +import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel'; +import { ParametersGroup } from './ParametersGroup'; +import * as React from 'react'; + +import { UnderlinedHeader } from '../../common-elements'; + +import { Schema } from '../Schema'; +import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch'; +import { FieldModel, RequestBodyModel } from '../../services/models'; + +import { MediaContentModel } from '../../services'; + +function safePush(obj, prop, item) { + if (!obj[prop]) obj[prop] = []; + obj[prop].push(item); +} + +interface ParametersProps { + parameters?: FieldModel[]; + body?: RequestBodyModel; +} + +const PARAM_PLACES = ['path', 'query', 'cookie', 'header']; + +export class Parameters extends React.PureComponent { + orderParams(params: FieldModel[]): Dict { + let res = {}; + params.forEach(param => { + safePush(res, param.in, param); + }); + return res; + } + + render() { + const { body, parameters = [] } = this.props; + if (body === undefined && parameters === undefined) { + return null; + } + + let paramsMap = this.orderParams(parameters); + + const paramsPlaces = parameters.length > 0 ? PARAM_PLACES : []; + + const bodyContent = body && body.content; + + return ( +
+ {paramsPlaces.map(place => ( + + ))} + {bodyContent && } +
+ ); + } +} + +function BodyContent(props: { content: MediaContentModel }): JSX.Element { + const { content } = props; + return ( + ( + + Request Body schema: + + )} + > + {({ schema }) => { + return ; + }} + + ); +} diff --git a/src/components/Parameters/ParametersGroup.tsx b/src/components/Parameters/ParametersGroup.tsx new file mode 100644 index 00000000..31d7c633 --- /dev/null +++ b/src/components/Parameters/ParametersGroup.tsx @@ -0,0 +1,34 @@ +import * as React from 'react'; + +import { UnderlinedHeader } from '../../common-elements'; +import { PropertiesTable } from '../../common-elements/fields-layout'; + +import { FieldModel } from '../../services/models'; +import { Field } from '../Fields/Field'; + +import { mapWithLast } from '../../utils'; + +export interface ParametersGroupProps { + place: string; + parameters: FieldModel[]; +} + +export class ParametersGroup extends React.PureComponent { + render() { + const { place, parameters } = this.props; + if (!parameters || !parameters.length) return null; + + return ( +
+ {place} Parameters + + + {mapWithLast(parameters, (field, isLast) => ( + + ))} + + +
+ ); + } +} diff --git a/src/components/PayloadSamples/MediaTypeSamples.tsx b/src/components/PayloadSamples/MediaTypeSamples.tsx new file mode 100644 index 00000000..cfb46b1b --- /dev/null +++ b/src/components/PayloadSamples/MediaTypeSamples.tsx @@ -0,0 +1,44 @@ +import * as React from 'react'; + +import { MediaTypeModel } from '../../services/models'; +import { StyledJson } from '../JsonViewer/JsonViewer'; +import { SourceCode } from '../SourceCode/SourceCode'; +import { SmallTabs, TabList, TabPanel, Tab } from '../../common-elements'; +import { NoSampleLabel } from './styled.elements'; + +import { isJsonLike, langFromMime } from '../../utils'; + +export interface PayloadSamplesProps { + mediaType: MediaTypeModel; +} + +export class MediaTypeSamples extends React.Component { + render() { + const examples = this.props.mediaType.examples || {}; + const mimeType = this.props.mediaType.name; + + const noSample = No sample; + const sampleView = isJsonLike(mimeType) + ? sample => + : sample => + (sample && ) || { noSample }; + + const examplesNames = Object.keys(examples); + if (examplesNames.length === 0) return noSample; + if (examplesNames.length > 1) { + return ( + + + {examplesNames.map(name => {examples[name].summary || name} )} + + {examplesNames.map(name => ( + {sampleView(examples[name].value)} + ))} + + ); + } else { + const name = examplesNames[0]; + return
{sampleView(examples[name].value)}
; + } + } +} diff --git a/src/components/PayloadSamples/PayloadSamples.tsx b/src/components/PayloadSamples/PayloadSamples.tsx new file mode 100644 index 00000000..50f6670c --- /dev/null +++ b/src/components/PayloadSamples/PayloadSamples.tsx @@ -0,0 +1,30 @@ +import { MediaTypeSamples } from './MediaTypeSamples'; +import * as React from 'react' +import { observer } from 'mobx-react'; + +import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch'; + +import { MediaContentModel } from '../../services/models'; +import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel'; +import { InvertedSimpleDropdown, MimeLabel } from './styled.elements'; + +export interface PayloadSamplesProps { + content: MediaContentModel; +} + +@observer +export class PayloadSamples extends React.Component { + render() { + const mimeContent = this.props.content; + if (mimeContent === undefined) return null; + + return ( + } + > + {mediaType => } + + ); + } +} diff --git a/src/components/PayloadSamples/styled.elements.ts b/src/components/PayloadSamples/styled.elements.ts new file mode 100644 index 00000000..6e2f8abf --- /dev/null +++ b/src/components/PayloadSamples/styled.elements.ts @@ -0,0 +1,35 @@ +import styled from '../../styled-components'; + +import { SimpleDropdown } from '../../common-elements'; + +export const MimeLabel = styled.div` + border-bottom: 1px solid rgba(255, 255, 255, 0.9); + margin: 0 0 10px 0; + display: block; + color: rgba(255, 255, 255, 0.8); +`; + +export const InvertedSimpleDropdown = styled(SimpleDropdown)` + border-bottom: 1px solid rgba(255, 255, 255, 0.9); + margin: 0 0 10px 0; + display: block; + + .Dropdown-control, + .Dropdown-control:hover { + color: rgba(255, 255, 255, 0.9); + + .Dropdown-arrow { + border-top-color: rgba(255, 255, 255, 0.9); + } + } + .Dropdown-menu { + margin: 0; + } +`; + + +export const NoSampleLabel = styled.div` + font-family: ${props => props.theme.code.fontFamily}; + font-size: 12px; + color: #ee807f; +`; diff --git a/src/components/Redoc.tsx b/src/components/Redoc.tsx new file mode 100644 index 00000000..5073583e --- /dev/null +++ b/src/components/Redoc.tsx @@ -0,0 +1,33 @@ +import * as React from 'react'; + +import { ContentRoot } from './ContentRoot/ContentRoot'; + +import { ThemeProvider } from '../styled-components'; +import defaultTheme from '../theme'; + +import LoadingWrap from './LoadingWrap/LoadingWrap'; +import { StoreProvider } from './StoreProvider'; +import { ErrorBoundary } from './ErrorBoundary'; + +export interface RedocProps { + specUrl?: string; + spec?: object; + theme?: any; + store?: any; +} + +export class Redoc extends React.Component { + render() { + return ( + + + + + + + + + + ); + } +} diff --git a/src/components/RequestSamples/RequestSamples.tsx b/src/components/RequestSamples/RequestSamples.tsx new file mode 100644 index 00000000..4dbd596e --- /dev/null +++ b/src/components/RequestSamples/RequestSamples.tsx @@ -0,0 +1,52 @@ +import { SourceCode } from '../SourceCode/SourceCode'; +import { PayloadSamples } from '../PayloadSamples/PayloadSamples'; +import * as React from 'react'; +import { observer } from 'mobx-react'; +import { OperationModel } from '../../services/models'; + +import { Tab, Tabs, TabList, TabPanel } from '../../common-elements'; + +export interface RequestSamplesProps { + operation: OperationModel; +} + +@observer +export class RequestSamples extends React.Component { + operation: OperationModel; + + visited = new Set(); + + render() { + const { operation } = this.props; + const requestBodyContent = operation.requestBody && operation.requestBody.content; + const hasBodySample = requestBodyContent && requestBodyContent.hasSample; + const samples = operation.codeSamples; + + const hasSamples = hasBodySample || samples.length > 0; + return ( + (hasSamples && ( +
+

Request samples

+ + + + {hasBodySample && Payload } + {samples.map(sample => {sample.lang})} + + {hasBodySample && ( + + + + )} + {samples.map(sample => ( + + + + ))} + +
+ )) || + null + ); + } +} diff --git a/src/components/ResponseSamples/ResponseSamples.tsx b/src/components/ResponseSamples/ResponseSamples.tsx new file mode 100644 index 00000000..fdf6ca1e --- /dev/null +++ b/src/components/ResponseSamples/ResponseSamples.tsx @@ -0,0 +1,65 @@ +import * as React from 'react'; +import { observer } from 'mobx-react'; + +import { MediaContentModel, OperationModel } from '../../services/models'; + +import { Tab, Tabs, TabList, TabPanel } from '../../common-elements'; +import { PayloadSamples } from '../PayloadSamples/PayloadSamples'; + +export interface ResponseSampleProps { + content: MediaContentModel; +} + +class ResponseSample extends React.Component { + render() { + return ; + } +} + +export interface ResponseSamplesProps { + operation: OperationModel; +} + +@observer +export class ResponseSamples extends React.Component { + operation: OperationModel; + + visited = new Set(); + + render() { + const { operation } = this.props; + let hasSuccessResponses = false; + const responses = operation.responses.filter(response => { + const code = response.code; + if (parseInt(code) >= 100 && parseInt(code) <= 399) { + hasSuccessResponses = true; + } + // filter only those with content + return response.content && response.content.hasSample; + }); + + return ( + (responses.length > 0 && ( +
+

Response samples

+ + + + {responses.map(response => ( + + {response.code} + + ))} + + {responses.map(response => ( + + + + ))} + +
+ )) || + null + ); + } +} diff --git a/src/components/Responses/Response.tsx b/src/components/Responses/Response.tsx new file mode 100644 index 00000000..b52449c8 --- /dev/null +++ b/src/components/Responses/Response.tsx @@ -0,0 +1,57 @@ +import * as React from 'react'; +import { observer } from 'mobx-react'; + +import { ResponseModel } from '../../services/models'; + +import { UnderlinedHeader } from '../../common-elements'; +import { Schema } from '../Schema'; +import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch'; +import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel'; + +import { ResponseHeaders } from './ResponseHeaders'; +import { ResponseDetailsWrap, StyledResponseTitle } from './styled.elements'; + +@observer +export class ResponseView extends React.Component<{ response: ResponseModel }> { + toggle = () => { + this.props.response.toggle(); + }; + + render() { + const { headers, type, description, code, expanded, content } = this.props.response; + const mimes = + content === undefined ? [] : content.mediaTypes.filter(mime => mime.schema !== undefined); + + const empty = headers.length === 0 && mimes.length === 0; + + return ( +
+ + {expanded && ( + + + ( + + Response Schema: + + )} + > + {({ schema }) => { + return ; + }} + + + )} +
+ ); + } +} diff --git a/src/components/Responses/ResponseHeaders.tsx b/src/components/Responses/ResponseHeaders.tsx new file mode 100644 index 00000000..275c4659 --- /dev/null +++ b/src/components/Responses/ResponseHeaders.tsx @@ -0,0 +1,30 @@ +import { PropertiesTable } from '../../common-elements/fields-layout'; +import * as React from 'react'; + +import { HeadersCaption } from './styled.elements'; +import { mapWithLast } from '../../utils'; +import { FieldModel } from '../../services/models'; +import { Field } from '../Fields/Field'; + +export interface ResponseHeadersProps { + headers?: FieldModel[]; +} + +export class ResponseHeaders extends React.PureComponent { + render() { + const { headers } = this.props; + if (headers === undefined || headers.length === 0) { + return null; + } + return ( + + Response Headers + + {mapWithLast(headers, (header, isLast) => ( + + ))} + + + ); + } +} diff --git a/src/components/Responses/ResponseTitle.tsx b/src/components/Responses/ResponseTitle.tsx new file mode 100644 index 00000000..ef1625fa --- /dev/null +++ b/src/components/Responses/ResponseTitle.tsx @@ -0,0 +1,34 @@ +import * as React from 'react'; + +import { Markdown } from '../Markdown/Markdown'; +import { ShelfIcon } from '../../common-elements'; + +export interface ResponseTitleProps { + code: string; + title: string; + type: string; + empty?: boolean; + opened?: boolean; + className?: string; + onClick?: () => void; +} + +export class ResponseTitle extends React.PureComponent { + render() { + const { title, type, empty, code, opened, className, onClick } = this.props; + return ( +
+ {!empty && ( + + )} + {code} + +
+ ); + } +} diff --git a/src/components/Responses/ResponsesList.tsx b/src/components/Responses/ResponsesList.tsx new file mode 100644 index 00000000..70a6a8f9 --- /dev/null +++ b/src/components/Responses/ResponsesList.tsx @@ -0,0 +1,31 @@ +import * as React from 'react'; +import styled from 'styled-components'; +import { ResponseView } from './Response'; +import { ResponseModel } from '../../services/models'; + +const ResponsesHeader = styled.h3` + font-size: 18px; + padding: 0.2em 0; + margin: 3em 0 1.1em; + color: #253137; + font-weight: normal; +`; + +export class ResponsesList extends React.PureComponent<{ + responses: ResponseModel[]; +}> { + render() { + const { responses } = this.props; + + if (!responses || responses.length === 0) return null; + + return ( +
+ Responses + {responses.map(response => { + return ; + })} +
+ ); + } +} diff --git a/src/components/Responses/styled.elements.ts b/src/components/Responses/styled.elements.ts new file mode 100644 index 00000000..632c2e77 --- /dev/null +++ b/src/components/Responses/styled.elements.ts @@ -0,0 +1,40 @@ +import styled from '../../styled-components'; + +import { ResponseTitle } from './ResponseTitle'; +import { UnderlinedHeader } from '../../common-elements'; +import { transparentizeHex } from '../../utils'; + +export const StyledResponseTitle = styled(ResponseTitle)` + padding: 10px; + border-radius: 2px; + margin-bottom: 4px; + line-height: 1.5em; + background-color: #f2f2f2; + cursor: pointer; + + color: ${props => props.theme.colors[props.type]}; + background-color: ${props => transparentizeHex(props.theme.colors[props.type], 0.08)}; + + ${props => + (props.empty && + ` +cursor: default; +&::before { + content: "—"; + font-weight: bold; + width: 1.5em; + text-align: center; + display: inline-block; +} +`) || + ''}; +`; + +export const ResponseDetailsWrap = styled.div` + padding: 10px; +`; + +export const HeadersCaption = UnderlinedHeader.withComponent('caption').extend` + text-align: left; + margin-top: 1em; +`; diff --git a/src/components/Schema/ArraySchema.tsx b/src/components/Schema/ArraySchema.tsx new file mode 100644 index 00000000..8d4fa594 --- /dev/null +++ b/src/components/Schema/ArraySchema.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; + +import { SchemaProps, Schema } from './Schema'; + +import { ArrayOpenningLabel, ArrayClosingLabel } from '../../common-elements'; + +export class ArraySchema extends React.PureComponent { + render() { + const itemsSchema = this.props.schema.items!; + return ( +
+ Array + + +
+ ); + } +} diff --git a/src/components/Schema/DiscriminatorDropdown.tsx b/src/components/Schema/DiscriminatorDropdown.tsx new file mode 100644 index 00000000..adc629b0 --- /dev/null +++ b/src/components/Schema/DiscriminatorDropdown.tsx @@ -0,0 +1,54 @@ +import * as React from 'react'; +import { observer } from 'mobx-react'; + +import { StyledDropdown, DropdownOption } from '../../common-elements/dropdown'; +import { SchemaModel } from '../../services/models'; + +@observer +export class DiscriminatorDropdown extends React.Component<{ + parent: SchemaModel; + enumValues: string[]; +}> { + sortOptions(options: DropdownOption[], enumValues: string[]): void { + if (enumValues.length === 0) return; + + const enumOrder = {}; + + enumValues.forEach((enumItem, idx) => { + enumOrder[enumItem] = idx; + }); + + options.sort((a, b) => { + return enumOrder[a.label] > enumOrder[b.label] ? 1 : -1; + }); + } + + render() { + const { parent, enumValues } = this.props; + if (parent.oneOf === undefined) { + return null; + } + + const options = parent.oneOf.map((subSchema, idx) => { + return { + value: idx.toString(), + label: subSchema.title, + }; + }); + + this.sortOptions(options, enumValues); + + return ( + + ); + } + + changeActiveChild = ({ value }) => { + const idx = parseInt(value); + this.props.parent.activateOneOf(idx); + }; +} diff --git a/src/components/Schema/ObjectSchema.tsx b/src/components/Schema/ObjectSchema.tsx new file mode 100644 index 00000000..1d0f7a69 --- /dev/null +++ b/src/components/Schema/ObjectSchema.tsx @@ -0,0 +1,80 @@ +import * as React from 'react'; +import { observer } from 'mobx-react'; + +import { SchemaModel, FieldModel } from '../../services/models'; + +import { Field } from '../Fields/Field'; +import { DiscriminatorDropdown } from './DiscriminatorDropdown'; +import { Schema, SchemaProps } from './Schema'; +import { + InnerPropertiesWrap, + PropertiesTable, + PropertiesTableCaption, + PropertyCellWithInner, +} from '../../common-elements/fields-layout'; + +import { mapWithLast } from '../../utils'; + +export interface ObjectSchemaProps extends SchemaProps { + discriminator?: { + fieldName: string; + parentSchema: SchemaModel; + }; +} + +@observer +export class ObjectSchema extends React.Component { + get parentSchema() { + return this.props.discriminator!.parentSchema; + } + + renderField(field: FieldModel, isLast: boolean, isDiscriminator: boolean = false) { + const withSubSchema = !field.schema.isPrimitive && !field.schema.isCircular; + return [ + field.toggle())) || undefined} + renderDiscriminatorSwitch={ + (isDiscriminator && + (() => ( + + ))) || + undefined + } + className={field.expanded ? 'expanded' : undefined} + showExamples={false} + />, + field.expanded && + withSubSchema && ( + + + + + + + + ), + ]; + } + + render() { + const { schema: { fields = [] }, showTitle, discriminator } = this.props; + + return ( + + {showTitle && {this.props.schema.title}} + + {mapWithLast(fields, (field, isLast) => + this.renderField( + field, + isLast, + discriminator && discriminator.fieldName === field.name, + ), + )} + + + ); + } +} diff --git a/src/components/Schema/OneOfSchema.tsx b/src/components/Schema/OneOfSchema.tsx new file mode 100644 index 00000000..25068985 --- /dev/null +++ b/src/components/Schema/OneOfSchema.tsx @@ -0,0 +1,33 @@ +import * as React from 'react'; +import { observer } from 'mobx-react'; + +import { OneOfButton, OneOfLabel, OneOfList } from '../../common-elements/schema'; +import { SchemaProps, Schema } from './Schema'; + +@observer +export class OneOfSchema extends React.Component { + render() { + const { schema: { oneOf }, schema } = this.props; + + if (oneOf === undefined) { + return null; + } + return ( +
+ {schema.oneOfType} + + {oneOf.map((subSchema, idx) => ( + schema.activateOneOf(idx)} + > + {subSchema.title || subSchema.displayType} + + ))} + + +
+ ); + } +} diff --git a/src/components/Schema/Schema.tsx b/src/components/Schema/Schema.tsx new file mode 100644 index 00000000..73410a4d --- /dev/null +++ b/src/components/Schema/Schema.tsx @@ -0,0 +1,74 @@ +import * as React from 'react'; +import { observer } from 'mobx-react'; + +import { FieldDetails } from '../Fields/FieldDetails'; +import { TypeName, CircularLabel } from '../../common-elements/fields'; + +import { SchemaModel } from '../../services/models'; + +import { ObjectSchema } from './ObjectSchema'; +import { OneOfSchema } from './OneOfSchema'; +import { ArraySchema } from './ArraySchema'; + +export interface SchemaProps { + schema: SchemaModel; + showTitle?: boolean; +} + +@observer +export class Schema extends React.Component> { + render() { + const { schema } = this.props; + if (!schema) return Schema not provided ; + const { type, oneOf, discriminatorProp, isCircular } = schema; + + if (isCircular) { + return ( +
+ {schema.displayType} + Circular +
+ ); + } + + if (discriminatorProp !== undefined) { + return ( + + ); + } + + if (oneOf !== undefined) { + return ; + } + + switch (type) { + case 'object': + return ; + case 'array': + return ; + } + + // TODO: maybe adjust FieldDetails to accept schema + return ( +
+ null, + expanded: false, + }} + /> +
+ ); + } +} diff --git a/src/components/Schema/__tests__/DiscriminatorDropdown.test.tsx b/src/components/Schema/__tests__/DiscriminatorDropdown.test.tsx new file mode 100644 index 00000000..c98859c9 --- /dev/null +++ b/src/components/Schema/__tests__/DiscriminatorDropdown.test.tsx @@ -0,0 +1,47 @@ +import { shallow } from 'enzyme'; +import toJson from 'enzyme-to-json'; +import * as React from 'react'; + +import { OpenAPIParser, Schema } from '../../../services'; +import { ObjectSchemaView, SchemaView } from '../Schema'; +import * as simpleDiscriminatorFixture from './fixtures/simple-discriminator.json'; + +describe('Components', () => { + describe('SchemaView', () => { + describe('discriminator', () => { + it('should correctly render SchemaView', () => { + const parser = new OpenAPIParser(); + parser.spec = simpleDiscriminatorFixture; + + const schema = new Schema( + parser, + { $ref: '#/components/schemas/Pet' }, + '#/components/schemas/Pet', + ); + const schemaView = shallow(); + expect(toJson(schemaView)).toMatchSnapshot(); + }); + + it('should correctly render discriminator dropdown', () => { + const parser = new OpenAPIParser(); + parser.spec = simpleDiscriminatorFixture; + + const schema = new Schema( + parser, + { $ref: '#/components/schemas/Pet' }, + '#/components/schemas/Pet', + ); + const schemaView = shallow( + , + ); + expect(toJson(schemaView)).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/src/components/Schema/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap b/src/components/Schema/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap new file mode 100644 index 00000000..478cfa5d --- /dev/null +++ b/src/components/Schema/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap @@ -0,0 +1,253 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Components SchemaView discriminator should correctly render SchemaView 1`] = ` + +`; + +exports[`Components SchemaView discriminator should correctly render discriminator dropdown 1`] = ` + + + Dog + + + + + + +`; diff --git a/src/components/Schema/__tests__/fixtures/simple-discriminator.json b/src/components/Schema/__tests__/fixtures/simple-discriminator.json new file mode 100644 index 00000000..f6abd9d4 --- /dev/null +++ b/src/components/Schema/__tests__/fixtures/simple-discriminator.json @@ -0,0 +1,46 @@ +{ + "components": { + "schemas": { + "Pet": { + "type": "object", + "required": ["type"], + "discriminator": { + "propertyName": "type" + }, + "properties": { + "type": { + "type": "string" + } + } + }, + "Dog": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/Pet" + } + ], + "properties": { + "packSize": { + "type": "number" + } + } + }, + "Cat": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/Pet" + }, + { + "properties": { + "packSize": { + "type": "number" + } + } + } + ] + } + } + } +} diff --git a/src/components/Schema/index.ts b/src/components/Schema/index.ts new file mode 100644 index 00000000..5adcc326 --- /dev/null +++ b/src/components/Schema/index.ts @@ -0,0 +1 @@ +export * from './Schema'; diff --git a/src/components/SecurityDefs/SecurityDefs.tsx b/src/components/SecurityDefs/SecurityDefs.tsx new file mode 100644 index 00000000..caefc5e8 --- /dev/null +++ b/src/components/SecurityDefs/SecurityDefs.tsx @@ -0,0 +1,7 @@ +import * as React from 'react'; + +export class SecurityDefs extends React.PureComponent { + render() { + return

Security Definitions here

; + } +} diff --git a/src/components/SelectOnClick/SelectOnClick.tsx b/src/components/SelectOnClick/SelectOnClick.tsx new file mode 100644 index 00000000..87ba359a --- /dev/null +++ b/src/components/SelectOnClick/SelectOnClick.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; + +import { ClipboardService } from '../../services'; + +export class SelectOnClick extends React.PureComponent { + handleClick = () => { + ClipboardService.selectElement(this.refs.child); + }; + + render() { + const { children } = this.props; + return ( +
+ {children} +
+ ); + } +} diff --git a/src/components/SideMenu/MenuItem.tsx b/src/components/SideMenu/MenuItem.tsx new file mode 100644 index 00000000..0c31ed46 --- /dev/null +++ b/src/components/SideMenu/MenuItem.tsx @@ -0,0 +1,60 @@ +import * as React from 'react'; +import { observer } from 'mobx-react'; + +import { IMenuItem, OperationModel } from '../../services'; +import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements'; +import { ShelfIcon } from '../../common-elements/shelfs'; +import { MenuItems } from './MenuItems'; + +interface MenuItemProps { + item: IMenuItem; + onActivate?: (item: IMenuItem) => void; +} + +@observer +export class MenuItem extends React.Component { + activate = (evt: React.MouseEvent) => { + this.props.onActivate!(this.props.item); + evt.stopPropagation(); + }; + + render() { + const { item } = this.props; + return ( + + {item.type === 'operation' ? ( + + ) : ( + + {item.name} + {(item.depth > 0 && + item.items.length > 0 && ( + + )) || + null} + + )} + {item.items.length > 0 && ( + + )} + + ); + } +} + +export type OperationMenuItemContentProps = { + item: OperationModel; +}; + +@observer +class OperationMenuItemContent extends React.Component { + render() { + const { item } = this.props; + return ( + + + {item.name} + + ); + } +} diff --git a/src/components/SideMenu/MenuItems.tsx b/src/components/SideMenu/MenuItems.tsx new file mode 100644 index 00000000..96e0b232 --- /dev/null +++ b/src/components/SideMenu/MenuItems.tsx @@ -0,0 +1,28 @@ +import * as React from 'react'; +import { observer } from 'mobx-react'; + +import { IMenuItem } from '../../services'; + +import { MenuItemUl } from './styled.elements'; +import { MenuItem } from './MenuItem'; + +interface MenuItemsProps { + items: IMenuItem[]; + active?: boolean; + onActivate?: (item: IMenuItem) => void; +} + +@observer +export class MenuItems extends React.Component { + render() { + const { items } = this.props; + const active = this.props.active == null ? true : this.props.active; + return ( + + {items.map((item, idx) => ( + + ))} + + ); + } +} diff --git a/src/components/SideMenu/SideMenu.tsx b/src/components/SideMenu/SideMenu.tsx new file mode 100644 index 00000000..2c8255e0 --- /dev/null +++ b/src/components/SideMenu/SideMenu.tsx @@ -0,0 +1,30 @@ +import * as React from 'react'; +import { observer } from 'mobx-react'; +import * as PropTypes from 'prop-types'; +import { getContext } from 'recompose'; + +import { BaseContainerProps } from '../../types/components'; +import { IMenuItem } from '../../services/MenuStore'; + +import { PerfectScrollbar } from '../../common-elements/perfect-scrollbar'; +import { MenuItems } from './MenuItems'; + +@observer +class SideMenu extends React.Component { + render() { + const store = this.props.store.menu; + return ( + + + + ); + } + + activate = (item: IMenuItem) => { + this.props.store.menu.activateAndScroll(item, true); + }; +} + +export default getContext({ + store: PropTypes.object, +})(SideMenu); diff --git a/src/components/SideMenu/styled.elements.ts b/src/components/SideMenu/styled.elements.ts new file mode 100644 index 00000000..a9b2e3ea --- /dev/null +++ b/src/components/SideMenu/styled.elements.ts @@ -0,0 +1,130 @@ +import { deprecatedCss } from '../../common-elements'; +import styled, { withProps, css } from '../../styled-components'; + +export const OperationBadge = withProps<{ type: string }>(styled.span).attrs({ + className: props => `operation-type ${props.type}`, +})` + width: 26px; + display: inline-block; + height: ${props => props.theme.code.fontSize};; + background-color: #333; + border-radius: 3px; + vertical-align: top; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAACgCAYAAADuDlcXAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpFNjQ5N0JDQUE3OTYxMUU0ODNGMUE0RUM3NjRDRTQyNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpFNjQ5N0JDQkE3OTYxMUU0ODNGMUE0RUM3NjRDRTQyNyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkU2NDk3QkM4QTc5NjExRTQ4M0YxQTRFQzc2NENFNDI3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkU2NDk3QkM5QTc5NjExRTQ4M0YxQTRFQzc2NENFNDI3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+mIrGwQAAAZ9JREFUeNrsmtuOwyAMRBmU//9lbx9208ayjQ1EarSDVFW56ARIGGZIIK/S3gWvX3X7LN3a6WxDHdPnnDBpcZHEOe3wrmLUMg2zatKykPOq1/5fK71tLIQR9jjYsaJfWdWAAcRsM2W1z9LNGcFkRlmtPhvpf7qmHAGEESZqLFr/qbHaCy4Is6oxLdvT+nWr0lLPCCPsFn+mA5e2UjLycL1o6qLMiapqRGoifVCDinrgU2mRyJthzZg3CSPs+2HhIM4YGq0a4oDgiGjYTKw20/OwUzAEuXz73YSqtdsV+F1a3eZpweFEGGG7Y3ULbJRk4nYPlEHbUi86wpNtbz4oB37PICOrLEdC9DKzFv7EkQ8tYY8Nr8tuyJrRsdpMrIJ0n4GPBmGEEUbYzRMKnFwug1B7rppmbCiyBjBrQ1vC8KW/CxrF7osNrRbxMjofWsIIuwU2vapnZfTRq4/wFXl3hG9bMzP6ZWV47LoB+Gym1/EyUleKI2GEPW8pQpu80bHLvsifSWFVAVEzo2VDTxxb9T16eO7sF0vmxPNPxPFHgAEA/rGUMXq/uWcAAAAASUVORK5CYII="); + background-repeat: no-repeat; + background-position: 6px 4px; + text-indent: -9000px; + margin-right: 6px; + margin-top: 2px; + + &.get { + background-position: 8px -12px; + background-color: ${props => props.theme.colors.http.get}; + } + + &.post { + background-position: 6px 4px; + background-color: ${props => props.theme.colors.http.post}; + } + + &.put { + background-position: 8px -28px; + background-color: ${props => props.theme.colors.http.put}; + } + + &.options { + background-position: 4px -148px; + background-color: ${props => props.theme.colors.http.options}; + } + + &.patch { + background-position: 4px -114px; + background-color: ${props => props.theme.colors.http.patch}; + } + + &.delete { + background-position: 4px -44px; + background-color: ${props => props.theme.colors.http.delete}; + } + + &.basic { + background-position: 5px -79px; + background-color: ${props => props.theme.colors.http.basic}; + } + + &.link { + background-position: 4px -131px; + background-color: ${props => props.theme.colors.http.link}; + } +`; + +function menuItemActiveBg(depth): string { + if (depth > 1) { + return '#e1e1e1'; + } else if (depth === 1) { + return '#f0f0f0'; + } else { + return ''; + } +} + +export const MenuItemUl = withProps<{ active: boolean }>(styled.ul)` + margin: 0; + padding: 0; + + & & { + font-size: 0.929em; + } + + ${props => (props.active ? '' : 'display: none;')}; +`; + +export const MenuItemLi = withProps<{ depth: number }>(styled.li)` + list-style: none inside none; + overflow: hidden; + text-overflow: ellipsis; + padding: 0; + ${props => (props.depth === 0 ? 'margin-top: 15px' : '')}; +`; + +export const menuItemDepth = { + '0': css` + color: rgba(38, 50, 56, 0.4); + text-transform: uppercase; + font-size: 0.8em; + padding-bottom: 0; + cursor: default; + `, + '1': css` + font-weight: 300; + font-size: 0.929em; + text-transform: uppercase; + color: ${props => props.theme.colors.main}; + `, +}; + +export const MenuItemLabel = withProps<{ + depth: number; + active: boolean; + deprecated?: boolean; +}>(styled.label)` + cursor: pointer; + color: ${props => props.theme.colors.text}; + margin: 0; + padding: 12.5px ${props => props.theme.spacingUnit}px; + display: flex; + justify-content: space-between; + font-family: ${props => props.theme.headingsFont.family}; + ${props => menuItemDepth[props.depth]}; + background-color: ${props => (props.active ? menuItemActiveBg(props.depth) : '')}; + + ${props => (props.deprecated && deprecatedCss) || ''}; +`; + +export const MenuItemTitle = withProps<{ width?: string }>(styled.span).attrs({ + className: 'menu-item-title', +})` + display: inline-block; + vertical-align: middle; + width: ${props => (props.width ? props.width : 'auto')}; +`; diff --git a/src/components/SourceCode/SourceCode.tsx b/src/components/SourceCode/SourceCode.tsx new file mode 100644 index 00000000..5bdb7c97 --- /dev/null +++ b/src/components/SourceCode/SourceCode.tsx @@ -0,0 +1,22 @@ +import * as React from 'react'; +import { highlight } from '../../utils'; +import styled from '../../styled-components'; + +const StyledPre = styled.pre` + font-family: ${props => props.theme.code.fontFamily}; + font-size: ${props => props.theme.code.fontSize}; + overflow-x: auto; + font-size: 0.9em; +`; + +export interface SourceCodeProps { + source: string; + lang: string; +} + +export class SourceCode extends React.PureComponent { + render() { + const { source, lang } = this.props; + return ; + } +} diff --git a/src/components/StoreProvider.ts b/src/components/StoreProvider.ts new file mode 100644 index 00000000..b2558e52 --- /dev/null +++ b/src/components/StoreProvider.ts @@ -0,0 +1,49 @@ +import { Component, Children } from 'react'; +import * as PropTypes from 'prop-types'; +import { AppStore, HistoryService } from '../services/'; + +interface SpecProps { + specUrl?: string; + spec?: object; + store?: AppStore; +} + +export class StoreProvider extends Component { + store: AppStore; + + static childContextTypes = { + store: PropTypes.object.isRequired, + }; + + constructor(props: SpecProps) { + super(props); + this.state = {}; + + this.store = props.store || new AppStore(); + + if (!this.store.spec.loaded) { + this.store.spec + .load(props.spec! || props.specUrl) + .then(() => { + HistoryService.emit(); + this.setError(); + }) + .catch(e => this.setError(e)); + } + } + + setError(e?: Error) { + this.setState({ + error: e, + }); + } + + getChildContext() { + return { store: this.props.store || this.store }; + } + + render() { + if (this.state.error) throw this.state.error; + return Children.only(this.props.children); + } +} diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 00000000..333da738 --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1 @@ +export * from './Redoc'; diff --git a/src/hmr-playground.tsx b/src/hmr-playground.tsx new file mode 100644 index 00000000..a24f860c --- /dev/null +++ b/src/hmr-playground.tsx @@ -0,0 +1,42 @@ +import * as React from 'react'; +import { render } from 'react-dom'; +// import DevTools from 'mobx-react-devtools'; + +import { AppContainer } from 'react-hot-loader'; +import { Redoc, RedocProps } from './components/Redoc'; +import { AppStore } from './services/AppStore'; + +const renderRoot = (Component: typeof Redoc, props: RedocProps) => + render( +
+ + + +
, + document.getElementById('example'), + ); + +const big = window.location.search.indexOf('big') > -1; +const props = { + specUrl: big ? 'big-swagger.json' : 'swagger.yaml', + store: new AppStore(), +}; + +renderRoot(Redoc, props); + +if (module.hot) { + const reload = (reloadStore = false) => () => { + if (reloadStore) { + // create a new Store + props.store.dispose(); + + const state = props.store.toJS(); + props.store = AppStore.fromJS(state); + } + + renderRoot(Redoc, props); + }; + + module.hot.accept(['./components/Redoc'], reload()); + module.hot.accept(['./services/AppStore'], reload(true)); +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..80e3bea6 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,2 @@ +export * from './components'; +export * from './services'; diff --git a/src/services/AppStore.ts b/src/services/AppStore.ts new file mode 100644 index 00000000..00ba6633 --- /dev/null +++ b/src/services/AppStore.ts @@ -0,0 +1,55 @@ +import { SpecStore } from './models'; +import { MenuStore } from './MenuStore'; +import { ScrollService } from './ScrollService'; + +export class AppStore { + menu: MenuStore; + scroll: ScrollService; + spec: SpecStore; + static i = 25; + + // TODO: store serialization ??? + + constructor() { + this.scroll = new ScrollService(); + this.spec = new SpecStore(); + this.menu = new MenuStore(this.spec, this.scroll); + } + + dispose() { + this.scroll.dispose(); + this.menu.dispose(); + } + + /** + * serializes store + * **SUPER HACKY AND NOT OPTIMAL IMPLEMENTATION** + */ + // TODO: + toJS() { + return { + menu: { + activeMenuIdx: this.menu.activeItemIdx, + }, + spec: { + parser: { + specUrl: this.spec.parser.specUrl, + spec: this.spec.parser.spec, + }, + }, + }; + } + /** + * deserialize store + * **SUPER HACKY AND NOT OPTIMAL IMPLEMENTATION** + */ + // TODO: + static fromJS(state): AppStore { + const inst = new AppStore(); + inst.spec.parser.specUrl = state.spec.parser.specUrl; + inst.spec.parser.spec = state.spec.parser.spec; + inst.menu.activeItemIdx = state.menu.activeItemIdx || 0; + inst.menu.activate(inst.menu.flatItems[inst.menu.activeItemIdx]); + return inst; + } +} diff --git a/lib/services/clipboard.service.ts b/src/services/ClipboardService.ts similarity index 72% rename from lib/services/clipboard.service.ts rename to src/services/ClipboardService.ts index 62d77a90..ad52367d 100644 --- a/lib/services/clipboard.service.ts +++ b/src/services/ClipboardService.ts @@ -1,12 +1,11 @@ -'use strict'; +const isSupported = document.queryCommandSupported && document.queryCommandSupported('copy'); -var isSupported = document.queryCommandSupported && document.queryCommandSupported('copy'); -export class Clipboard { - static isSupported():boolean { +export class ClipboardService { + static isSupported(): boolean { return isSupported; } - static selectElement(element:any):void { + static selectElement(element: any): void { let range; let selection; if ((document.body).createTextRange) { @@ -22,15 +21,15 @@ export class Clipboard { } } - static deselect():void { - if ( (document).selection ) { + static deselect(): void { + if ((document).selection) { (document).selection.empty(); - } else if ( window.getSelection ) { + } else if (window.getSelection) { window.getSelection().removeAllRanges(); } } - static copySelected():boolean { + static copySelected(): boolean { let result; try { result = document.execCommand('copy'); @@ -40,14 +39,14 @@ export class Clipboard { return result; } - static copyElement(element:any):boolean { - Clipboard.selectElement(element); - let res = Clipboard.copySelected(); - if (res) Clipboard.deselect(); + static copyElement(element: any): boolean { + ClipboardService.selectElement(element); + let res = ClipboardService.copySelected(); + if (res) ClipboardService.deselect(); return res; } - static copyCustom(text:string):boolean { + static copyCustom(text: string): boolean { let textArea = document.createElement('textarea'); textArea.style.position = 'fixed'; textArea.style.top = '0'; @@ -69,14 +68,13 @@ export class Clipboard { // Avoid flash of white box if rendered for any reason. textArea.style.background = 'transparent'; - textArea.value = text; document.body.appendChild(textArea); textArea.select(); - let res = Clipboard.copySelected(); + let res = ClipboardService.copySelected(); document.body.removeChild(textArea); return res; diff --git a/src/services/HistoryService.ts b/src/services/HistoryService.ts new file mode 100644 index 00000000..aa06b273 --- /dev/null +++ b/src/services/HistoryService.ts @@ -0,0 +1,64 @@ +import { bind, debounce } from 'decko'; +import { EventEmitter } from 'eventemitter3'; + +const EVENT = 'hashchange'; + +function isSameHash(a: string, b: string): boolean { + return a === b || '#' + a === b || a === '#' + b; +} + +class _HistoryService { + private causedHashChange: boolean = false; + private _emiter; + + constructor() { + this._emiter = new EventEmitter(); + this.bind(); + } + + get hash(): string { + return window.location.hash; + } + + subscribe(cb): () => void { + const emmiter = this._emiter.addListener(EVENT, cb); + return () => emmiter.removeListener(EVENT, cb); + } + + emit = () => { + if (this.causedHashChange) { + this.causedHashChange = false; + return; + } + this._emiter.emit(EVENT, this.hash); + }; + + bind() { + window.addEventListener('hashchange', this.emit, false); + } + + dispose() { + window.removeEventListener('hashchange', this.emit); + this.causedHashChange = false; + } + + @bind + @debounce + update(hash: string | null, rewriteHistory: boolean = false) { + if (hash == null || isSameHash(hash, this.hash)) return; + if (rewriteHistory) { + window.history.replaceState(null, '', window.location.href.split('#')[0] + '#' + hash); + return; + } + this.causedHashChange = true; + window.location.hash = hash; + } +} + +export const HistoryService = new _HistoryService(); + +if (module.hot) { + module.hot.dispose(() => { + HistoryService.dispose(); + }); +} diff --git a/src/services/MarkdownRenderer.ts b/src/services/MarkdownRenderer.ts new file mode 100644 index 00000000..6d481820 --- /dev/null +++ b/src/services/MarkdownRenderer.ts @@ -0,0 +1,188 @@ +import * as Remarkable from 'remarkable'; +import { IMenuItem, SECTION_ATTR } from './MenuStore'; +import { GroupModel } from './models'; +import { highlight } from '../utils'; + +const md = new Remarkable('default', { + html: true, + linkify: true, + breaks: false, + typographer: false, + highlight: (str, lang) => { + return highlight(str, lang); + }, +}); + +const COMPONENT_REGEXP = '^\\s*\\s*$'; + +type MarkdownHeading = { + name: string; + children?: MarkdownHeading[]; + content?: string; +}; + +export class MarkdownRenderer { + public headings: GroupModel[] = []; + currentTopHeading: GroupModel; + + private _origRules: any = {}; + + saveOrigRules() { + this._origRules.open = md.renderer.rules.heading_open; + this._origRules.close = md.renderer.rules.heading_close; + } + + restoreOrigRules() { + md.renderer.rules.heading_open = this._origRules.open; + md.renderer.rules.heading_close = this._origRules.close; + } + + saveHeading(name: string, container: IMenuItem[] = this.headings): GroupModel { + const item = new GroupModel('section', { + name, + }); + item.depth = 1; + container.push(item); + return item; + } + + flattenHeadings(container?: MarkdownHeading[]): MarkdownHeading[] { + if (container === undefined) return []; + let res: MarkdownHeading[] = []; + for (let heading of container) { + res.push(heading); + res.push(...this.flattenHeadings(heading.children)); + } + return res; + } + + attachHeadingsContent(rawText: string) { + const buildRegexp = heading => new RegExp(``); + + const tmpEl = document.createElement('DIV'); + + const html2Str = html => { + tmpEl.innerHTML = html; + return tmpEl.innerText; + }; + + let flatHeadings = this.flattenHeadings(this.headings); + if (flatHeadings.length < 1) return; + let prevHeading = flatHeadings[0]; + + let prevPos = rawText.search(buildRegexp(prevHeading)); + for (let i = 1; i < flatHeadings.length; i++) { + let heading = flatHeadings[i]; + let currentPos = rawText.substr(prevPos + 1).search(buildRegexp(heading)) + prevPos + 1; + prevHeading.content = html2Str(rawText.substring(prevPos, currentPos)); + + prevHeading = heading; + prevPos = currentPos; + } + prevHeading.content = html2Str(rawText.substring(prevPos)); + } + + headingOpenRule = (tokens, idx) => { + if (tokens[idx].hLevel > 2) { + return this._origRules.open(tokens, idx); + } else { + let content = tokens[idx + 1].content; + if (tokens[idx].hLevel === 1) { + this.currentTopHeading = this.saveHeading(content); + let id = this.currentTopHeading.id; + return ( + `` + + `` + + `` + ); + } else if (tokens[idx].hLevel === 2) { + let { id } = this.saveHeading(content, this.currentTopHeading.items); + return ( + `` + + `` + + `` + ); + } + } + }; + + headingCloseRule = (tokens, idx) => { + if (tokens[idx].hLevel > 2) { + return this._origRules.close(tokens, idx); + } else { + return `\n`; + } + }; + + renderMd(rawText: string, raw: boolean = true): string { + if (!raw) { + this.saveOrigRules(); + md.renderer.rules.heading_open = this.headingOpenRule; + md.renderer.rules.heading_close = this.headingCloseRule; + } + + let text = rawText; + + let res = md.render(text); + + this.attachHeadingsContent(res); + + if (!raw) { + this.restoreOrigRules(); + } + return res; + } + + extractHeadings(rawText: string): GroupModel[] { + this.renderMd(rawText, false); + const res = this.headings; + this.headings = []; + return res; + } + + renderMdWithComponents( + rawText: string, + components: { [name: string]: new () => React.Component }, + raw: boolean = true, + ): (string | { component: new () => React.Component; attrs: any })[] { + let componentDefs: string[] = []; + let match; + let anyCompRegexp = new RegExp(COMPONENT_REGEXP.replace('{component}', '(.*?)'), 'gmi'); + while ((match = anyCompRegexp.exec(rawText))) { + componentDefs.push(match[1]); + } + + let splitCompRegexp = new RegExp(COMPONENT_REGEXP.replace('{component}', '.*?'), 'mi'); + let htmlParts = rawText.split(splitCompRegexp); + let res: any[] = []; + for (let i = 0; i < htmlParts.length; i++) { + const htmlPart = htmlParts[i]; + if (htmlPart) { + res.push(this.renderMd(htmlPart, raw)); + } + if (componentDefs[i]) { + const { componentName, attrs } = parseComponent(componentDefs[i]); + res.push({ + component: componentName && components[componentName], + attrs: attrs, + }); + } + } + return res; + } +} + +function parseComponent( + htmlTag: string, +): { + componentName?: string; + attrs: any; +} { + const match = /<([\w_-]+).*?>/.exec(htmlTag); + if (match === null || match.length <= 1) return { componentName: undefined, attrs: {} }; + const componentName = match[1]; + return { + componentName, + attrs: {}, // TODO + }; +} diff --git a/src/services/MenuBuilder.ts b/src/services/MenuBuilder.ts new file mode 100644 index 00000000..7f8fa65b --- /dev/null +++ b/src/services/MenuBuilder.ts @@ -0,0 +1,195 @@ +import { OpenAPIParser } from './OpenAPIParser'; +import { GroupModel, OperationModel } from './models'; +import { JsonPointer, isOperationName } from '../utils'; +import { OpenAPIOperation, OpenAPIParameter, OpenAPISpec, OpenAPITag, Referenced } from '../types'; +import { MarkdownRenderer } from './MarkdownRenderer'; + +export type TagInfo = OpenAPITag & { + operations: ExtendedOpenAPIOperation[]; + used?: boolean; +}; + +export type ExtendedOpenAPIOperation = { + _$ref: string; + httpVerb: string; + pathParams: Referenced[]; +} & OpenAPIOperation; + +export type TagsInfoMap = Dict; + +export interface TagGroup { + name: string; + tags: string[]; +} + +export const GROUP_DEPTH = 0; +export type ContentItemModel = GroupModel | OperationModel; + +export class MenuBuilder { + /** + * Builds page content structure based on tags + */ + static buildStructure(parser: OpenAPIParser): ContentItemModel[] { + const spec = parser.spec!; + + const items: ContentItemModel[] = []; + const tagsMap = MenuBuilder.getTagsWithOperations(spec); + items.push(...MenuBuilder.addMarkdownItems(spec.info.description || '')); + if (spec['x-tagGroups']) { + items.push(...MenuBuilder.getTagGroupsItems(parser, undefined, spec['x-tagGroups'], tagsMap)); + } else { + items.push(...MenuBuilder.getTagsItems(parser, tagsMap)); + } + return items; + } + + /** + * extracts items from markdown description + * @param description - markdown source + */ + static addMarkdownItems(description: string): ContentItemModel[] { + const renderer = new MarkdownRenderer(); + const headings = renderer.extractHeadings(description || ''); + return headings; + } + + /** + * Returns array of OperationsGroup items for the tag groups (x-tagGroups vendor extenstion) + * @param tags value of `x-tagGroups` vendor extension + */ + static getTagGroupsItems( + parser: OpenAPIParser, + parent: GroupModel | undefined, + groups: TagGroup[], + tags: TagsInfoMap, + ): GroupModel[] { + let res: GroupModel[] = []; + for (let group of groups) { + let item = new GroupModel('group', group, parent); + item.depth = GROUP_DEPTH; + item.items = MenuBuilder.getTagsItems(parser, tags, item, group); + res.push(item); + } + // TODO checkAllTagsUsedInGroups + return res; + } + + /** + * Returns array of OperationsGroup items for the tags of the group or for all tags + * @param tagsMap tags info returned from `getTagsWithOperations` + * @param parent parent item + * @param group group which this tag belongs to. if not provided gets all tags + */ + static getTagsItems( + parser: OpenAPIParser, + tagsMap: TagsInfoMap, + parent?: GroupModel, + group?: TagGroup, + ): ContentItemModel[] { + let tagNames; + + if (group === undefined) { + tagNames = Object.keys(tagsMap); // all tags + } else { + tagNames = group.tags; + } + + const tags = tagNames.map(tagName => { + if (!tagsMap[tagName]) { + console.warn(`Non-existing tag "${tagName}" is added to the group "${group!.name}"`); + return null; + } + tagsMap[tagName].used = true; + return tagsMap[tagName]; + }); + + let res: (GroupModel | OperationModel)[] = []; + for (let tag of tags) { + if (!tag) continue; + let item = new GroupModel('tag', tag, parent); + item.depth = GROUP_DEPTH + 1; + item.items = this.getOperationsItems(parser, item, tag, item.depth + 1); + + // don't put empty tag into content, instead put its operations + if (tag.name === '') { + let items = this.getOperationsItems(parser, undefined, tag, item.depth); + res.push(...items); + continue; + } + + res.push(item); + } + return res; + } + + /** + * Returns array of Operation items for the tag + * @param parent parent OperationsGroup + * @param tag tag info returned from `getTagsWithOperations` + * @param depth items depth + */ + static getOperationsItems( + parser: OpenAPIParser, + parent: GroupModel | undefined, + tag: TagInfo, + depth: number, + ): OperationModel[] { + if (tag.operations.length === 0) { + return []; + } + + let res: OperationModel[] = []; + for (let operationInfo of tag.operations) { + let operation = new OperationModel(parser, operationInfo, parent); + operation.depth = depth; + res.push(operation); + } + return res; + } + + /** + * collects tags and maps each tag to list of operations belonging to this tag + */ + static getTagsWithOperations(spec: OpenAPISpec): TagsInfoMap { + const tags: TagsInfoMap = {}; + for (let tag of spec.tags || []) { + tags[tag.name] = Object.assign(tag); + tags[tag.name].operations = []; + } + + const paths = spec.paths; + for (let pathName of Object.keys(paths)) { + const path = paths[pathName]; + const operations = Object.keys(path).filter(isOperationName); + for (let operationName of operations) { + const operationInfo = path[operationName]; + let operationTags = operationInfo.tags; + + if (!operationTags || !operationTags.length) { + // empty tag + operationTags = ['']; + } + const operationPointer = JsonPointer.compile(['paths', pathName, operationName]); + for (let tagName of operationTags) { + let tag = tags[tagName]; + if (tag === undefined) { + tag = { + name: tagName, + operations: [], + }; + tags[tagName] = tag; + } + if (tag['x-traitTag']) continue; + tag.operations.push({ + ...operationInfo, + _$ref: operationPointer, + httpVerb: operationName, + pathParams: path.parameters || [], + }); + } + } + } + + return tags; + } +} diff --git a/src/services/MenuStore.ts b/src/services/MenuStore.ts new file mode 100644 index 00000000..eeb2bb28 --- /dev/null +++ b/src/services/MenuStore.ts @@ -0,0 +1,235 @@ +import { OperationModel, SpecStore } from './models'; +import { computed, action } from 'mobx'; + +import { ScrollService } from './ScrollService'; +import { HistoryService } from './HistoryService'; + +import { GROUP_DEPTH } from './MenuBuilder'; +import { flattenByProp } from '../utils'; + +export type MenuItemGroupType = 'group' | 'tag' | 'section'; +export type MenuItemType = MenuItemGroupType | 'operation'; + +/** Generic interface for MenuItems */ +export interface IMenuItem { + id: string; + absoluteIdx?: number; + name: string; + depth: number; + active: boolean; + items: Array; + parent?: IMenuItem; + deprecated?: boolean; + type: MenuItemType; + + getHash(): string; + deactivate(): void; + activate(): void; +} + +export const SECTION_ATTR = 'data-section-id'; + +/** + * Stores all side-menu related information + */ +export class MenuStore { + /** + * cached flattened menu items to support absolute indexing + */ + private _flatItems: IMenuItem[]; + private _unsubscribe: Function; + private _hashUnsubscribe: Function; + + /** + * active item absolute index (when flattened). -1 means nothing is selected + */ + activeItemIdx: number = -1; + + /** + * + * @param spec [SpecStore](#SpecStore) which contains page content structure + * @param _scrollService scroll service instance used by this menu + */ + constructor(private spec: SpecStore, private _scrollService: ScrollService) { + this._unsubscribe = _scrollService.subscribe(this.updateOnScroll); + this._hashUnsubscribe = HistoryService.subscribe(this.updateOnHash); + } + + /** + * top level menu items (not flattened) + */ + @computed + get items(): IMenuItem[] { + return this.spec.operationGroups; + } + + /** + * update active items on scroll + * @param isScrolledDown whether last scroll was downside + */ + @action.bound + updateOnScroll(isScrolledDown: boolean): void { + const step = isScrolledDown ? 1 : -1; + let itemIdx = this.activeItemIdx; + while (true) { + if (itemIdx === -1 && !isScrolledDown) { + break; + } + + if (itemIdx >= this.flatItems.length - 1 && isScrolledDown) { + break; + } + + if (isScrolledDown) { + const el = this.getElementAt(itemIdx + 1); + if (this._scrollService.isElementBellow(el)) { + break; + } + } else { + const el = this.getElementAt(itemIdx); + if (this._scrollService.isElementAbove(el)) { + break; + } + } + itemIdx += step; + } + + this.activate(this.flatItems[itemIdx], true, true); + } + + /** + * update active items on hash change + * @param hash current hash + */ + @action.bound + updateOnHash(hash: string): boolean { + if (!hash) return false; + let item: IMenuItem | undefined; + hash = hash.substr(1); + let namespace = hash.split('/')[0]; + let ptr = decodeURIComponent(hash.substr(namespace.length + 1)); + if (namespace === 'section' || namespace === 'tag') { + let sectionId = ptr.split('/')[0]; + ptr = ptr.substr(sectionId.length); + + let searchId; + if (namespace === 'section') { + searchId = hash; + } else { + searchId = ptr || namespace + '/' + sectionId; + } + + item = this.flatItems.find(item => item.id === searchId); + if (item === undefined) { + this._scrollService.scrollIntoViewBySelector(`[${SECTION_ATTR}="${searchId}"]`); + return false; + } + } else if (namespace === 'operation') { + item = this.flatItems.find(item => { + return (item as OperationModel).operationId === ptr; + }); + } + this.activateAndScroll(item, false); + return item !== undefined; + } + + /** + * get section/operation DOM Node related to the item or null if it doesn't exist + * @param idx item absolute index + */ + getElementAt(idx: number): Element | null { + const item = this.flatItems[idx]; + return (item && document.querySelector(`[${SECTION_ATTR}="${item.id}"]`)) || null; + } + + /** + * current active item + */ + get activeItem(): IMenuItem { + return this.flatItems[this.activeItemIdx] || undefined; + } + + /** + * flattened items as they appear in the tree depth-first (top to bottom in the view) + */ + get flatItems(): IMenuItem[] { + if (!this._flatItems) { + this._flatItems = flattenByProp(this.items, 'items'); + } + + this._flatItems.forEach((item, idx) => (item.absoluteIdx = idx)); + + return this._flatItems; + } + + /** + * activate menu item + * @param item item to activate + * @param updateHash [true] whether to update location hash + * @param rewriteHistory [false] whether to rewrite browser history (do not create new enrty) + */ + @action + activate( + item: IMenuItem | undefined, + updateHash: boolean = true, + rewriteHistory: boolean = false, + ) { + if ((this.activeItem && this.activeItem.id) === (item && item.id)) { + return; + } + this.deactivate(this.activeItem); + if (!item) { + HistoryService.update('', rewriteHistory); + return; + } + + // do not allow activating group items + // TODO: control over options + if (item.depth <= GROUP_DEPTH) { + return; + } + + this.activeItemIdx = item.absoluteIdx!; + if (updateHash) { + HistoryService.update(item.getHash(), rewriteHistory); + } + + while (item !== undefined) { + item.activate(); + item = item.parent; + } + } + + /** + * makes item and all the parents not active + * @param item item to deactivate + */ + deactivate(item: IMenuItem | undefined) { + while (item !== undefined) { + item.deactivate(); + item = item.parent; + } + } + + /** + * activate menu item and scroll to it + * @see MenuStore.activate + */ + @action + activateAndScroll(item: IMenuItem | undefined, updateHash: boolean, rewriteHistory?: boolean) { + this.activate(item, updateHash, rewriteHistory); + this.scrollToActive(); + } + + /** + * scrolls to active section + */ + scrollToActive(): void { + this._scrollService.scrollIntoView(this.getElementAt(this.activeItemIdx)); + } + + dispose() { + this._unsubscribe(); + this._hashUnsubscribe(); + } +} diff --git a/src/services/OpenAPIParser.ts b/src/services/OpenAPIParser.ts new file mode 100644 index 00000000..d5827dc2 --- /dev/null +++ b/src/services/OpenAPIParser.ts @@ -0,0 +1,242 @@ +import { action, computed, observable } from 'mobx'; +import * as JsonSchemaRefParser from 'json-schema-ref-parser'; +import { resolve as urlResolve } from 'url'; + +import { OpenAPIRef, OpenAPISchema, OpenAPISpec, Referenced } from '../types'; + +import { JsonPointer } from '../utils/JsonPointer'; +import { isNamedDefinition } from '../utils/openapi'; + +export type MergedOpenAPISchema = OpenAPISchema & { namedParents?: string[] }; + +/** + * Helper class to keep track of visited references to avoid + * endless recursion because of circular refs + */ +class RefCounter { + public _counter = {}; + + reset(): void { + this._counter = {}; + } + + visit(ref: string): void { + this._counter[ref] = this._counter[ref] ? this._counter[ref] + 1 : 1; + } + + exit(ref: string): void { + this._counter[ref] = this._counter[ref] && this._counter[ref] - 1; + } + + visited(ref: string): boolean { + return !!this._counter[ref]; + } +} + +/** + * Loads and keeps spec. Provides raw spec operations + */ +export class OpenAPIParser { + @observable specUrl: string; + @observable.ref spec?: OpenAPISpec; + + private _parser: JsonSchemaRefParser; + private _refCounter: RefCounter = new RefCounter(); + + @computed + get loaded(): boolean { + return this.spec !== undefined; + } + + /** + * loads and bundles the spec via url to spec or by providing spec itself. + * Async as bundling is async as spec may contain extrenal refs. + * @param urlOrObject url to the spec or the spec itself + */ + @action + async load(urlOrObject: string | object): Promise { + if (this.loaded) { + return this.spec!; + } + + this._parser = new JsonSchemaRefParser(); + if (typeof urlOrObject === 'string') { + this.specUrl = urlResolve(window.location.href, urlOrObject); + } else { + this.specUrl = window.location.href; + } + + const spec = await this._parser.bundle(urlOrObject, { + resolve: { http: { withCredentials: false } }, + } as object); + + this.validate(spec); + + this.spec = spec; + + return this.spec!; + } + + validate(spec: any) { + // TODO: validate + if (spec.openapi === undefined) { + throw new Error('Document must be valid OpenAPI 3.0.0 definition'); + } + } + + /** + * get spec part by JsonPointer ($ref) + */ + byRef = (ref: string): T | undefined => { + let res; + if (this.spec === undefined) return; + try { + res = JsonPointer.get(this.spec, decodeURIComponent(ref)); + } catch (e) { + // if resolved from outer files simple jsonpointer.get fails to get correct schema + if (ref.charAt(0) !== '#') ref = '#' + ref; + try { + res = this._parser.$refs.get(decodeURIComponent(ref)); + } catch (e) { + // do nothing + } + } + return res; + }; + + /** + * checks if the objectt is OpenAPI reference (containts $ref property) + */ + isRef(obj: any): obj is OpenAPIRef { + return obj.$ref !== undefined && obj.$ref !== null; + } + + /** + * resets visited enpoints. should be run after + */ + resetVisited() { + for (let k in this._refCounter._counter) { + if (this._refCounter._counter[k] > 0) { + console.log('>>>', k, this._refCounter._counter[k]); + } + } + this._refCounter = new RefCounter(); + } + + exitRef(ref: Referenced) { + if (!this.isRef(ref)) return; + this._refCounter.exit(ref.$ref); + } + + /** + * Resolve given reference object or return as is if it is not a reference + * @param obj object to dereference + * @param forceCircular whether to dereference even if it is cirular ref + */ + deref(obj: OpenAPIRef | T, forceCircular: boolean = false): T { + if (this.isRef(obj)) { + const resolved = this.byRef(obj.$ref)!; + if (this._refCounter.visited(obj.$ref) && !forceCircular) { + // circular reference detected + return Object.assign({}, resolved, { 'x-circular-ref': true }); + } + this._refCounter.visit(obj.$ref); + // deref again in case one more $ref is here + if (this.isRef(resolved)) { + const res = this.deref(resolved); + this.exitRef(resolved); + return res; + } + return resolved; + } + return obj; + } + + /** + * Merge allOf contsraints. + * @param schema schema with allOF + * @param $ref pointer of the schema + * @param forceCircular whether to dereference children even if it is a cirular ref + */ + mergeAllOf( + schema: OpenAPISchema, + $ref: string, + forceCircular: boolean = false, + ): MergedOpenAPISchema { + if (schema.allOf === undefined) { + return schema; + } + + let receiver: MergedOpenAPISchema = { + ...schema, + allOf: undefined, + namedParents: [], + }; + + const allOfSchemas = schema.allOf.map((subSchema, idx) => { + return { + $ref: subSchema.$ref || $ref + '/allOf/' + idx, + schema: this.deref(subSchema, forceCircular), + }; + }); + + if (receiver.title === undefined && isNamedDefinition($ref)) { + receiver.title = JsonPointer.baseName($ref); + } + + for (let { $ref: subSchemaRef, schema: subSchema } of allOfSchemas) { + if ( + receiver.type !== subSchema.type && + receiver.type !== undefined && + subSchema.type !== undefined + ) { + throw new Error(`Uncopatible types in allOf at "${$ref}"`); + } + + receiver.type = subSchema.type; + if (subSchema.properties !== undefined) { + // TODO: merge properties contents + receiver.properties = { + ...(receiver.properties || {}), + ...subSchema.properties, + }; + } + + if (subSchema.required !== undefined) { + receiver.required = (receiver.required || []).concat(subSchema.required); + } + + if (isNamedDefinition(subSchemaRef)) { + receiver.namedParents!.push(subSchemaRef); + if (receiver.title === undefined) { + receiver.title = JsonPointer.baseName(subSchemaRef); + } + } + + // merge rest of constraints + // TODO: do more intelegent merge + receiver = { ...subSchema, ...receiver }; + } + return receiver; + } + + /** + * Find all derived definitions among #/components/schemas from any of $refs + * returns map of definition pointer to definition name + * @param $refs array of references to find derived from + */ + findDerived($refs: string[]): Dict { + const res: Dict = {}; + const schemas = (this.spec!.components && this.spec!.components!.schemas) || {}; + for (let defName in schemas) { + const def = this.deref(schemas[defName]); + if ( + def.allOf !== undefined && + def.allOf.find(obj => obj.$ref !== undefined && $refs.indexOf(obj.$ref) > -1) + ) { + res['#/components/schemas/' + defName] = defName; + } + } + return res; + } +} diff --git a/src/services/ScrollService.ts b/src/services/ScrollService.ts new file mode 100644 index 00000000..b20c2000 --- /dev/null +++ b/src/services/ScrollService.ts @@ -0,0 +1,71 @@ +import { debounce, bind } from 'decko'; +import { EventEmitter } from 'eventemitter3'; + +const EVENT = 'scroll'; + +export class ScrollService { + private _scrollParent: Window | HTMLElement; + private _emiter: EventEmitter; + private _prevOffsetY: number = 0; + constructor() { + this._scrollParent = window; + this._emiter = new EventEmitter(); + this.bind(); + } + + bind() { + this._prevOffsetY = this.scrollY(); + this._scrollParent.addEventListener('scroll', this.handleScroll); + } + + dispose() { + this._scrollParent.removeEventListener('scroll', this.handleScroll); + this._emiter.removeAllListeners(EVENT); + } + + scrollY(): number { + if (this._scrollParent === window) { + return window.pageYOffset; + } else if (this._scrollParent instanceof HTMLElement) { + return this._scrollParent.scrollTop; + } else { + return 0; + } + } + + isElementBellow(el: Element | null) { + if (el === null) return; + return el.getBoundingClientRect().top > 0; + } + + isElementAbove(el: Element | null) { + if (el === null) return; + return Math.trunc(el.getBoundingClientRect().top) <= 0; + } + + subscribe(cb): () => void { + const emmiter = this._emiter.addListener(EVENT, cb); + return () => emmiter.removeListener(EVENT, cb); + } + + scrollIntoView(element: Element | null) { + if (element === null) { + return; + } + element.scrollIntoView(); + } + + scrollIntoViewBySelector(selector: string) { + const element = document.querySelector(selector); + this.scrollIntoView(element); + } + + @bind + @debounce(100) + handleScroll() { + const scrollY = this.scrollY(); + const isScrolledDown = scrollY - this._prevOffsetY > 0; + this._prevOffsetY = this.scrollY(); + this._emiter.emit(EVENT, isScrolledDown); + } +} diff --git a/src/services/SpecStore.ts b/src/services/SpecStore.ts new file mode 100644 index 00000000..b5afbf92 --- /dev/null +++ b/src/services/SpecStore.ts @@ -0,0 +1,51 @@ +import { observable, computed } from 'mobx'; + +// import { OpenAPIExternalDocumentation, OpenAPIInfo } from '../types'; + +import { MenuBuilder } from './MenuBuilder'; +import { OpenAPIParser } from './OpenAPIParser'; +import { ApiInfoModel } from './models/ApiInfo'; + +/** + * Store that containts all the specification related information in the form of tree + */ +export class SpecStore { + @observable.ref parser: OpenAPIParser; + + constructor() { + this.parser = new OpenAPIParser(); + } + + load(specOrUrl: string | object) { + return this.parser.load(specOrUrl); + } + + @computed + get loaded() { + return this.parser.loaded; + } + + @computed + get info() { + if (!this.parser.loaded) return; + return new ApiInfoModel(this.parser); + } + + @computed + get externalDocs() { + if (this.parser.loaded) return; + return this.parser.spec!.externalDocs; + } + + @computed + get operationGroups() { + if (!this.parser.loaded) return []; + return MenuBuilder.buildStructure(this.parser); + } + + @computed + get security() { + // TODO: implement security + throw new Error('Not implemented'); + } +} diff --git a/src/services/__tests__/__snapshots__/prism.test.ts.snap b/src/services/__tests__/__snapshots__/prism.test.ts.snap new file mode 100644 index 00000000..43799965 --- /dev/null +++ b/src/services/__tests__/__snapshots__/prism.test.ts.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`prism.js helpers highlight js code 1`] = `"const t = 10;"`; diff --git a/src/services/__tests__/history.service.test.ts b/src/services/__tests__/history.service.test.ts new file mode 100644 index 00000000..26255faa --- /dev/null +++ b/src/services/__tests__/history.service.test.ts @@ -0,0 +1,25 @@ +import { HistoryService } from '../HistoryService'; + +describe('History service', () => { + test('should be an instance', () => { + expect(typeof HistoryService).not.toBe('function'); + expect(HistoryService.subscribe).toBeDefined(); + }); + + test('History subscribe', () => { + const fn = jest.fn(); + HistoryService.subscribe(fn); + HistoryService.emit(); + expect(fn).toHaveBeenCalled(); + }); + + test('History subscribe should return unsubsribe function', () => { + const fn = jest.fn(); + const unsubscribe = HistoryService.subscribe(fn); + HistoryService.emit(); + expect(fn).toHaveBeenCalled(); + unsubscribe(); + HistoryService.emit(); + expect(fn).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/services/__tests__/prism.test.ts b/src/services/__tests__/prism.test.ts new file mode 100644 index 00000000..8f982f1d --- /dev/null +++ b/src/services/__tests__/prism.test.ts @@ -0,0 +1,19 @@ +import { highlight, mapLang } from '../../utils/highlight'; + +describe('prism.js helpers', () => { + test('mapLang should map "json" to "js"', () => { + expect(mapLang('json')).toBe('js'); + }); + + test('mapLang should map to "clike" by default', () => { + expect(mapLang('non-existring')).toBe('clike'); + }); + + test('highlight js code', () => { + expect(highlight('const t = 10;', 'js')).toMatchSnapshot(); + }); + + test('highlight raw text should just return text', () => { + expect(highlight('Hello world', 'clike')).toBe('Hello world'); + }); +}); diff --git a/src/services/index.ts b/src/services/index.ts new file mode 100644 index 00000000..516cf99b --- /dev/null +++ b/src/services/index.ts @@ -0,0 +1,9 @@ +export * from './AppStore'; +export * from './OpenAPIParser'; +export * from './MarkdownRenderer'; +export * from './MenuStore'; +export * from './ScrollService'; +export * from './SpecStore'; +export * from './ClipboardService'; +export * from './HistoryService'; +export * from './models'; diff --git a/src/services/models/ApiInfo.ts b/src/services/models/ApiInfo.ts new file mode 100644 index 00000000..3919f598 --- /dev/null +++ b/src/services/models/ApiInfo.ts @@ -0,0 +1,33 @@ +import { OpenAPIContact, OpenAPIInfo, OpenAPILicense } from '../../types'; +import { OpenAPIParser } from '../OpenAPIParser'; + +export class ApiInfoModel implements OpenAPIInfo { + title: string; + version: string; + + description?: string; + termsOfService?: string; + contact?: OpenAPIContact; + license?: OpenAPILicense; + + constructor(public parser: OpenAPIParser) { + Object.assign(this, parser.spec!.info); + } + + get downloadLink() { + if (!this.parser.specUrl && window.Blob && window.URL) { + const blob = new Blob([JSON.stringify(this.parser.spec, null, 2)], { + type: 'application/json', + }); + return window.URL.createObjectURL(blob); + } + return this.parser.specUrl; + } + + get downloadFileName(): string | undefined { + if (!this.parser.specUrl && window.Blob && window.URL) { + return 'swagger.json'; + } + return undefined; + } +} diff --git a/src/services/models/Example.ts b/src/services/models/Example.ts new file mode 100644 index 00000000..01ce3893 --- /dev/null +++ b/src/services/models/Example.ts @@ -0,0 +1,14 @@ +import { Referenced, OpenAPIExample } from '../../types'; +import { OpenAPIParser } from '../OpenAPIParser'; + +export class ExampleModel { + value: any; + summary?: string; + description?: string; + externalValue?: string; + + constructor(parser: OpenAPIParser, infoOrRef: Referenced) { + Object.assign(this, parser.deref(infoOrRef)); + parser.exitRef(infoOrRef); + } +} diff --git a/src/services/models/Field.ts b/src/services/models/Field.ts new file mode 100644 index 00000000..f4d9c783 --- /dev/null +++ b/src/services/models/Field.ts @@ -0,0 +1,42 @@ +import { observable, action } from 'mobx'; + +import { OpenAPIParameter, Referenced } from '../../types'; + +import { SchemaModel } from './Schema'; +import { OpenAPIParser } from '../OpenAPIParser'; + +/** + * Field or Parameter model ready to be used by components + */ +export class FieldModel { + @observable public expanded: boolean = false; + + public schema: SchemaModel; + public name: string; + public required: boolean; + public description: string; + public example?: string; + public deprecated: boolean; + public in?: string; + + constructor(parser: OpenAPIParser, infoOrRef: Referenced, pointer: string) { + const info = parser.deref(infoOrRef); + + this.name = info.name; + this.in = info.in; + this.required = !!info.required; + this.schema = new SchemaModel(parser, info.schema, pointer + '/schema'); + this.description = + info.description === undefined ? this.schema.description || '' : info.description; + const example = info.example || this.schema.example; + this.example = example && JSON.stringify(example); + + this.deprecated = info.deprecated === undefined ? !!this.schema.deprecated : info.deprecated; + parser.exitRef(infoOrRef); + } + + @action + toggle() { + this.expanded = !this.expanded; + } +} diff --git a/src/services/models/Group.model.ts b/src/services/models/Group.model.ts new file mode 100644 index 00000000..ae2e8b25 --- /dev/null +++ b/src/services/models/Group.model.ts @@ -0,0 +1,57 @@ +import { observable, action } from 'mobx'; +import * as slugify from 'slugify'; + +import { OpenAPIExternalDocumentation, OpenAPITag } from '../../types'; +import { ContentItemModel } from '../MenuBuilder'; +import { IMenuItem, MenuItemGroupType } from '../MenuStore'; + +/** + * Operations Group model ready to be used by components + */ +export class GroupModel implements IMenuItem { + //#region IMenuItem fields + id: string; + absoluteIdx?: number; + name: string; + description?: string; + type: MenuItemGroupType; + + items: Array = []; + parent?: GroupModel; + externalDocs?: OpenAPIExternalDocumentation; + + @observable active: boolean = false; + + depth: number; + //#endregion + + constructor(type: MenuItemGroupType, tagOrGroup: OpenAPITag, parent?: GroupModel) { + this.id = type + '/' + slugify(tagOrGroup.name); + this.type = type; + this.name = tagOrGroup['x-displayName'] || tagOrGroup.name; + this.description = tagOrGroup.description || ''; + this.parent = parent; + this.externalDocs = tagOrGroup.externalDocs; + + // groups are active (expanded) by default + if (this.type === 'group') { + this.active = true; + } + } + + @action + activate() { + this.active = true; + } + + @action + deactivate() { + // disallow deactivating groups + if (this.type === 'group') return; + this.active = false; + } + + getHash() { + return this.id; + } +} diff --git a/src/services/models/MediaContent.ts b/src/services/models/MediaContent.ts new file mode 100644 index 00000000..f9e4eb9d --- /dev/null +++ b/src/services/models/MediaContent.ts @@ -0,0 +1,49 @@ +import { observable, action, computed } from 'mobx'; + +import { OpenAPIMediaType } from '../../types'; +import { MediaTypeModel } from './MediaType'; + +import { OpenAPIParser } from '../OpenAPIParser'; + +/** + * MediaContent model ready to be sued by React components + * Contains multiple MediaTypes and keeps track of the currently active on + */ +export class MediaContentModel { + mediaTypes: MediaTypeModel[]; + + @observable activeMimeIdx = 0; + + /** + * @param isRequestType needed to know if skipe RO/RW fields in objects + */ + constructor( + public parser: OpenAPIParser, + info: { [mime: string]: OpenAPIMediaType }, + public isRequestType: boolean = false, + ) { + this.mediaTypes = Object.entries(info).map(([name, mime]) => { + // reset deref cache just in case something is left there + parser.resetVisited(); + return new MediaTypeModel(parser, name, isRequestType, mime); + }); + } + + /** + * Set active media type by index + * @param idx media type index + */ + @action + activate(idx: number) { + this.activeMimeIdx = idx; + } + + @computed + get active() { + return this.mediaTypes[this.activeMimeIdx]; + } + + get hasSample(): boolean { + return this.mediaTypes.filter(mime => !!mime.examples).length > 0; + } +} diff --git a/src/services/models/MediaType.ts b/src/services/models/MediaType.ts new file mode 100644 index 00000000..8d2320ff --- /dev/null +++ b/src/services/models/MediaType.ts @@ -0,0 +1,59 @@ +import * as Sampler from 'openapi-sampler'; + +import { OpenAPIExample, OpenAPIMediaType } from '../../types'; +import { SchemaModel } from './Schema'; + +import { mapValues, isJsonLike } from '../../utils'; +import { OpenAPIParser } from '../OpenAPIParser'; +import { ExampleModel } from './Example'; + +export class MediaTypeModel { + examples?: { [name: string]: OpenAPIExample }; + schema?: SchemaModel; + name: string; + isRequestType: boolean; + + /** + * @param isRequestType needed to know if skipe RO/RW fields in objects + */ + constructor(parser: OpenAPIParser, name: string, isRequestType: boolean, info: OpenAPIMediaType) { + this.name = name; + this.isRequestType = isRequestType; + this.schema = info.schema && new SchemaModel(parser, info.schema, ''); + if (info.examples !== undefined) { + this.examples = mapValues(info.examples, example => new ExampleModel(parser, example)); + } else if (info.example !== undefined) { + this.examples = { + default: new ExampleModel(parser, { value: info.example }), + }; + } else if (isJsonLike(name)) { + this.generateExample(parser, info); + } + } + + generateExample(parser: OpenAPIParser, info: OpenAPIMediaType) { + const { schema, isRequestType } = this; + if (schema && schema.oneOf) { + this.examples = {}; + for (let subSchema of schema.oneOf) { + this.examples[subSchema.title] = { + value: Sampler.sample( + subSchema.rawSchema, + { skipReadOnly: isRequestType, skipReadWrite: !isRequestType }, + parser.spec, + ), + }; + } + } else { + this.examples = { + default: new ExampleModel(parser, { + value: Sampler.sample( + info.schema, + { skipReadOnly: isRequestType, skipReadWrite: !isRequestType }, + parser.spec, + ), + }), + }; + } + } +} diff --git a/src/services/models/Operation.ts b/src/services/models/Operation.ts new file mode 100644 index 00000000..9c876f0e --- /dev/null +++ b/src/services/models/Operation.ts @@ -0,0 +1,132 @@ +import { observable, action } from 'mobx'; +import { join as joinPaths } from 'path'; +import { parse as urlParse } from 'url'; + +import { IMenuItem } from '../MenuStore'; +import { GroupModel } from './Group.model'; + +import { OpenAPIExternalDocumentation, OpenAPIServer } from '../../types'; + +import { FieldModel } from './Field'; +import { ResponseModel } from './Response'; +import { RequestBodyModel } from './RequestBody'; +import { CodeSample } from './types'; +import { OpenAPIParser } from '../OpenAPIParser'; +import { ContentItemModel, ExtendedOpenAPIOperation } from '../MenuBuilder'; +import { JsonPointer, getOperationSummary, isAbsolutePath, stripTrailingSlash } from '../../utils'; + +function isNumeric(n) { + return !isNaN(parseFloat(n)) && isFinite(n); +} + +/** + * Operation model ready to be used by components + */ +export class OperationModel implements IMenuItem { + //#region IMenuItem fields + id: string; + absoluteIdx?: number; + name: string; + description?: string; + type = 'operation' as 'operation'; + + parent?: GroupModel; + externalDocs?: OpenAPIExternalDocumentation; + items: Array = []; + + depth: number; + + @observable ready?: boolean = true; + @observable active: boolean = false; + //#endregion + + _$ref: string; + operationId?: string; + httpVerb: string; + deprecated: boolean; + requestBody?: RequestBodyModel; + parameters: FieldModel[]; + responses: ResponseModel[]; + path: string; + servers: OpenAPIServer[]; + codeSamples: CodeSample[]; + + constructor(parser: OpenAPIParser, operationSpec: ExtendedOpenAPIOperation, parent?: GroupModel) { + this.id = operationSpec._$ref; + this.name = getOperationSummary(operationSpec); + this.description = operationSpec.description; + + this.parent = parent; + this.externalDocs = operationSpec.externalDocs; + + this._$ref = operationSpec._$ref; + this.deprecated = !!operationSpec.deprecated; + this.httpVerb = operationSpec.httpVerb; + this.deprecated = !!operationSpec.deprecated; + this.operationId = operationSpec.operationId; + this.requestBody = + operationSpec.requestBody && new RequestBodyModel(parser, operationSpec.requestBody); + this.codeSamples = operationSpec['x-code-samples'] || []; + this.path = JsonPointer.baseName(this._$ref, 2); + + this.parameters = (operationSpec.parameters || []).map( + paramOrRef => new FieldModel(parser, paramOrRef, this._$ref), + ); + + this.responses = Object.keys(operationSpec.responses || []) + .filter(code => isNumeric(code) || code === 'default') // filter out other props (e.g. x-props) + .map(code => new ResponseModel(parser, code, operationSpec.responses[code])); + + this.servers = normalizeServers( + parser.specUrl, + operationSpec.servers || parser.spec!.servers || [], + ); + } + + /** + * set operation as active (used by side menu) + */ + @action + activate() { + this.active = true; + } + + /** + * set operation as inactive (used by side menu) + */ + @action + deactivate() { + this.active = false; + } + + getHash() { + return this.operationId !== undefined + ? 'operation/' + this.operationId + : this.parent !== undefined ? this.parent.id + this.id : this.id; + } +} + +function normalizeServers(specUrl: string, servers: OpenAPIServer[]): OpenAPIServer[] { + if (servers.length === 0) { + return [ + { + url: specUrl, + }, + ]; + } + + function normalizeUrl(url: string): string { + url = isAbsolutePath(url) ? url : joinPaths(specUrl, url); + return stripTrailingSlash(url.startsWith('//') ? `${specProtocol}${url}` : url); + } + + const { protocol: specProtocol } = urlParse(specUrl); + + return servers.map(server => { + return { + ...server, + url: normalizeUrl(server.url), + description: server.description || '', + }; + }); +} diff --git a/src/services/models/RequestBody.ts b/src/services/models/RequestBody.ts new file mode 100644 index 00000000..dde14ce6 --- /dev/null +++ b/src/services/models/RequestBody.ts @@ -0,0 +1,20 @@ +import { OpenAPIRequestBody, Referenced } from '../../types'; + +import { MediaContentModel } from './MediaContent'; +import { OpenAPIParser } from '../OpenAPIParser'; + +export class RequestBodyModel { + description: string; + required: boolean; + content?: MediaContentModel; + + constructor(parser: OpenAPIParser, infoOrRef: Referenced) { + const info = parser.deref(infoOrRef); + this.description = info.description || ''; + this.required = !!info.required; + parser.exitRef(infoOrRef); + if (info.content !== undefined) { + this.content = new MediaContentModel(parser, info.content, true); + } + } +} diff --git a/src/services/models/Response.ts b/src/services/models/Response.ts new file mode 100644 index 00000000..b1d1f836 --- /dev/null +++ b/src/services/models/Response.ts @@ -0,0 +1,42 @@ +import { observable, action } from 'mobx'; + +import { OpenAPIResponse, Referenced } from '../../types'; + +import { FieldModel } from './Field'; +import { MediaContentModel } from './MediaContent'; +import { OpenAPIParser } from '../OpenAPIParser'; +import { getStatusCodeType } from '../../utils'; + +export class ResponseModel { + @observable public expanded: boolean = false; + + public content?: MediaContentModel; + public code: string; + public description: string; + public type: string; + public headers: FieldModel[] = []; + + constructor(parser: OpenAPIParser, code: string, infoOrRef: Referenced) { + const info = parser.deref(infoOrRef); + parser.exitRef(infoOrRef); + this.code = code; + if (info.content !== undefined) { + this.content = new MediaContentModel(parser, info.content, false); + } + this.description = info.description || ''; + this.type = getStatusCodeType(code); + + const headers = info.headers; + if (headers !== undefined) { + this.headers = Object.keys(headers).map(name => { + const header = headers[name]; + return new FieldModel(parser, { ...header, name }, ''); + }); + } + } + + @action + toggle() { + this.expanded = !this.expanded; + } +} diff --git a/src/services/models/Schema.ts b/src/services/models/Schema.ts new file mode 100644 index 00000000..d19f96bf --- /dev/null +++ b/src/services/models/Schema.ts @@ -0,0 +1,221 @@ +import { MergedOpenAPISchema } from '../'; +import { observable, action } from 'mobx'; + +import { OpenAPISchema, Referenced } from '../../types'; + +import { FieldModel } from './Field'; +import { OpenAPIParser } from '../OpenAPIParser'; +import { + detectType, + humanizeConstraints, + isNamedDefinition, + isPrimitiveType, + JsonPointer, +} from '../../utils/'; + +// TODO: refactor this model, maybe use getters instead of copying all the values +export class SchemaModel { + _$ref: string; + + type: string; + displayType: string; + typePrefix: string = ''; + title: string; + description: string; + + isPrimitive: boolean; + isCircular: boolean = false; + + format?: string; + nullable: boolean; + deprecated: boolean; + pattern?: string; + example?: any; + enum: any[]; + default?: any; + + constraints: string[]; + + fields?: FieldModel[]; + items?: SchemaModel; + + oneOf?: SchemaModel[]; + oneOfType: string; + discriminatorProp: string; + @observable activeOneOf: number = 0; + + rawSchema: OpenAPISchema; + schema: MergedOpenAPISchema; + + /** + * @param isChild if schema discriminator Child + * When true forces dereferencing in allOfs even if circular + */ + constructor( + parser: OpenAPIParser, + schemaOrRef?: Referenced, + $ref?: string, + isChild: boolean = false, + ) { + if (schemaOrRef === undefined) { + return; + } + + this._$ref = schemaOrRef.$ref || $ref || ''; + this.rawSchema = parser.deref(schemaOrRef); + this.schema = parser.mergeAllOf(this.rawSchema, this._$ref, isChild); + this.init(parser, isChild); + + parser.exitRef(schemaOrRef); + for (let $ref of this.schema.namedParents || []) { + // exit all the refs visited during allOf traverse + parser.exitRef({ $ref }); + } + } + + /** + * Set specified alternative schema as active + * @param idx oneOf index + */ + @action + activateOneOf(idx: number) { + this.activeOneOf = idx; + } + + init(parser: OpenAPIParser, isChild: boolean) { + const schema = this.schema; + this.isCircular = schema['x-circular-ref']; + + this.title = + schema.title || (isNamedDefinition(this._$ref) && JsonPointer.baseName(this._$ref)) || ''; + this.description = schema.description || ''; + this.type = schema.type || detectType(schema); + this.format = schema.format; + this.nullable = !!schema.nullable; + this.enum = schema.enum || []; + this.example = schema.example; + this.deprecated = !!schema.deprecated; + this.pattern = schema.pattern; + + this.constraints = humanizeConstraints(schema); + this.displayType = this.title === '' ? this.type : `${this.title} (${this.type})`; + this.isPrimitive = isPrimitiveType(schema); + this.default = schema.default; + + if (this.isCircular) { + return; + } + + if (!isChild && schema.discriminator !== undefined) { + this.initDiscriminator(schema, parser); + return; + } + + if (schema.oneOf !== undefined) { + this.initOneOf(schema.oneOf, parser); + this.oneOfType = 'One of'; + if (schema.anyOf !== undefined) { + console.warn( + `oneOf and anyOf are not supported on the same level. Skipping anyOf at ${this._$ref}`, + ); + } + return; + } + + if (schema.anyOf !== undefined) { + this.initOneOf(schema.anyOf, parser); + this.oneOfType = 'Any of'; + return; + } + + if (this.type === 'object') { + this.fields = buildFields(parser, schema, this._$ref); + } else if (this.type === 'array' && schema.items) { + this.items = new SchemaModel(parser, schema.items, this._$ref + '/items'); + this.displayType = this.items.displayType; + this.typePrefix = this.items.typePrefix + 'Array of '; + this.isPrimitive = this.items.isPrimitive; + if (this.items.isPrimitive) { + this.enum = this.items.enum; + } + } + } + + private initOneOf(oneOf: OpenAPISchema[], parser: OpenAPIParser) { + this.oneOf = oneOf!.map( + (variant, idx) => + // TODO: merge base schema into each oneOf + new SchemaModel(parser, variant, this._$ref + '/oneOf/' + idx), + ); + this.displayType = this.oneOf.map(schema => schema.displayType).join(' or '); + } + + private initDiscriminator( + schema: OpenAPISchema & { + namedParents?: string[]; + }, + parser: OpenAPIParser, + ) { + this.discriminatorProp = schema.discriminator!.propertyName; + const derived = parser.findDerived([...(schema.namedParents || []), this._$ref]); + + if (schema.oneOf) { + for (let variant of schema.oneOf) { + if (variant.$ref === undefined) continue; + const name = JsonPointer.dirName(variant.$ref); + derived[variant.$ref] = name; + } + } + + const mapping = schema.discriminator!.mapping || {}; + for (let name in mapping) { + derived[mapping[name]] = name; + } + + const refs = Object.keys(derived); + this.oneOf = refs.map(ref => { + const schema = new SchemaModel(parser, parser.byRef(ref)!, ref, true); + schema.title = derived[ref]; + return schema; + }); + } +} + +function buildFields(parser: OpenAPIParser, schema: OpenAPISchema, $ref: string): FieldModel[] { + const props = schema.properties || []; + const additionalProps = schema.additionalProperties; + const defaults = schema.default || {}; + const fields = Object.keys(props || []).map(fieldName => { + const required = + schema.required === undefined ? false : schema.required.indexOf(fieldName) > -1; + + return new FieldModel( + parser, + { + name: fieldName, + required, + schema: { + ...props[fieldName], + default: props[fieldName].default || defaults[fieldName], + }, + }, + $ref + '/properties/' + fieldName, + ); + }); + + if (typeof additionalProps === 'object') { + fields.push( + new FieldModel( + parser, + { + name: '[property name] *', + required: false, + schema: additionalProps, + }, + $ref + '/additionalProperties', + ), + ); + } + + return fields; +} diff --git a/src/services/models/index.ts b/src/services/models/index.ts new file mode 100644 index 00000000..da35301f --- /dev/null +++ b/src/services/models/index.ts @@ -0,0 +1,12 @@ +export * from '../SpecStore'; +export * from './Group.model'; +export * from './Operation'; +export * from './RequestBody'; +export * from './Example'; +export * from './MediaContent'; +export * from './MediaType'; +export * from './Response'; +export * from './Schema'; +export * from './Field'; +export * from './ApiInfo'; +export * from './types'; diff --git a/src/services/models/types.ts b/src/services/models/types.ts new file mode 100644 index 00000000..a53b5359 --- /dev/null +++ b/src/services/models/types.ts @@ -0,0 +1,4 @@ +export type CodeSample = { + lang: string; + source: string; +}; diff --git a/src/styled-components.ts b/src/styled-components.ts new file mode 100644 index 00000000..783d3c10 --- /dev/null +++ b/src/styled-components.ts @@ -0,0 +1,26 @@ +import * as styledComponents from 'styled-components'; +import { ThemedStyledComponentsModule } from 'styled-components'; + +import { ThemeInterface } from './theme'; + +type StyledFunction = styledComponents.ThemedStyledFunction; + +function withProps( + styledFunction: StyledFunction>, +): StyledFunction> { + return styledFunction; +} + +const { + default: styled, + css, + injectGlobal, + keyframes, + ThemeProvider, + withTheme, +} = (styledComponents as ThemedStyledComponentsModule) as ThemedStyledComponentsModule< + ThemeInterface +>; + +export { css, injectGlobal, keyframes, ThemeProvider, withTheme, withProps }; +export default styled; diff --git a/src/theme.ts b/src/theme.ts new file mode 100644 index 00000000..512b09da --- /dev/null +++ b/src/theme.ts @@ -0,0 +1,60 @@ +const theme = { + spacingUnit: 20, + colors: { + main: '#32329f', + success: '#00aa13', + redirect: 'orange', + error: '#e53935', + info: 'skyblue', + text: '#263238', + warning: '#f1c400', + http: { + get: '#6bbd5b', + post: '#248fb2', + put: '#9b708b', + options: '#d3ca12', + patch: '#e09d43', + delete: '#e27a7a', + basic: '#999', + link: '#31bbb6', + }, + }, + schemaView: { + linesColor: '#7f99cf', + }, + baseFont: { + size: '14px', + lineHeight: '1.5', + weight: '300', + family: 'Roboto, sans-serif', + smoothing: 'antialiased', + optimizeSpeed: true, + }, + headingsFont: { + family: 'Montserrat, sans-serif', + }, + code: { + fontSize: '13px', + fontFamily: '"Lucida Console", Monaco, monospace', + }, + links: { + color: undefined, // by default main color + visited: undefined, // by default main color + hover: undefined, // by default main color + }, + menu: { + width: '260px', + backgroundColor: '#fafafa', + }, + logo: { + maxHeight: '120px', + }, + rightPanel: { + backgroundColor: '#263238', + width: 40, + }, +}; + +export default theme; + +export type ThemeInterface = typeof theme; diff --git a/src/types/components.ts b/src/types/components.ts new file mode 100644 index 00000000..2ee0de1a --- /dev/null +++ b/src/types/components.ts @@ -0,0 +1,5 @@ +import { AppStore } from '../services/AppStore'; + +export interface BaseContainerProps { + store: AppStore; +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 00000000..48387488 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,5 @@ +export * from './open-api'; + +export type Diff = ({ [P in T]: P } & + { [P in U]: never } & { [x: string]: never })[T]; +export type Omit = { [P in Diff]: T[P] }; diff --git a/src/types/open-api.ts b/src/types/open-api.ts new file mode 100644 index 00000000..c33ffc12 --- /dev/null +++ b/src/types/open-api.ts @@ -0,0 +1,227 @@ +import { Omit } from './'; + +export type OpenAPISpec = { + openapi: string; + info: OpenAPIInfo; + servers?: OpenAPIServer[]; + paths: OpenAPIPaths; + components?: OpenAPIComponents; + security?: OpenAPISecurityRequirement[]; + tags?: OpenAPITag[]; + externalDocs?: OpenAPIExternalDocumentation; +}; + +export interface OpenAPIInfo { + title: string; + version: string; + + description?: string; + termsOfService?: string; + contact?: OpenAPIContact; + license?: OpenAPILicense; +} + +export type OpenAPIServer = { + url: string; + description?: string; + variables?: { [name: string]: OpenAPIServerVariable }; +}; + +export type OpenAPIServerVariable = { + enum?: string[]; + default: string; + description?: string; +}; + +export type OpenAPIPaths = { [path: string]: OpenAPIPath }; +export type OpenAPIRef = { + $ref: string; +}; + +export type Referenced = OpenAPIRef | T; + +export type OpenAPIPath = + // | OpenAPIRef // paths can't be external in redoc because they are prebundled + // | { + { + summary?: string; + description?: string; + get?: OpenAPIOperation; + put?: OpenAPIOperation; + post?: OpenAPIOperation; + delete?: OpenAPIOperation; + options?: OpenAPIOperation; + head?: OpenAPIOperation; + patch?: OpenAPIOperation; + trace?: OpenAPIOperation; + servers?: OpenAPIServer[]; + parameters?: Referenced[]; + }; + +export type OpenAPIOperation = { + tags?: string[]; + summary?: string; + description?: string; + externalDocs?: OpenAPIExternalDocumentation; + operationId?: string; + parameters?: Referenced[]; + requestBody?: Referenced; + responses: OpenAPIResponses; + callbacks?: { [name: string]: Referenced }; + deprecated?: boolean; + security?: OpenAPISecurityRequirement[]; + servers?: OpenAPIServer[]; +}; + +export type OpenAPIParameter = { + name: string; + in?: OpenAPIParameterLocation; + description?: string; + required?: boolean; + deprecated?: boolean; + allowEmptyValue?: boolean; + style?: OpenAPIParameterStyle; + explode?: boolean; + allowReserved?: boolean; + schema?: Referenced; + example?: any; + examples?: { [media: string]: Referenced }; + content?: { [media: string]: OpenAPIMediaType }; +}; + +export type OpenAPIExample = { + value: any; + summary?: string; + description?: string; + externalValue?: string; +}; + +export type OpenAPISchema = { + $ref?: string; + type?: string; + properties?: { [name: string]: OpenAPISchema }; + additionalProperties?: boolean | OpenAPISchema; + description?: string; + default?: any; + items?: OpenAPISchema; + required?: string[]; + readOnly?: boolean; + writeOnly?: boolean; + deprecated?: boolean; + format?: string; + externalDocs?: OpenAPIExternalDocumentation; + discriminator?: OpenAPIDiscriminator; + nullable?: boolean; + oneOf?: OpenAPISchema[]; + anyOf?: OpenAPISchema[]; + allOf?: OpenAPISchema[]; + not?: OpenAPISchema; + + title?: string; + multipleOf?: number; + maximum?: number; + exclusiveMaximum?: boolean; + minimum?: number; + exclusiveMinimum?: boolean; + maxLength?: number; + minLength?: number; + pattern?: string; + maxItems?: number; + minItems?: number; + uniqueItems?: boolean; + maxProperties?: number; + minProperties?: number; + enum?: any[]; + example?: any; +}; + +export type OpenAPIDiscriminator = { + propertyName: string; + mapping?: { [name: string]: string }; +}; + +export type OpenAPIMediaType = { + schema?: Referenced; + example?: any; + examples?: { [name: string]: Referenced }; + encoding?: { [field: string]: OpenAPIEncoding }; +}; + +export type OpenAPIEncoding = { + contentType: string; + headers?: { [name: string]: Referenced }; + style: OpenAPIParameterStyle; + explode: boolean; + allowReserved: boolean; +}; + +export type OpenAPIParameterLocation = 'query' | 'header' | 'path' | 'cookie'; +export type OpenAPIParameterStyle = + | 'matrix' + | 'label' + | 'form' + | 'simple' + | 'spaceDelimited' + | 'pipeDelimited' + | 'deepObject'; + +export type OpenAPIRequestBody = { + description?: string; + required?: boolean; + content: { [mime: string]: OpenAPIMediaType }; +}; + +export type OpenAPIResponses = { + [code: string]: OpenAPIResponse; +}; + +export type OpenAPIResponse = { + description?: string; + headers?: { [name: string]: Referenced }; + content: { [mime: string]: OpenAPIMediaType }; + links: { [name: string]: Referenced }; +}; + +export type OpenAPILink = {}; + +export type OpenAPIHeader = Omit; + +export type OpenAPICallback = {}; + +export type OpenAPIComponents = { + schemas?: { [name: string]: Referenced }; + responses?: { [name: string]: Referenced }; + parameters?: { [name: string]: Referenced }; + examples?: { [name: string]: Referenced }; + requestBodies?: { [name: string]: Referenced }; + headers?: { [name: string]: Referenced }; + securitySchemes?: { [name: string]: Referenced }; + links?: { [name: string]: Referenced }; + callbacks?: { [name: string]: Referenced }; +}; + +export type OpenAPISecurityRequirement = {}; +export type OpenAPISecurityScheme = {}; + +export type OpenAPITag = { + name: string; + description?: string; + externalDocs?: OpenAPIExternalDocumentation; + 'x-displayName'?: string; +}; + +export type OpenAPIExternalDocumentation = { + description?: string; + url?: string; +}; + +export type OpenAPIContact = { + name?: string; + url?: string; + email?: string; +}; + +export type OpenAPILicense = { + name: string; + url?: string; +}; diff --git a/lib/utils/JsonPointer.ts b/src/utils/JsonPointer.ts similarity index 54% rename from lib/utils/JsonPointer.ts rename to src/utils/JsonPointer.ts index 80009c08..bad92bb8 100644 --- a/lib/utils/JsonPointer.ts +++ b/src/utils/JsonPointer.ts @@ -16,12 +16,12 @@ export class JsonPointer { * // returns foo * JsonPointerHelper.baseName('/path/foo/subpath', 2) */ - static baseName(pointer, level=1) { - let tokens = JsonPointer.parse(pointer); - return tokens[tokens.length - (level)]; - } + static baseName(pointer, level = 1) { + let tokens = JsonPointer.parse(pointer); + return tokens[tokens.length - level]; + } - /** + /** * returns dirname of pointer * if level > 1 returns corresponding dirname in the hierarchy * @example @@ -30,12 +30,12 @@ export class JsonPointer { * // returns /path * JsonPointerHelper.dirName('/path/foo/subpath', 2) */ - static dirName(pointer, level=1) { - let tokens = JsonPointer.parse(pointer); - return JsonPointerLib.compile(tokens.slice(0, tokens.length - level)); - } + static dirName(pointer, level = 1) { + let tokens = JsonPointer.parse(pointer); + return JsonPointerLib.compile(tokens.slice(0, tokens.length - level)); + } - /** + /** * returns relative path tokens * @example * // returns ['subpath'] @@ -43,50 +43,50 @@ export class JsonPointer { * // returns ['foo', 'subpath'] * JsonPointerHelper.relative('/path', '/path/foo/subpath') */ - static relative(from, to):string[] { - let fromTokens = JsonPointer.parse(from); - let toTokens = JsonPointer.parse(to); - return toTokens.slice(fromTokens.length); - } + static relative(from, to): string[] { + let fromTokens = JsonPointer.parse(from); + let toTokens = JsonPointer.parse(to); + return toTokens.slice(fromTokens.length); + } - /** + /** * overridden JsonPointer original parse to take care of prefixing '#' symbol * that is not valid JsonPointer */ - static parse(pointer) { - let ptr = pointer; - if (ptr.charAt(0) === '#') { - ptr = ptr.substring(1); - } - return origParse(ptr); - } + static parse(pointer) { + let ptr = pointer; + if (ptr.charAt(0) === '#') { + ptr = ptr.substring(1); + } + return origParse(ptr); + } - /** + /** * Creates a JSON pointer path, by joining one or more tokens to a base path. * * @param {string} base - The base path * @param {string|string[]} tokens - The token(s) to append (e.g. ["name", "first"]) * @returns {string} */ - static join(base, tokens) { - // TODO: optimize - let baseTokens = JsonPointer.parse(base); - let resTokens = baseTokens.concat(tokens); - return JsonPointerLib.compile(resTokens); - } + static join(base, tokens) { + // TODO: optimize + let baseTokens = JsonPointer.parse(base); + let resTokens = baseTokens.concat(tokens); + return JsonPointerLib.compile(resTokens); + } - static get(object: Object, pointer:string) { - return JsonPointerLib.get(object, pointer); - } + static get(object: Object, pointer: string) { + return JsonPointerLib.get(object, pointer); + } - static compile(tokens: string[]) { - return JsonPointerLib.compile(tokens); - } + static compile(tokens: string[]) { + return JsonPointerLib.compile(tokens); + } - static escape(pointer: string) { - return JsonPointerLib.escape(pointer); - } + static escape(pointer: string) { + return JsonPointerLib.escape(pointer); + } } -JsonPointerLib.parse = JsonPointer.parse; +(JsonPointerLib as any).parse = JsonPointer.parse; Object.assign(JsonPointer, JsonPointerLib); export default JsonPointer; diff --git a/src/utils/__tests__/helpers.test.ts b/src/utils/__tests__/helpers.test.ts new file mode 100644 index 00000000..0d3e3947 --- /dev/null +++ b/src/utils/__tests__/helpers.test.ts @@ -0,0 +1,23 @@ +import { mapWithLast } from '../helpers'; + +describe('Utils', () => { + describe('helpers', () => { + test('mapWithLast', () => { + const arr = [1, 2, 3]; + const fn = (...args) => args; + + const actual = mapWithLast(arr, fn); + const expected = [[1, false], [2, false], [3, true]]; + expect(actual).toEqual(expected); + }); + + test('mapWithLast for empty array', () => { + const arr = []; + const fn = (...args) => args; + + const actual = mapWithLast(arr, fn); + const expected = []; + expect(actual).toEqual(expected); + }); + }); +}); diff --git a/src/utils/__tests__/openapi.test.ts b/src/utils/__tests__/openapi.test.ts new file mode 100644 index 00000000..acdad223 --- /dev/null +++ b/src/utils/__tests__/openapi.test.ts @@ -0,0 +1,186 @@ +import { + detectType, + getOperationSummary, + getStatusCodeType, + isOperationName, + isPrimitiveType, +} from '../'; + +describe('Utils', () => { + describe('openapi getStatusCode', () => { + it('Should return info for status codes within 100 and 200', () => { + expect(getStatusCodeType(100)).toEqual('info'); + expect(getStatusCodeType(150)).toEqual('info'); + expect(getStatusCodeType(199)).toEqual('info'); + }); + + it('Should return success for status codes within 200 and 300', () => { + expect(getStatusCodeType(200)).toEqual('success'); + expect(getStatusCodeType(250)).toEqual('success'); + expect(getStatusCodeType(299)).toEqual('success'); + }); + it('Should return redirect for status codes within 300 and 400', () => { + expect(getStatusCodeType(300)).toEqual('redirect'); + expect(getStatusCodeType(399)).toEqual('redirect'); + }); + it('Should return error for status codes above 400', () => { + expect(getStatusCodeType(400)).toEqual('error'); + expect(getStatusCodeType(500)).toEqual('error'); + expect(getStatusCodeType(599)).toEqual('error'); + }); + + it('Should throw for incorrect HTTP code', () => { + expect(() => getStatusCodeType(99)).toThrow('invalid HTTP code'); + expect(() => getStatusCodeType(600)).toThrow('invalid HTTP code'); + }); + }); + + describe('openapi isOperationName', () => { + it('Should return `true` for correct HTTP verbs', () => { + expect(isOperationName('get')).toEqual(true); + expect(isOperationName('post')).toEqual(true); + expect(isOperationName('put')).toEqual(true); + expect(isOperationName('head')).toEqual(true); + expect(isOperationName('patch')).toEqual(true); + expect(isOperationName('delete')).toEqual(true); + expect(isOperationName('options')).toEqual(true); + }); + + it('Should return `false` for incorrect HTTP verbs', () => { + expect(isOperationName('properties')).toEqual(false); + expect(isOperationName('x-name')).toEqual(false); + expect(isOperationName('fix')).toEqual(false); + }); + }); + + let sixtyLetterStr = ''; + for (let i = 0; i < 60; i++) { + sixtyLetterStr += 'a'; + } + + describe('openapi getOperationSummary', () => { + it('Should return operation self summary if exists', () => { + const operation = { + summary: 'test', + operationId: 'fail', + description: 'fail', + }; + expect(getOperationSummary(operation as any)).toEqual('test'); + }); + + it('Should return operationId if no summary', () => { + const operation = { + operationId: 'test', + description: 'fail', + }; + expect(getOperationSummary(operation as any)).toEqual('test'); + }); + + it('Should return description if no summary and operationId', () => { + const operation = { + description: 'test', + }; + expect(getOperationSummary(operation as any)).toEqual('test'); + }); + + it('Should return only first 50 description letter if no summary and operationId', () => { + const operation = { + description: sixtyLetterStr, + }; + expect(sixtyLetterStr.length).toBeGreaterThan(50); + expect(getOperationSummary(operation as any).length).toBe(50); + }); + + it('Should return if no info', () => { + const operation = { + description: undefined, + }; + expect(getOperationSummary(operation as any)).toBe(''); + }); + }); + + describe('openapi detectType', () => { + it('Should detect object.type if it is specified', () => { + expect( + detectType({ + type: 'object', + }), + ).toBe('object'); + + expect( + detectType({ + type: 'object', + minimum: 1, + }), + ).toBe('object'); + }); + + const tests = { + number: ['multipleOf', 'maximum', 'exclusiveMaximum', 'minimum', 'exclusiveMinimum'], + + string: ['pattern', 'minLength', 'maxLength'], + + array: ['items', 'maxItems', 'minItems', 'uniqueItems'], + object: ['maxProperties', 'minProperties', 'required', 'additionalProperties', 'properties'], + }; + + Object.keys(tests).forEach(name => { + it(`Should detect ${name} if ${name} properties are present`, () => { + tests[name].forEach(propName => { + expect( + detectType({ + [propName]: 0, + }), + ).toBe(name); + }); + }); + }); + }); + + describe('openapi isPrimitiveType', () => { + it('Should return true for empty object', () => { + const schema = { + type: 'object', + }; + expect(isPrimitiveType(schema)).toEqual(true); + }); + + it('Should return false for object with props', () => { + const schema = { + type: 'object', + properties: { + a: { + type: 'string', + }, + }, + }; + expect(isPrimitiveType(schema)).toEqual(false); + }); + + it('Should return false for array with non-empty objects', () => { + const schema = { + type: 'array', + items: { + type: 'object', + properties: { + a: { + type: 'string', + }, + }, + }, + }; + expect(isPrimitiveType(schema)).toEqual(false); + }); + + it('should return false for object with additionalProperties', () => { + const schema = { + type: 'array', + items: { + type: 'object', + additionalProperties: true, + }, + }; + expect(isPrimitiveType(schema)).toEqual(false); + }); + }); +}); diff --git a/src/utils/__tests__/styled.test.ts b/src/utils/__tests__/styled.test.ts new file mode 100644 index 00000000..630bf950 --- /dev/null +++ b/src/utils/__tests__/styled.test.ts @@ -0,0 +1,19 @@ +import * as react from 'React'; +import { transparentizeHex } from '../styled'; + +describe('transparentizeHex', () => { + test('simple transparentize', () => { + const res = transparentizeHex('#000000', 0.5); + expect(res).toBe('rgba(0, 0, 0, 0.5)'); + }); + + test('transparentize hex shorthand', () => { + const res = transparentizeHex('#123', 0.5); + expect(res).toBe('rgba(17, 34, 51, 0.5)'); + }); + + test('do not transparentize (withot last param)', () => { + const res = transparentizeHex('#010203'); + expect(res).toBe('rgb(1, 2, 3)'); + }); +}); diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts new file mode 100644 index 00000000..d05bb887 --- /dev/null +++ b/src/utils/helpers.ts @@ -0,0 +1,65 @@ +/** + * Maps over array passing `isLast` bool to iterator as the second arguemnt + */ +export function mapWithLast(array: T[], iteratee: (item: T, isLast: boolean) => P) { + const res: P[] = []; + for (let i = 0; i < array.length - 1; i++) { + res.push(iteratee(array[i], false)); + } + if (array.length !== 0) { + res.push(iteratee(array[array.length - 1], true)); + } + return res; +} + +/** + * Creates an object with the same keys as object and values generated by running each + * own enumerable string keyed property of object thru iteratee. + * The iteratee is invoked with three arguments: (value, key, object). + * + * @param object the object to iterate over + * @param iteratee the function invoked per iteration. + */ +export function mapValues( + object: Dict, + iteratee: (val: T, key: string, obj: Dict) => P, +): Dict

{ + const res: { [key: string]: P } = {}; + for (let key in object) { + if (object.hasOwnProperty(key)) { + res[key] = iteratee(object[key], key, object); + } + } + return res; +} + +/** + * flattens collection using `prop` field as a children + * @param items collection items + * @param prop item property with child elements + */ +export function flattenByProp(items: T[], prop: P): T[] { + const res: T[] = []; + const iterate = (items: T[]) => { + for (let i = 0; i < items.length; i++) { + const item = items[i]; + res.push(item); + if (item[prop]) { + iterate(item[prop]); + } + } + }; + iterate(items); + return res; +} + +export function stripTrailingSlash(path: string): string { + if (path.endsWith('/')) { + return path.substring(0, path.length - 1); + } + return path; +} + +export function isAbsolutePath(path: string): boolean { + return /^(?:[a-z]+:)?/i.test(path); +} diff --git a/src/utils/highlight.ts b/src/utils/highlight.ts new file mode 100644 index 00000000..676313d4 --- /dev/null +++ b/src/utils/highlight.ts @@ -0,0 +1,55 @@ +import * as Prism from 'prismjs'; +import 'prismjs/components/prism-actionscript.js'; +import 'prismjs/components/prism-c.js'; +import 'prismjs/components/prism-cpp.js'; +import 'prismjs/components/prism-csharp.js'; +import 'prismjs/components/prism-php.js'; +import 'prismjs/components/prism-coffeescript.js'; +import 'prismjs/components/prism-go.js'; +import 'prismjs/components/prism-haskell.js'; +import 'prismjs/components/prism-java.js'; +import 'prismjs/components/prism-lua.js'; +import 'prismjs/components/prism-matlab.js'; +import 'prismjs/components/prism-perl.js'; +import 'prismjs/components/prism-python.js'; +import 'prismjs/components/prism-r.js'; +import 'prismjs/components/prism-ruby.js'; +import 'prismjs/components/prism-bash.js'; +import 'prismjs/components/prism-swift.js'; +import 'prismjs/components/prism-objectivec.js'; +import 'prismjs/components/prism-scala.js'; +import 'prismjs/components/prism-markup.js'; // xml + +import 'prismjs/themes/prism-dark.css'; // dark theme + +const DEFAULT_LANG = 'clike'; + +/** + * map language names to Prism.js names + */ +function mapLang(lang: string): string { + return ( + { + json: 'js', + 'c++': 'cpp', + 'c#': 'csharp', + 'objective-c': 'objectivec', + shell: 'bash', + viml: 'vim', + }[lang] || DEFAULT_LANG + ); +} + +/** + * Highlight source code string using Prism.js + * @param source source code to highlight + * @param lang highlight language + * @return highlighted souce code as **html string** + */ +export function highlight(source: string, lang: string): string { + let grammar = Prism.languages[lang]; + if (!grammar) { + grammar = Prism.languages[mapLang(lang)]; + } + return Prism.highlight(source, grammar); +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 00000000..4c5d506e --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,6 @@ +export * from './JsonPointer'; +export * from './styled'; + +export * from './openapi'; +export * from './helpers'; +export * from './highlight'; diff --git a/lib/utils/JsonFormatterPipe.ts b/src/utils/jsonToHtml.ts similarity index 65% rename from lib/utils/JsonFormatterPipe.ts rename to src/utils/jsonToHtml.ts index af821d22..417fa6cf 100644 --- a/lib/utils/JsonFormatterPipe.ts +++ b/src/utils/jsonToHtml.ts @@ -1,26 +1,24 @@ -'use strict'; -import { Pipe, PipeTransform } from '@angular/core'; -import { DomSanitizer } from '@angular/platform-browser'; - -function isBlank(obj) { - return obj === undefined || obj === null; -} - -var level = 1; +let level = 1; const COLLAPSE_LEVEL = 2; -@Pipe({ name: 'jsonFormatter' }) -export class JsonFormatter implements PipeTransform { - constructor(private sanitizer: DomSanitizer) {} - transform(value) { - if (isBlank(value)) return value; - return this.sanitizer.bypassSecurityTrustHtml(jsonToHTML(value)); - } +export function jsonToHTML(json) { + level = 1; + var output = ''; + output += '

'; + output += valueToHTML(json); + output += '
'; + return output; } function htmlEncode(t) { - return t != undefined ? - t.toString().replace(/&/g, '&').replace(/"/g, '"').replace(//g, '>') : ''; + return t != undefined + ? t + .toString() + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(//g, '>') + : ''; } function decorateWithSpan(value, className) { @@ -28,7 +26,8 @@ function decorateWithSpan(value, className) { } function valueToHTML(value) { - var valueType = typeof value, output = ''; + var valueType = typeof value, + output = ''; if (value == undefined) { output += decorateWithSpan('null', 'type-null'); } else if (value && value.constructor === Array) { @@ -45,7 +44,13 @@ function valueToHTML(value) { output += decorateWithSpan(value, 'type-number'); } else if (valueType === 'string') { if (/^(http|https):\/\/[^\s]+$/.test(value)) { - output += decorateWithSpan('"', 'type-string') + '' + htmlEncode(value) + '' + + output += + decorateWithSpan('"', 'type-string') + + '' + + htmlEncode(value) + + '' + decorateWithSpan('"', 'type-string'); } else { output += decorateWithSpan('"' + value + '"', 'type-string'); @@ -60,7 +65,8 @@ function valueToHTML(value) { function arrayToHTML(json) { var collapsed = level > COLLAPSE_LEVEL ? 'collapsed' : ''; var i, length; - var output = '
[
    '; + var output = + '
    [
      '; var hasContents = false; for (i = 0, length = json.length; i < length; i++) { hasContents = true; @@ -80,14 +86,18 @@ function arrayToHTML(json) { function objectToHTML(json) { var collapsed = level > COLLAPSE_LEVEL ? 'collapsed' : ''; - var i, key, length, keys = Object.keys(json); - var output = '
      {
        '; + var i, + key, + length, + keys = Object.keys(json); + var output = + '
        {
          '; var hasContents = false; for (i = 0, length = keys.length; i < length; i++) { key = keys[i]; hasContents = true; output += '