Add support of x-permissions vendor extension

This commit is contained in:
Vincent Bailleau 2017-09-27 10:48:52 +02:00
parent 278e5d7917
commit f212145d83
13 changed files with 267 additions and 11 deletions

View File

@ -157,6 +157,51 @@ description: Pagination description (can use markdown syntax)
x-traitTag: true
```
### Permission Object vendor extensions
#### x-permissions
| Field Name | Type | Description |
| :------------- | :------: | :---------- |
| x-permissions | Object | Define the several permissions available for the API and associated to the operations |
###### Usage in ReDoc
`x-permissions` are rendered bellow the API description of ReDoc
###### x-permissions example (main declaration)
yaml
```yaml
swagger: '2.0'
info:
...
tags: [...]
x-permissions:
admin:
title: Pet store admin
description: 'Pet store Admin have special privileges on PetStore API allowing them to manage their store and users from their company '
user:
title: Pet Store user
description: 'Pet store user'
```
##### Usage in Redoc operations
`x-permissions` are rendered above the Operation description of ReDoc
###### x-permissions example (operation declaration)
yaml
```yaml
/user/logout:
get:
tags:
- "user"
x-permissions:
- name: "admin"
- name: "user"
```
### Operation Object vendor extensions
Extends OpenAPI [Operation Object](http://swagger.io/specification/#operationObject)
#### x-code-samples

View File

@ -8,6 +8,10 @@
<div class="operation-tags" *ngIf="operation.info.tags.length">
<a *ngFor="let tag of operation.info.tags" attr.href="#tag/{{tag}}"> {{tag}} </a>
</div>
<div class="operation-permissions" *ngIf="operation.info.permissions.length">
<span><em>Permissions:</em><a *ngFor="let permission of operation.info.permissions" attr.href="#section/Permissions/{{permission.name}}"> {{permission.title}} </a>
</span>
</div>
<p *ngIf="operation.info.description" class="operation-description"
[innerHtml]="operation.info.description | marked">
</p>

View File

@ -53,6 +53,30 @@
}
}
.operation-permissions {
margin-top: 20px;
font-size: 14px;
padding: 0.2em 0;
margin: 3em 0 1.1em;
color: #253137;
font-weight: normal;
a {
font-size: 12px;
font-family: Courier, monospace;
background-color: rgba(38, 50, 56, 0.02);
border: 1px solid rgba(38, 50, 56, 0.1);
margin: 2px 3px;
padding: 0px 5px;
border-radius: 2px;
color: #263238;
display: inline-block;
min-width: 20px;
text-align: center;
}
}
.operation-content, .operation-samples {
display: block;
box-sizing: border-box;

View File

@ -10,8 +10,8 @@ import {
import { getChildDebugElement } from '../../../tests/helpers';
import { Operation } from './operation';
import { SpecManager } from '../../utils/spec-manager';;
import { LazyTasksService } from '../../shared/components/LazyFor/lazy-for';;
import { SpecManager } from '../../utils/spec-manager';
import { LazyTasksService } from '../../shared/components/LazyFor/lazy-for';
describe('Redoc components', () => {
beforeEach(() => {

View File

@ -12,6 +12,7 @@ export interface OperationInfo {
path: string;
info: {
tags: string[];
permissions: string[];
description: string;
};
bodyParam: any;
@ -20,7 +21,7 @@ export interface OperationInfo {
externalDocs?: {
url: string;
description?: string;
}
};
}
@Component({
@ -56,7 +57,8 @@ export class Operation extends BaseComponent implements OnInit {
path: JsonPointer.baseName(this.pointer, 2),
info: {
description: this.componentSchema.description,
tags: this.filterMainTags(this.componentSchema.tags)
tags: this.filterMainTags(this.componentSchema.tags),
permissions: this.reduceMainPermissions(this.componentSchema['x-permissions'])
},
bodyParam: this.findBodyParam(),
summary: SchemaHelper.operationSummary(this.componentSchema),
@ -77,6 +79,17 @@ export class Operation extends BaseComponent implements OnInit {
return tags.filter(tag => tagsMap[tag] && tagsMap[tag]['x-traitTag']);
}
reduceMainPermissions(permissions) {
var permissionsMap = this.specMgr.getPermissionsMap();
if (!permissions) return [];
return permissions.reduce(( filtered, permission) => {
if( permissionsMap[permission.name] ) {
filtered.push(permissionsMap[permission.name]);
}
return filtered;
}, []);
}
findBodyParam():SwaggerBodyParameter {
let params = this.specMgr.getOperationParams(this.pointer);
let bodyParam = params.find(param => param.in === 'body');

View File

@ -0,0 +1,5 @@
<div class="x-permissions" *ngFor="let def of defs">
<h2 class="sharable-header" attr.section="section/Permissions/{{def.name}}">
<a class="share-link" href="#section/Permissions/{{def.name}}"></a>{{def.title}}</h2>
<div [innerHTML]="def.details.description | marked"></div>
</div>

View File

@ -0,0 +1,36 @@
@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;
}

View File

@ -0,0 +1,48 @@
'use strict';
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { SpecManager, BaseComponent } from '../base';
import { ComponentParser } from '../../services/component-parser.service';
@Component({
selector: 'x-permissions',
styleUrls: ['./x-permissions.css'],
templateUrl: './x-permissions.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class XPermissions extends BaseComponent implements OnInit {
info: any = {};
specUrl: String;
defs: any[];
static insertTagIntoDescription(md:string) {
if (ComponentParser.contains(md, 'x-permissions')) return md;
if (/^#\s?Permissions\s*$/mi.test(md)) return md;
return md + '\n\n# Permissions \n' + ComponentParser.build('x-permissions');
}
constructor(specMgr:SpecManager) {
super(specMgr);
}
init() {
this.componentSchema = this.componentSchema['x-permissions'];
this.defs = Object.keys(this.componentSchema).map(name => {
let details = this.componentSchema[name];
let title = details.title ? details.title : name;
details.description = details.description ? details.description : 'none';
return {
name,
title,
details
};
});
}
ngOnInit() {
this.preinit();
}
}

View File

@ -14,6 +14,7 @@ import { OperationsList } from './OperationsList/operations-list';
import { Operation } from './Operation/operation';
import { Warnings } from './Warnings/warnings';
import { SecurityDefinitions } from './SecurityDefinitions/security-definitions';
import { XPermissions } from './Permissions/x-permissions';
import { LoadingBar } from './LoadingBar/loading-bar';
import { RedocSearch } from './Search/redoc-search';
import { ExternalDocs } from './ExternalDocs/external-docs';
@ -24,9 +25,9 @@ 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
XPermissions, 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 };
XPermissions, LoadingBar, SideMenuItems, ExternalDocs, EndpointLink };

View File

@ -5,10 +5,10 @@ import { CommonModule } from '@angular/common';
import { SpecManager } from './utils/spec-manager';
import { Redoc, SecurityDefinitions, Operation, REDOC_DIRECTIVES } from './components/index';
import { Redoc, SecurityDefinitions, XPermissions, 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 { CustomErrorHandler } from './utils/';
import { LazyTasksService } from './shared/components/LazyFor/lazy-for';
import {
@ -31,7 +31,7 @@ import {
imports: [ CommonModule ],
declarations: [ REDOC_DIRECTIVES, REDOC_COMMON_DIRECTIVES, REDOC_PIPES ],
bootstrap: [ Redoc ],
entryComponents: [ SecurityDefinitions, DynamicNg2Wrapper, Operation ],
entryComponents: [ SecurityDefinitions, XPermissions, DynamicNg2Wrapper, Operation ],
providers: [
ScrollService,
Hash,
@ -42,7 +42,7 @@ import {
ContentProjector,
{ provide: APP_ID, useValue: 'redoc' },
{ provide: ErrorHandler, useClass: CustomErrorHandler },
{ provide: COMPONENT_PARSER_ALLOWED, useValue: { 'security-definitions': SecurityDefinitions} }
{ provide: COMPONENT_PARSER_ALLOWED, useValue: { 'security-definitions': SecurityDefinitions, 'x-permissions': XPermissions} },
],
exports: [Redoc, REDOC_DIRECTIVES, REDOC_COMMON_DIRECTIVES, REDOC_PIPES]
})

View File

@ -101,6 +101,13 @@ export class SpecManager {
require('../components/SecurityDefinitions/security-definitions').SecurityDefinitions;
mdRender.addPreprocessor(SecurityDefinitions.insertTagIntoDescription);
}
if( this._schema['x-permissions']) {
let permissions =
require('../components/Permissions/x-permissions').XPermissions;
mdRender.addPreprocessor(permissions.insertTagIntoDescription);
}
this._schema.info['x-redoc-html-description'] = mdRender.renderMd(this._schema.info.description);
this._schema.info['x-redoc-markdown-headers'] = mdRender.headings;
}
@ -185,6 +192,23 @@ export class SpecManager {
return tagsMap;
}
getPermissionsMap() {
let permissions = this._schema['x-permissions'] || [];
var permissionsMap = {};
for (let key in permissions) {
if( !permissions.hasOwnProperty(key) ) {
continue;
}
permissionsMap[key] = {
name: key,
description: permissions[key].description,
title: permissions[key].title ? permissions[key].title : key
};
}
return permissionsMap;
}
findDerivedDefinitions(defPointer: string, schema?: any): DescendantInfo[] {
let definition = schema || this.byPointer(defPointer);
if (!definition) throw new Error(`Can't load schema at ${defPointer}`);

