mirror of
https://github.com/Redocly/redoc.git
synced 2025-02-23 21:40:32 +03:00
Merge remote-tracking branch 'Rebilly/master'
This commit is contained in:
commit
2145d15c02
71
CHANGELOG.md
71
CHANGELOG.md
|
@ -1,3 +1,74 @@
|
|||
<a name="1.11.0"></a>
|
||||
# 1.11.0 (2017-03-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* do not hang when swagger doesn't contain any paths ([e4f5388](https://github.com/Rebilly/ReDoc/commit/e4f5388)), closes [#216](https://github.com/Rebilly/ReDoc/issues/216)
|
||||
[#201](https://github.com/Rebilly/ReDoc/issues/201)
|
||||
* optimize and support inherited discriminator ([64e5741](https://github.com/Rebilly/ReDoc/commit/64e5741))
|
||||
* redoc hangs when indexing recursive discriminator-based definitions ([1e96f88](https://github.com/Rebilly/ReDoc/commit/1e96f88))
|
||||
* wrong warnings for $ref not single ([193f4bf](https://github.com/Rebilly/ReDoc/commit/193f4bf)), closes [#221](https://github.com/Rebilly/ReDoc/issues/221)
|
||||
* x-extendedDiscriminator not working ([4899f3e](https://github.com/Rebilly/ReDoc/commit/4899f3e)), closes [#217](https://github.com/Rebilly/ReDoc/issues/217)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* copy pretty-printed JSON ([e99d66d](https://github.com/Rebilly/ReDoc/commit/e99d66d)), closes [#219](https://github.com/Rebilly/ReDoc/issues/219)
|
||||
* support for OpenAPI object as a parameter for `init` ([d99f256](https://github.com/Rebilly/ReDoc/commit/d99f256)), closes [#224](https://github.com/Rebilly/ReDoc/issues/224)
|
||||
|
||||
<a name="1.10.2"></a>
|
||||
## 1.10.2 (2017-03-01)
|
||||
|
||||
### Bug Fixes
|
||||
* clear page fragment when scroll to the beginning
|
||||
* update docs for x-tagGroup, add warning [#215](https://github.com/Rebilly/ReDoc/issues/215)
|
||||
* show warning for non-used in tagGroup tags
|
||||
|
||||
<a name="1.10.1"></a>
|
||||
## 1.10.1 (2017-02-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve x-servers dropdown animation performance ([69c7d98](https://github.com/Rebilly/ReDoc/commit/69c7d98))
|
||||
|
||||
|
||||
|
||||
<a name="1.10.0"></a>
|
||||
# 1.10.0 (2017-02-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
* Revert: remove unused hide-hostname option ([7031176](https://github.com/Rebilly/ReDoc/commit/7031176))
|
||||
|
||||
### Features
|
||||
|
||||
* new option `required-props-first` ([c724df4](https://github.com/Rebilly/ReDoc/commit/c724df4)), closes [#191](https://github.com/Rebilly/ReDoc/issues/191)
|
||||
* update fragment while scrolling and on menu clicks ([66c06b3](https://github.com/Rebilly/ReDoc/commit/66c06b3)), closes [#138](https://github.com/Rebilly/ReDoc/issues/138) [#202](https://github.com/Rebilly/ReDoc/issues/202)
|
||||
|
||||
|
||||
|
||||
<a name="1.9.0"></a>
|
||||
# 1.9.0 (2017-02-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* do not crash if version is not string ([accd016](https://github.com/Rebilly/ReDoc/commit/accd016)), closes [#208](https://github.com/Rebilly/ReDoc/issues/208)
|
||||
* long paths break EndpointLink ui ([8472045](https://github.com/Rebilly/ReDoc/commit/8472045))
|
||||
* remove unused hide-hostname option ([7031176](https://github.com/Rebilly/ReDoc/commit/7031176))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add support for `x-servers` ([fd49082](https://github.com/Rebilly/ReDoc/commit/fd49082))
|
||||
* Color of "default" Response depends on other successful responses are specified ([9d0dd25](https://github.com/Rebilly/ReDoc/commit/9d0dd25)), closes [#197](https://github.com/Rebilly/ReDoc/issues/197)
|
||||
* improved type string with minLength == maxLength ([e76bcc3](https://github.com/Rebilly/ReDoc/commit/e76bcc3)), closes [#212](https://github.com/Rebilly/ReDoc/issues/212)
|
||||
* show type string with minLength 1 as "non-empty" ([d175a4d](https://github.com/Rebilly/ReDoc/commit/d175a4d)), closes [#192](https://github.com/Rebilly/ReDoc/issues/192)
|
||||
|
||||
|
||||
|
||||
<a name="1.8.1"></a>
|
||||
## 1.8.1 (2017-02-23)
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@ We host the latest and all the previous ReDoc releases on GitHub Pages-based **C
|
|||
<head>
|
||||
<title>ReDoc</title>
|
||||
<!-- needed for adaptive design -->
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!--
|
||||
|
@ -122,6 +123,7 @@ ReDoc makes use of the following [vendor extensions](http://swagger.io/specifica
|
|||
* [`x-logo`](docs/redoc-vendor-extensions.md#x-logo) - is used to specify API logo
|
||||
* [`x-traitTag`](docs/redoc-vendor-extensions.md#x-traitTag) - useful for handling out common things like Pagination, Rate-Limits, etc
|
||||
* [`x-code-samples`](docs/redoc-vendor-extensions.md#x-code-samples) - specify operation code samples
|
||||
* [`x-examples`](docs/redoc-vendor-extensions.md#x-examples) - specify JSON example for requests
|
||||
* [`x-nullable`](docs/redoc-vendor-extensions.md#nullable) - mark schema param as a nullable
|
||||
* [`x-displayName`](docs/redoc-vendor-extensions.md#x-displayname) - specify human-friendly names for the menu categories
|
||||
* [`x-tagGroups`](docs/redoc-vendor-extensions.md#x-tagGroups) - group tags by categories in the side menu
|
||||
|
@ -136,14 +138,16 @@ ReDoc makes use of the following [vendor extensions](http://swagger.io/specifica
|
|||
* **function**: A getter function. Must return a number representing the offset (in pixels);
|
||||
* `suppress-warnings` - if set, warnings are not rendered at the top of documentation (they still are logged to the console).
|
||||
* `lazy-rendering` - if set, enables lazy rendering mode in ReDoc. This mode is useful for APIs with big number of operations (e.g. > 50). In this mode ReDoc shows initial screen ASAP and then renders the rest operations asynchronously while showing progress bar on the top. Check out the [demo](\\rebilly.github.io/ReDoc) for the example.
|
||||
* `hide-hostname` - if set, the protocol and hostname is not shown in the method definition.
|
||||
* `expand-responses` - specify which responses to expand by default by response codes. Values should be passed as comma-separated list without spaces e.g. `expand-responses="200,201"`. Special value `"all"` expands all responses by default. Be careful: this option can slow-down documentation rendering time.
|
||||
* `required-props-first` - show required properties first ordered in the same order as in `required` array.
|
||||
|
||||
## Advanced usage
|
||||
Instead of adding `spec-url` attribute to the `<redoc>` element you can initialize ReDoc via globally exposed `Redoc` object:
|
||||
```js
|
||||
Redoc.init(specUrl, options)
|
||||
Redoc.init(specOrSpecUrl, options)
|
||||
```
|
||||
|
||||
`specOrSpecUrl` is either JSON object with specification or an URL to the spec in `JSON` or `YAML` format.
|
||||
`options` is javascript object with camel-cased version of `<redoc>` tag attribute names as the keys, e.g.:
|
||||
```js
|
||||
Redoc.init('http://petstore.swagger.io/v2/swagger.json', {
|
||||
|
|
|
@ -110,9 +110,7 @@ module.exports = function (options) {
|
|||
'AOT': options.AOT
|
||||
}),
|
||||
|
||||
new StringReplacePlugin(),
|
||||
new webpack.NormalModuleReplacementPlugin(/node_modules\/rxjs\/operator\/.*/, root('build/empty.js')),
|
||||
new webpack.NormalModuleReplacementPlugin(/node_modules\/rxjs\/testing\//, root('build/empty.js'))
|
||||
new StringReplacePlugin()
|
||||
],
|
||||
node: {
|
||||
global: true,
|
||||
|
|
|
@ -13,7 +13,8 @@ Backported from OpenAPI 3.0 [`servers`](https://github.com/OAI/OpenAPI-Specifica
|
|||
| x-tagGroups | [ [Tag Group Object](#tagGroupObject) ] | A list of tag groups |
|
||||
|
||||
###### Usage in Redoc
|
||||
`x-tagGroups` is used to group tags in the side menu
|
||||
`x-tagGroups` is used to group tags in the side menu.
|
||||
If you are going to use `x-tagGroups`, please make sure you **add all tags to a group**, since a tag that is not in a group, **will not be displayed** at all!
|
||||
|
||||
#### <a name="tagGroupObject"></a>Tag Group Object
|
||||
Information about tags group
|
||||
|
@ -168,6 +169,17 @@ lang: JavaScript
|
|||
source: console.log('Hello World');
|
||||
```
|
||||
|
||||
### Parameter Object vendor extensions
|
||||
Extends OpenAPI [Parameter Object](http://swagger.io/specification/#parameterObject)
|
||||
#### x-examples
|
||||
| Field Name | Type | Description |
|
||||
| :------------- | :------: | :---------- |
|
||||
| x-examples | [Example Object](http://swagger.io/specification/#exampleObject) | Object that contains examples for the request. Applies when `in` is `body` and mime-type is `application/json` |
|
||||
|
||||
###### Usage in ReDoc
|
||||
`x-examples` are rendered in the JSON tab on the right panel of ReDoc.
|
||||
|
||||
|
||||
### Schema Object vendor extensions
|
||||
Extends OpenAPI [Schema Object](http://swagger.io/specification/#schemaObject)
|
||||
#### x-nullable
|
||||
|
@ -177,3 +189,57 @@ Extends OpenAPI [Schema Object](http://swagger.io/specification/#schemaObject)
|
|||
|
||||
###### Usage in ReDoc
|
||||
Schemas marked as `x-nullable` are marked in ReDoc with the label Nullable
|
||||
|
||||
#### x-extendedDiscriminator
|
||||
**ATTENTION**: This is ReDoc-specific vendor extension. It won't be supported by other tools.
|
||||
|
||||
| Field Name | Type | Description |
|
||||
| :------------- | :------: | :---------- |
|
||||
| x-extendedDiscriminator | string | specifies extended discriminator |
|
||||
|
||||
###### Usage in ReDoc
|
||||
ReDoc uses this vendor extension to solve name-clash issues with the standard `discriminator`.
|
||||
Value of this field specifies the field which will be used as a extended discriminator.
|
||||
ReDoc displays definition with selectpicker using which user can select value of the `x-extendedDiscriminator`-marked field.
|
||||
ReDoc displays the definition which is derived from the current (using `allOf`) and has `enum` with only one value which is the same as the selected value of the field specified as `x-extendedDiscriminator`.
|
||||
|
||||
###### x-extendedDiscriminator example
|
||||
|
||||
```yaml
|
||||
|
||||
Payment:
|
||||
x-extendedDiscriminator: type
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
|
||||
CashPayment:
|
||||
allOf:
|
||||
- $ref: "#/definitions/Payment"
|
||||
- properties:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- cash
|
||||
currency:
|
||||
type: string
|
||||
|
||||
PayPalPayment:
|
||||
allOf:
|
||||
- $ref: "#/definitions/Payment"
|
||||
- properties:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- paypal
|
||||
userEmail:
|
||||
type: string
|
||||
```
|
||||
|
||||
In the example above the names of definitions (`PayPalPayment`) are named differently than
|
||||
names in the payload (`paypal`) which is not supported by default `discriminator`.
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<svg class="expand-icon" xmlns="http://www.w3.org/2000/svg" version="1.1" x="0" y="0" viewBox="0 0 24 24" xml:space="preserve">
|
||||
<polygon fill="white" points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "/>
|
||||
</svg>
|
||||
<div class="servers-overlay" [@overlayExpand]="expanded ? 'expanded' : 'collapsed'">
|
||||
<div class="servers-overlay">
|
||||
<div *ngFor="let server of servers" class="server-item">
|
||||
<div class="description" [innerHtml]="server.description | marked"></div>
|
||||
<div select-on-click class="url">
|
||||
|
|
|
@ -91,6 +91,10 @@
|
|||
position: absolute;
|
||||
}
|
||||
|
||||
.servers-overlay {
|
||||
transform: translateY(-50%) scaleY(0);
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
:host.expanded {
|
||||
> .method-endpoint {
|
||||
border-color: $side-bar-bg-color;
|
||||
|
@ -101,4 +105,8 @@
|
|||
.expand-icon {
|
||||
transform: rotateZ(180deg);
|
||||
}
|
||||
|
||||
.servers-overlay {
|
||||
transform: translateY(0%) scaleY(1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import { getChildDebugElement } from '../../../tests/helpers';
|
|||
|
||||
import { EndpointLink } from './endpoint-link';
|
||||
import { SpecManager } from '../../utils/spec-manager';
|
||||
import { OptionsService } from '../../services/';
|
||||
|
||||
describe('Redoc components', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -20,9 +21,11 @@ describe('Redoc components', () => {
|
|||
let builder;
|
||||
let component: EndpointLink;
|
||||
let specMgr: SpecManager;
|
||||
let opts: OptionsService;
|
||||
|
||||
beforeEach(async(inject([SpecManager], (_specMgr) => {
|
||||
beforeEach(async(inject([SpecManager, OptionsService], (_specMgr, _opts) => {
|
||||
specMgr = _specMgr;
|
||||
opts = _opts;
|
||||
})));
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -44,7 +47,7 @@ describe('Redoc components', () => {
|
|||
};
|
||||
specMgr.init();
|
||||
|
||||
component = new EndpointLink(specMgr);
|
||||
component = new EndpointLink(specMgr, opts);
|
||||
});
|
||||
|
||||
it('should replace // with appropriate protocol', () => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
import { Component, ChangeDetectionStrategy, Input, OnInit, HostListener, HostBinding} from '@angular/core';
|
||||
import { BaseComponent, SpecManager } from '../base';
|
||||
import { trigger, state, animate, transition, style } from '@angular/core';
|
||||
import { OptionsService } from '../../services/';
|
||||
|
||||
export interface ServerInfo {
|
||||
description: string;
|
||||
|
@ -12,18 +12,7 @@ export interface ServerInfo {
|
|||
selector: 'endpoint-link',
|
||||
styleUrls: ['./endpoint-link.css'],
|
||||
templateUrl: './endpoint-link.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
animations: [
|
||||
trigger('overlayExpand', [
|
||||
state('collapsed, void',
|
||||
style({ height: '0px' })),
|
||||
state('expanded',
|
||||
style({ height: '*' })),
|
||||
transition('collapsed <=> expanded', [
|
||||
animate('200ms ease')
|
||||
])
|
||||
])
|
||||
]
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class EndpointLink implements OnInit {
|
||||
@Input() path:string;
|
||||
|
@ -38,7 +27,7 @@ export class EndpointLink implements OnInit {
|
|||
this.expanded = !this.expanded;
|
||||
}
|
||||
|
||||
constructor(public specMgr:SpecManager) {
|
||||
constructor(public specMgr:SpecManager, public optionsService: OptionsService) {
|
||||
this.expanded = false;
|
||||
}
|
||||
|
||||
|
@ -60,7 +49,11 @@ export class EndpointLink implements OnInit {
|
|||
}
|
||||
|
||||
getBaseUrl():string {
|
||||
return this.specMgr.apiUrl;
|
||||
if (this.optionsService.options.hideHostname) {
|
||||
return '';
|
||||
} else {
|
||||
return this.specMgr.apiUrl;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
|
|
@ -10,7 +10,7 @@ import { Component,
|
|||
} from '@angular/core';
|
||||
|
||||
import { BaseSearchableComponent, SpecManager } from '../base';
|
||||
import { SchemaNormalizer, SchemaHelper, AppStateService } from '../../services/';
|
||||
import { SchemaNormalizer, SchemaHelper, AppStateService, OptionsService } from '../../services/';
|
||||
import { JsonPointer, DescendantInfo } from '../../utils/';
|
||||
import { Zippy } from '../../shared/components';
|
||||
import { JsonSchemaLazy } from './json-schema-lazy';
|
||||
|
@ -39,11 +39,12 @@ export class JsonSchema extends BaseSearchableComponent implements OnInit {
|
|||
descendants: DescendantInfo[];
|
||||
|
||||
constructor(
|
||||
specMgr:SpecManager,
|
||||
specMgr: SpecManager,
|
||||
app: AppStateService,
|
||||
private _renderer: Renderer,
|
||||
private cdr: ChangeDetectorRef,
|
||||
private _elementRef: ElementRef) {
|
||||
private _elementRef: ElementRef,
|
||||
private optionsService: OptionsService) {
|
||||
super(specMgr, app);
|
||||
this.normalizer = new SchemaNormalizer(specMgr);
|
||||
}
|
||||
|
@ -90,8 +91,8 @@ export class JsonSchema extends BaseSearchableComponent implements OnInit {
|
|||
}).sort((a, b) => {
|
||||
return enumOrder[a.name] > enumOrder[b.name] ? 1 : -1;
|
||||
});
|
||||
this.descendants.forEach((d, idx) => d.idx = idx);
|
||||
}
|
||||
this.descendants.forEach((d, idx) => d.idx = idx);
|
||||
this.selectDescendantByIdx(0);
|
||||
}
|
||||
|
||||
|
@ -126,7 +127,11 @@ export class JsonSchema extends BaseSearchableComponent implements OnInit {
|
|||
|
||||
this.properties = this.schema._properties || [];
|
||||
if (this.isRequestSchema) {
|
||||
this.properties = this.properties && this.properties.filter(prop => !prop.readOnly);
|
||||
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(
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Input, HostBinding, Component, OnInit, ChangeDetectionStrategy, Element
|
|||
import JsonPointer from '../../utils/JsonPointer';
|
||||
import { BaseComponent, SpecManager } from '../base';
|
||||
import { SchemaHelper } from '../../services/schema-helper.service';
|
||||
import { OptionsService } from '../../services/';
|
||||
import { OptionsService, MenuService } from '../../services/';
|
||||
|
||||
|
||||
interface MethodInfo {
|
||||
|
@ -36,7 +36,10 @@ export class Method extends BaseComponent implements OnInit {
|
|||
|
||||
method: MethodInfo;
|
||||
|
||||
constructor(specMgr:SpecManager, private optionsService: OptionsService) {
|
||||
constructor(
|
||||
specMgr:SpecManager,
|
||||
private optionsService: OptionsService,
|
||||
private menu: MenuService) {
|
||||
super(specMgr);
|
||||
}
|
||||
|
||||
|
@ -58,11 +61,9 @@ export class Method extends BaseComponent implements OnInit {
|
|||
}
|
||||
|
||||
buildAnchor() {
|
||||
if (this.operationId) {
|
||||
return 'operation/' + encodeURIComponent(this.componentSchema.operationId);
|
||||
} else {
|
||||
return this.parentTagId + encodeURIComponent(this.pointer);
|
||||
}
|
||||
this.menu.hashFor(this.pointer,
|
||||
{ type: 'method', operationId: this.operationId, pointer: this.pointer },
|
||||
this.parentTagId );
|
||||
}
|
||||
|
||||
filterMainTags(tags) {
|
||||
|
|
|
@ -15,7 +15,7 @@ import { BaseComponent } from '../base';
|
|||
import * as detectScollParent from 'scrollparent';
|
||||
|
||||
import { SpecManager } from '../../utils/spec-manager';
|
||||
import { SearchService, OptionsService, Hash, AppStateService, SchemaHelper } from '../../services/';
|
||||
import { SearchService, OptionsService, Options, Hash, AppStateService, SchemaHelper } from '../../services/';
|
||||
import { LazyTasksService } from '../../shared/components/LazyFor/lazy-for';
|
||||
|
||||
@Component({
|
||||
|
@ -29,7 +29,7 @@ export class Redoc extends BaseComponent implements OnInit {
|
|||
|
||||
error: any;
|
||||
specLoaded: boolean;
|
||||
options: any;
|
||||
options: Options;
|
||||
|
||||
loadingProgress: number;
|
||||
|
||||
|
@ -84,7 +84,8 @@ export class Redoc extends BaseComponent implements OnInit {
|
|||
}
|
||||
|
||||
load() {
|
||||
this.specMgr.load(this.options.specUrl).catch(err => {
|
||||
// bunlde spec directly if passsed or load by URL
|
||||
this.specMgr.load(this.options.spec || this.options.specUrl).catch(err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { BaseComponent, SpecManager } from '../base';
|
||||
import JsonPointer from '../../utils/JsonPointer';
|
||||
import { statusCodeType } from '../../utils/helpers';
|
||||
import { statusCodeType, getJsonLike } from '../../utils/helpers';
|
||||
|
||||
|
||||
function isNumeric(n) {
|
||||
|
@ -11,7 +11,7 @@ function isNumeric(n) {
|
|||
}
|
||||
|
||||
function hasExample(response) {
|
||||
return ((response.examples && response.examples['application/json']) ||
|
||||
return ((response.examples && getJsonLike(response.examples)) ||
|
||||
response.schema);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
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 { getJsonLike } from '../../utils/helpers';
|
||||
|
||||
@Component({
|
||||
selector: 'schema-sample',
|
||||
|
@ -42,8 +43,16 @@ export class SchemaSample extends BaseComponent implements OnInit {
|
|||
this.pointer += '/schema';
|
||||
}
|
||||
|
||||
if (base.examples && base.examples['application/json']) {
|
||||
sample = base.examples['application/json'];
|
||||
// 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;
|
||||
}
|
||||
|
||||
let jsonLikeSample = base.examples && getJsonLike(base.examples);
|
||||
if (jsonLikeSample) {
|
||||
sample = jsonLikeSample;
|
||||
} else {
|
||||
let selectedDescendant;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ 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) {
|
||||
|
@ -21,13 +22,16 @@ if (IS_PRODUCTION) {
|
|||
export const version = LIB_VERSION;
|
||||
|
||||
var moduleRef;
|
||||
export function init(specUrl:string, options:any = {}) {
|
||||
export function init(specUrlOrSpec:string|any, options:any = {}) {
|
||||
if (moduleRef) {
|
||||
destroy();
|
||||
}
|
||||
|
||||
Redoc._preOptions = options;
|
||||
options.specUrl = options.specUrl || specUrl;
|
||||
options.specUrl = options.specUrl || (isString(specUrlOrSpec) ? specUrlOrSpec : '');
|
||||
if (!isString(specUrlOrSpec)) {
|
||||
options.spec = specUrlOrSpec;
|
||||
}
|
||||
return bootstrapRedoc()
|
||||
.then(appRef => {
|
||||
moduleRef = appRef;
|
||||
|
|
|
@ -7,6 +7,7 @@ import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
|||
@Injectable()
|
||||
export class Hash {
|
||||
public value = new BehaviorSubject<string | null>(null);
|
||||
private noEmit:boolean = false;
|
||||
constructor(private location: PlatformLocation) {
|
||||
this.bind();
|
||||
}
|
||||
|
@ -21,7 +22,17 @@ export class Hash {
|
|||
|
||||
bind() {
|
||||
this.location.onHashChange(() => {
|
||||
if (this.noEmit) return;
|
||||
this.value.next(this.hash);
|
||||
});
|
||||
}
|
||||
|
||||
update(hash: string|null) {
|
||||
if (hash == undefined) return;
|
||||
this.noEmit = true;
|
||||
window.location.hash = hash;
|
||||
setTimeout(() => {
|
||||
this.noEmit = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ describe('Menu service', () => {
|
|||
beforeEach(inject([SpecManager, Hash, ScrollService, LazyTasksService],
|
||||
( _specMgr, _hash, _scroll, _tasks) => {
|
||||
hashService = _hash;
|
||||
spyOn(hashService, 'update').and.stub();
|
||||
scroll = _scroll;
|
||||
tasks = _tasks;
|
||||
specMgr = _specMgr;
|
||||
|
|
|
@ -3,6 +3,7 @@ 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';
|
||||
|
@ -140,7 +141,7 @@ export class MenuService {
|
|||
onHashChange(hash?: string) {
|
||||
if (hash == undefined) return;
|
||||
let activated = this.activateByHash(hash);
|
||||
if (!this.tasks.empty) {
|
||||
if (!this.tasks.processed) {
|
||||
this.tasks.start(this.activeIdx, this);
|
||||
this.scrollService.setStickElement(this.getCurrentEl());
|
||||
if (activated) this.scrollToActive();
|
||||
|
@ -207,7 +208,10 @@ export class MenuService {
|
|||
|
||||
this.deactivate(this.activeIdx);
|
||||
this.activeIdx = idx;
|
||||
if (idx < 0) return;
|
||||
if (idx < 0) {
|
||||
this.hash.update('');
|
||||
return;
|
||||
}
|
||||
|
||||
item.active = true;
|
||||
|
||||
|
@ -216,6 +220,7 @@ export class MenuService {
|
|||
cItem.parent.active = true;
|
||||
cItem = cItem.parent;
|
||||
}
|
||||
this.hash.update(this.hashFor(item.id, item.metadata, item.parent && item.parent.id));
|
||||
this.changedActiveItem.next(item);
|
||||
}
|
||||
|
||||
|
@ -320,6 +325,23 @@ export class MenuService {
|
|||
return res;
|
||||
}
|
||||
|
||||
hashFor(
|
||||
id: string|null, itemMeta:
|
||||
{operationId: string, type: string, pointer: string},
|
||||
parentId: string
|
||||
) {
|
||||
if (!id) return null;
|
||||
if (itemMeta && itemMeta.type === 'method') {
|
||||
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;
|
||||
|
||||
|
@ -333,7 +355,7 @@ export class MenuService {
|
|||
|
||||
tags = tags.map(k => {
|
||||
if (!this._tagsWithMethods[k]) {
|
||||
console.warn(`Non-existing tag "${k}" is specified in tag group "${tagGroup.name}"`);
|
||||
WarningsService.warn(`Non-existing tag "${k}" is added to the group "${tagGroup.name}"`);
|
||||
return null;
|
||||
}
|
||||
this._tagsWithMethods[k].used = true;
|
||||
|
@ -390,7 +412,7 @@ export class MenuService {
|
|||
checkAllTagsUsedInGroups() {
|
||||
for (let tag of Object.keys(this._tagsWithMethods)) {
|
||||
if (!this._tagsWithMethods[tag].used) {
|
||||
console.warn(`Tag "${tag}" is not added to any group`);
|
||||
WarningsService.warn(`Tag "${tag}" is not added to any group`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,18 +13,23 @@ const OPTION_NAMES = new Set([
|
|||
'disableLazySchemas',
|
||||
'specUrl',
|
||||
'suppressWarnings',
|
||||
'hideHostname',
|
||||
'lazyRendering',
|
||||
'expandResponses'
|
||||
'expandResponses',
|
||||
'requiredPropsFirst'
|
||||
]);
|
||||
|
||||
interface Options {
|
||||
export interface Options {
|
||||
scrollYOffset?: any;
|
||||
disableLazySchemas?: boolean;
|
||||
specUrl?: string;
|
||||
suppressWarnings?: boolean;
|
||||
hideHostname?: boolean;
|
||||
lazyRendering?: boolean;
|
||||
expandResponses?: Set<string> | 'all';
|
||||
$scrollParent?: HTMLElement | Window;
|
||||
requiredPropsFirst?: boolean;
|
||||
spec?: any;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
|
@ -87,7 +92,9 @@ export class OptionsService {
|
|||
|
||||
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.expandResponses)) {
|
||||
let str = this._options.expandResponses as string;
|
||||
if (str === 'all') return;
|
||||
|
|
|
@ -86,4 +86,49 @@ describe('Spec Helper', () => {
|
|||
(() => 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -341,4 +341,19 @@ export class SchemaHelper {
|
|||
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,6 +108,7 @@ class SchemaWalker {
|
|||
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);
|
||||
|
@ -124,7 +125,7 @@ export class AllOfMerger {
|
|||
defaults(into, subSchema);
|
||||
subSchema._pointer = tmpPtr;
|
||||
}
|
||||
into.discriminator = null;
|
||||
if (!hadDiscriminator) into.discriminator = null;
|
||||
into.allOf = null;
|
||||
}
|
||||
|
||||
|
@ -223,7 +224,8 @@ class SchemaDereferencer {
|
|||
// if resolved schema doesn't have title use name from ref
|
||||
resolved.title = resolved.title || JsonPointer.baseName($ref);
|
||||
|
||||
let keysCount = Object.keys(schema).length;
|
||||
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');
|
||||
|
|
|
@ -186,10 +186,13 @@ export class SearchService {
|
|||
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') {
|
||||
this.indexSchema(schema.items, title, JsonPointer.join(absolutePointer, ['items']), menuPointer);
|
||||
this.indexSchema(schema.items, title, JsonPointer.join(absolutePointer, ['items']), menuPointer, parent);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -205,20 +208,18 @@ export class SearchService {
|
|||
body += ' ' + schema.enum.join(' ');
|
||||
}
|
||||
|
||||
if (!parent) {
|
||||
// redoc doesn't display top level descriptions and titles
|
||||
this.index({
|
||||
pointer: absolutePointer,
|
||||
menuId: menuPointer,
|
||||
title,
|
||||
body
|
||||
});
|
||||
}
|
||||
this.index({
|
||||
pointer: absolutePointer,
|
||||
menuId: menuPointer,
|
||||
title,
|
||||
body
|
||||
});
|
||||
|
||||
if (schema.properties) {
|
||||
Object.keys(schema.properties).forEach(propName => {
|
||||
let propPtr = JsonPointer.join(absolutePointer, ['properties', propName]);
|
||||
this.indexSchema(schema.properties[propName], propName, propPtr, menuPointer);
|
||||
let prop:SwaggerSchema = schema.properties[propName];
|
||||
this.indexSchema(prop, propName, propPtr, menuPointer, parent);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
'use strict';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Subject } from 'rxjs';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
|
||||
@Injectable()
|
||||
export class WarningsService {
|
||||
private static _warnings: Array<string> = [];
|
||||
private static _warningsObs = new Subject<Array<string>>();
|
||||
private static _warningsObs = new BehaviorSubject<Array<string>>([]);
|
||||
|
||||
static get warnings() {
|
||||
return WarningsService._warningsObs;
|
||||
|
|
|
@ -27,7 +27,7 @@ export class CopyButton implements OnInit {
|
|||
onClick() {
|
||||
let copied;
|
||||
if (this.copyText) {
|
||||
copied = Clipboard.copyCustom(JSON.stringify(this.copyText));
|
||||
copied = Clipboard.copyCustom(JSON.stringify(this.copyText, null, 2));
|
||||
} else {
|
||||
copied = Clipboard.copyElement(this.copyElement);
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ export class LazyTasksService {
|
|||
private _tasks = [];
|
||||
private _current: number = 0;
|
||||
private _syncCount: number = 0;
|
||||
private _emptyProcessed = false;
|
||||
private menuService;
|
||||
|
||||
public loadProgress = new BehaviorSubject<number>(0);
|
||||
|
@ -38,8 +39,10 @@ export class LazyTasksService {
|
|||
constructor(public optionsService: OptionsService) {
|
||||
}
|
||||
|
||||
get empty() {
|
||||
return this._current === this._tasks.length;
|
||||
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) {
|
||||
|
@ -97,10 +100,17 @@ export class LazyTasksService {
|
|||
} 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<ul>
|
||||
<li *ngFor="let tab of tabs" [ngClass]="{active: tab.active}" (click)="selectTab(tab)"
|
||||
class="tab-{{tab.tabStatus}}" [innerHTML]="tab.tabTitle"></li>
|
||||
class="tab-{{tab.tabStatus}}" [innerHtml]="tab.tabTitle | safe"></li>
|
||||
</ul>
|
||||
<ng-content></ng-content>
|
||||
|
|
|
@ -8,7 +8,7 @@ export function stringify(obj:any) {
|
|||
return JSON.stringify(obj);
|
||||
}
|
||||
|
||||
export function isString(str:any) {
|
||||
export function isString(str:any):str is String {
|
||||
return typeof str === 'string';
|
||||
}
|
||||
|
||||
|
@ -114,3 +114,17 @@ export function snapshot(obj) {
|
|||
|
||||
return temp;
|
||||
}
|
||||
|
||||
export function isJsonLike(contentType: string): boolean {
|
||||
return contentType.search(/json/i) !== -1;
|
||||
}
|
||||
|
||||
export function getJsonLike(object: object) {
|
||||
const jsonLikeKeys = Object.keys(object).filter(isJsonLike);
|
||||
|
||||
if (!jsonLikeKeys.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return object[jsonLikeKeys.shift()];
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||
import { isString, stringify, isBlank } from './helpers';
|
||||
import JsonPointer from './JsonPointer';
|
||||
import { MdRenderer } from './';
|
||||
|
@ -66,13 +66,13 @@ export class MarkedPipe implements PipeTransform {
|
|||
@Pipe({ name: 'safe' })
|
||||
export class SafePipe implements PipeTransform {
|
||||
constructor(private sanitizer: DomSanitizer) {}
|
||||
transform(value:string) {
|
||||
transform(value:string|SafeHtml):SafeHtml {
|
||||
if (isBlank(value)) return value;
|
||||
if (!isString(value)) {
|
||||
throw new InvalidPipeArgumentException(JsonPointerEscapePipe, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
return this.sanitizer.bypassSecurityTrustHtml(value);
|
||||
return this.sanitizer.bypassSecurityTrustHtml(value as string);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import { MdRenderer } from './md-renderer';
|
|||
|
||||
import { SwaggerOperation, SwaggerParameter } from './swagger-typings';
|
||||
import { snapshot } from './helpers';
|
||||
import { WarningsService } from '../services/warnings.service';
|
||||
|
||||
function getDiscriminator(obj) {
|
||||
return obj.discriminator || obj['x-extendedDiscriminator'];
|
||||
|
@ -181,7 +182,37 @@ export class SpecManager {
|
|||
|
||||
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.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 &&
|
||||
|
@ -189,12 +220,6 @@ export class SpecManager {
|
|||
let subTypes = def['x-derived-from'] ||
|
||||
def.allOf.map(subType => subType._pointer || subType.$ref);
|
||||
|
||||
let pointers;
|
||||
if (definition['x-derived-from']) {
|
||||
pointers = [defPointer, ...definition['x-derived-from']];
|
||||
} else {
|
||||
pointers = [defPointer];
|
||||
}
|
||||
let idx = -1;
|
||||
|
||||
for (let ptr of pointers) {
|
||||
|
@ -204,12 +229,23 @@ export class SpecManager {
|
|||
|
||||
if (idx < 0) continue;
|
||||
|
||||
let derivedName = defName;
|
||||
let derivedName;
|
||||
if (extendedDiscriminatorProp) {
|
||||
let prop = def.properties && def.properties[extendedDiscriminatorProp];
|
||||
if (prop && prop.enum && prop.enum.length === 1) {
|
||||
derivedName = prop.enum[0];
|
||||
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}`});
|
||||
|
|
5
manual-types/index.d.ts
vendored
5
manual-types/index.d.ts
vendored
|
@ -13,6 +13,11 @@ declare module "*.css" {
|
|||
export default content;
|
||||
}
|
||||
|
||||
declare module "*.json" {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare var LIB_VERSION: any;
|
||||
declare var IS_PRODUCTION: any;
|
||||
declare var AOT: any;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "redoc",
|
||||
"description": "Swagger-generated API Reference Documentation",
|
||||
"version": "1.9.0",
|
||||
"version": "1.11.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/Rebilly/ReDoc"
|
||||
|
|
|
@ -81,7 +81,9 @@ describe('Language tabs sync', () => {
|
|||
fixFFTest(done);
|
||||
});
|
||||
|
||||
it('should sync language tabs', () => {
|
||||
// skip as it fails for no reason on IE on sauce-labs
|
||||
// TODO: fixme
|
||||
xit('should sync language tabs', () => {
|
||||
var $item = $$('[operation-id="addPet"] tabs > ul > li').last();
|
||||
// check if correct item
|
||||
expect($item.getText()).toContain('PHP');
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
import { SpecManager } from '../../lib/utils/spec-manager';
|
||||
import * as xExtendedDefs from './x-extended-defs.json';
|
||||
|
||||
describe('Utils', () => {
|
||||
describe('Schema manager', () => {
|
||||
let specMgr: SpecManager;
|
||||
|
@ -175,6 +177,23 @@ describe('Utils', () => {
|
|||
deriveDefs.should.be.instanceof(Array);
|
||||
deriveDefs.should.be.empty();
|
||||
});
|
||||
|
||||
it('should correctly work with x-extendedDiscriminator', () => {
|
||||
specMgr._schema = {
|
||||
definitions: xExtendedDefs
|
||||
};
|
||||
|
||||
let deriveDefs = specMgr.findDerivedDefinitions('#/definitions/Payment');
|
||||
deriveDefs.should.be.instanceof(Array);
|
||||
deriveDefs.should.be.deepEqual([
|
||||
{
|
||||
name: 'cash',
|
||||
$ref: '#/definitions/CashPayment'
|
||||
}, {
|
||||
name: 'paypal',
|
||||
$ref: '#/definitions/PayPalPayment'
|
||||
}])
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
import {statusCodeType} from '../../lib/utils/helpers';
|
||||
import {statusCodeType, isJsonLike, getJsonLike } from '../../lib/utils/helpers';
|
||||
|
||||
describe('Utils', () => {
|
||||
describe('statusCodeType', () => {
|
||||
it('Should return info for status codes within 100 and 200', ()=> {
|
||||
|
@ -30,4 +31,34 @@ describe('Utils', () => {
|
|||
(() => statusCodeType(600)).should.throw('invalid HTTP code');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isJsonLike', () => {
|
||||
it('Should return true for a string that contains `json`', () => {
|
||||
isJsonLike('application/json').should.be.equal(true);
|
||||
});
|
||||
it('Should return false for a string that does not contain `json`', () => {
|
||||
isJsonLike('application/xml').should.be.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getJsonLike', () => {
|
||||
it('Should return a value when a JSON-like key exists', () => {
|
||||
const examples = {
|
||||
"application/vnd.api+json": {
|
||||
"message": "Hello World"
|
||||
},
|
||||
"application/xml": "<message>Hello World</message>"
|
||||
};
|
||||
|
||||
(getJsonLike(examples).message).should.be.equal("Hello World");
|
||||
});
|
||||
|
||||
it('Should return undefined when no JSON-like key exists', () => {
|
||||
const examples = {
|
||||
"application/xml": "<message>Hello World</message>"
|
||||
};
|
||||
|
||||
getJsonLike(examples).should.be.equal(false);
|
||||
});
|
||||
})
|
||||
});
|
||||
|
|
57
tests/unit/x-extended-defs.json
Normal file
57
tests/unit/x-extended-defs.json
Normal file
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"Payment": {
|
||||
"x-extendedDiscriminator": "type",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"CashPayment": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Payment"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"cash"
|
||||
]
|
||||
},
|
||||
"currency": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"PayPalPayment": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Payment"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"paypal"
|
||||
]
|
||||
},
|
||||
"userEmail": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user