redoc/src/services/models/Operation.ts

172 lines
4.6 KiB
TypeScript
Raw Normal View History

2018-01-22 21:30:53 +03:00
import { action, observable } from 'mobx';
2017-10-12 00:01:37 +03:00
import { join as joinPaths } from 'path';
import { parse as urlParse } from 'url';
import { IMenuItem } from '../MenuStore';
import { GroupModel } from './Group.model';
2018-01-22 21:30:53 +03:00
import { SecurityRequirementModel } from './SecurityRequirement';
2017-10-12 00:01:37 +03:00
import { OpenAPIExternalDocumentation, OpenAPIServer } from '../../types';
import {
getOperationSummary,
isAbsolutePath,
JsonPointer,
mergeParams,
sortByRequired,
2018-03-05 18:58:19 +03:00
stripTrailingSlash,
} from '../../utils';
2018-01-22 21:30:53 +03:00
import { ContentItemModel, ExtendedOpenAPIOperation } from '../MenuBuilder';
import { OpenAPIParser } from '../OpenAPIParser';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
2017-10-12 00:01:37 +03:00
import { FieldModel } from './Field';
import { RequestBodyModel } from './RequestBody';
2018-01-22 21:30:53 +03:00
import { ResponseModel } from './Response';
2017-10-12 00:01:37 +03:00
import { CodeSample } from './types';
/**
* 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;
2018-01-22 21:30:53 +03:00
items: ContentItemModel[] = [];
2017-10-12 00:01:37 +03:00
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[];
security: SecurityRequirementModel[];
2017-10-12 00:01:37 +03:00
codeSamples: CodeSample[];
2017-11-21 14:00:33 +03:00
constructor(
parser: OpenAPIParser,
operationSpec: ExtendedOpenAPIOperation,
parent: GroupModel | undefined,
options: RedocNormalizedOptions,
) {
this.id =
operationSpec.operationId !== undefined
? 'operation/' + operationSpec.operationId
: this.parent !== undefined
? this.parent.id + operationSpec._$ref
: operationSpec._$ref;
2017-10-12 00:01:37 +03:00
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 =
2017-11-21 14:24:41 +03:00
operationSpec.requestBody && new RequestBodyModel(parser, operationSpec.requestBody, options);
2017-10-12 00:01:37 +03:00
this.codeSamples = operationSpec['x-code-samples'] || [];
this.path = JsonPointer.baseName(this._$ref, 2);
this.parameters = mergeParams(
parser,
operationSpec.pathParameters,
operationSpec.parameters,
).map(paramOrRef => new FieldModel(parser, paramOrRef, this._$ref, options));
2017-10-12 00:01:37 +03:00
if (options.requiredPropsFirst) {
sortByRequired(this.parameters);
}
let hasSuccessResponses = false;
2017-10-12 00:01:37 +03:00
this.responses = Object.keys(operationSpec.responses || [])
.filter(code => {
2018-01-22 21:30:53 +03:00
if (parseInt(code, 10) >= 100 && parseInt(code, 10) <= 399) {
hasSuccessResponses = true;
}
return isNumeric(code) || code === 'default';
}) // filter out other props (e.g. x-props)
2017-11-21 14:00:33 +03:00
.map(code => {
return new ResponseModel(
parser,
code,
hasSuccessResponses,
operationSpec.responses[code],
options,
);
});
2017-10-12 00:01:37 +03:00
this.servers = normalizeServers(
parser.specUrl,
operationSpec.servers || parser.spec.servers || [],
2017-10-12 00:01:37 +03:00
);
this.security = (operationSpec.security || parser.spec.security || []).map(
security => new SecurityRequirementModel(security, parser),
);
2017-10-12 00:01:37 +03:00
}
/**
* 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;
}
}
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
2017-10-12 00:01:37 +03:00
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 || '',
};
});
}