View File

@ -77,6 +77,8 @@
post:
tags:
- "pet"
x-permissions:
- name: "admin"
summary: "Add a new pet to the store"
description: ""
operationId: "addPet"
@ -138,6 +140,8 @@
put:
tags:
- "pet"
x-permissions:
- name: "admin"
summary: "Update an existing pet"
description: ""
operationId: "updatePet"
@ -173,6 +177,9 @@
- "pet"
- "Pagination"
- "JSONP"
x-permissions:
- name: "admin"
- name: "user"
summary: "Finds Pets by status"
description: "Multiple status values can be provided with comma seperated strings"
operationId: "findPetsByStatus"
@ -214,6 +221,9 @@
- "pet"
- "Pagination"
- "JSONP"
x-permissions:
- name: "admin"
- name: "user"
summary: "Finds Pets by tags"
description: "Muliple tags can be provided with comma seperated strings. Use tag1, tag2, tag3 for testing."
operationId: "findPetsByTags"
@ -249,6 +259,9 @@
tags:
- "pet"
- "JSONP"
x-permissions:
- name: "admin"
- name: "user"
summary: "Find pet by ID"
description: "Returns a single pet"
operationId: "getPetById"
@ -278,6 +291,8 @@
post:
tags:
- "pet"
x-permissions:
- name: "admin"
summary: "Updates a pet in the store with form data"
description: ""
operationId: "updatePetWithForm"
@ -317,6 +332,8 @@
delete:
tags:
- "pet"
x-permissions:
- name: "admin"
summary: "Deletes a pet"
description: ""
operationId: "deletePet"
@ -348,6 +365,8 @@
post:
tags:
- "pet"
x-permissions:
- name: "admin"
summary: "uploads an image"
description: ""
operationId: "uploadFile"
@ -390,6 +409,8 @@
tags:
- "store"
- "JSONP"
x-permissions:
- name: "admin"
summary: "Returns pet inventories by status"
description: "Returns a map of status codes to quantities"
operationId: "getInventory"
@ -411,6 +432,8 @@
post:
tags:
- "store"
x-permissions:
- name: "admin"
summary: "Place an order for a pet"
description: ""
operationId: "placeOrder"
@ -437,6 +460,8 @@
tags:
- "store"
- "JSONP"
x-permissions:
- name: "admin"
summary: "Find purchase order by ID"
description: "For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions"
operationId: "getOrderById"
@ -465,6 +490,8 @@
delete:
tags:
- "store"
x-permissions:
- name: "admin"
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"
operationId: "deleteOrder"
@ -488,6 +515,8 @@
post:
tags:
- "user"
x-permissions:
- name: "admin"
summary: "Create user"
description: "This can only be done by the logged in user."
operationId: "createUser"
@ -509,6 +538,8 @@
post:
tags:
- "user"
x-permissions:
- name: "admin"
summary: "Creates list of users with given input array"
description: ""
operationId: "createUsersWithArrayInput"
@ -532,6 +563,8 @@
post:
tags:
- "user"
x-permissions:
- name: "admin"
summary: "Creates list of users with given input array"
description: ""
operationId: "createUsersWithListInput"
@ -555,6 +588,9 @@
get:
tags:
- "user"
x-permissions:
- name: "admin"
- name: "user"
summary: "Logs user into the system"
description: ""
operationId: "loginUser"
@ -594,6 +630,9 @@
get:
tags:
- "user"
x-permissions:
- name: "admin"
- name: "user"
summary: "Logs out current logged in user session"
description: ""
operationId: "logoutUser"
@ -609,6 +648,9 @@
tags:
- "user"
- "JSONP"
x-permissions:
- name: "admin"
- name: "user"
summary: "Get user by user name"
description: ""
operationId: "getUserByName"
@ -634,6 +676,9 @@
put:
tags:
- "user"
x-permissions:
- name: "admin"
- name: "user"
summary: "Updated user"
description: "This can only be done by the logged in user."
operationId: "updateUser"
@ -662,6 +707,8 @@
delete:
tags:
- "user"
x-permissions:
- name: "admin"
summary: "Delete user"
description: "This can only be done by the logged in user."
operationId: "deleteUser"
@ -853,3 +900,11 @@
externalDocs:
description: "Find out more about Swagger"
url: "http://swagger.io"
x-permissions:
admin:
title: Pet store admin
description: 'Pet store Admin have special privileges on PetStore API allowing them to manage their store and users from their company '
user:
title: Pet Store user
description: 'Pet store user'

View File

@ -52,7 +52,8 @@ beforeEach(function() {
services.SearchService,
{ provide: sharedComponents.LazyTasksService, useClass: sharedComponents.LazyTasksServiceSync },
//{ provide: ErrorHandler, useClass: forwardRef(function() {return services.CustomErrorHandler}) },
{ provide: services.COMPONENT_PARSER_ALLOWED, useValue: { 'security-definitions': components.SecurityDefinitions }}
{ provide: services.COMPONENT_PARSER_ALLOWED, useValue: { 'security-definitions': components.SecurityDefinitions }},
{ provide: services.COMPONENT_PARSER_ALLOWED, useValue: { 'x-permissions': components.XPermissions }}
],
declarations: [REDOC_PIPES, REDOC_DIRECTIVES, REDOC_COMMON_DIRECTIVES]
});