Skip to content

Commit fd49082

Browse files
committed
feat: Add support for x-servers
1 parent dcb97a5 commit fd49082

File tree

13 files changed

+294
-79
lines changed

13 files changed

+294
-79
lines changed

demo/swagger.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ securityDefinitions:
7979
type: apiKey
8080
name: api_key
8181
in: header
82+
x-servers:
83+
- url: //petstore.swagger.io/v2
84+
description: Default server
85+
- url: //petstore.swagger.io/sandbox
86+
description: Sandbox server
8287
paths:
8388
/pet:
8489
post:
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<div class="method-endpoint" (click)="handleClick()">
2+
<h5 class="http-verb" [ngClass]="verb">{{verb}}</h5>
3+
<span><!--
4+
--><span class="method-api-url-path">{{path}}</span><!--
5+
--></span>
6+
<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">
7+
<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 "/>
8+
</svg>
9+
</div>
10+
<div class="servers-overlay" [@overlayExpand]="expanded ? 'expanded' : 'collapsed'">
11+
<div *ngFor="let server of servers" class="server-item">
12+
<div class="description" [innerHtml]="server.description | marked"></div>
13+
<div select-on-click class="url">
14+
<span class="method-api-url"> {{server.url}}</span>{{path}}
15+
</div>
16+
</div>
17+
</div>
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
@import '../../shared/styles/variables';
2+
3+
:host {
4+
display: block;
5+
position: relative;
6+
cursor: pointer;
7+
}
8+
9+
.method-endpoint {
10+
padding: 10px 20px;
11+
border-radius: $border-radius*2;
12+
background-color: darken($black, 2%);
13+
display: block;
14+
font-weight: $light;
15+
white-space: nowrap;
16+
overflow-x: auto;
17+
border: 1px solid transparent;
18+
}
19+
20+
.method-endpoint > .method-params-subheader {
21+
padding-top: 1px;
22+
padding-bottom: 0;
23+
margin: 0;
24+
font-size: 12/14em;
25+
color: $black;
26+
vertical-align: middle;
27+
display: inline-block;
28+
border-radius: $border-radius;
29+
}
30+
31+
.method-api-url {
32+
color: rgba($black, .8);
33+
&-path {
34+
font-family: $headers-font, $headers-font-family;
35+
position: relative;
36+
top: 1px;
37+
color: #ffffff;
38+
margin-left: 10px;
39+
}
40+
}
41+
42+
.http-verb {
43+
color: $black;
44+
background: #ffffff;
45+
padding: 3px 10px;
46+
text-transform: uppercase;
47+
display: inline-block;
48+
margin: 0;
49+
}
50+
51+
.servers-overlay {
52+
position: absolute;
53+
width: 100%;
54+
z-index: 100;
55+
background: $side-bar-bg-color;
56+
color: $black;
57+
box-sizing: border-box;
58+
box-shadow: 4px 4px 6px rgba(0, 0, 0, 0.33);
59+
overflow: hidden;
60+
border-bottom-left-radius: $border-radius*2;
61+
border-bottom-right-radius: $border-radius*2;
62+
}
63+
64+
.server-item {
65+
padding: 10px;
66+
//margin-bottom: 10px;
67+
68+
& > .url {
69+
padding: 5px;
70+
border: 1px solid $border-color;
71+
background: $background-color;
72+
word-break: break-all;
73+
}
74+
75+
&:last-child {
76+
margin-bottom: 0;
77+
}
78+
}
79+
80+
.expand-icon {
81+
height: 20px;
82+
width: 20px;
83+
display: inline-block;
84+
float: right;
85+
margin-top: 2px;
86+
background: darken($black, 2%);
87+
transform: rotateZ(0);
88+
transition: all 0.2s ease;
89+
}
90+
91+
:host.expanded {
92+
> .method-endpoint {
93+
border-color: $side-bar-bg-color;
94+
border-bottom-left-radius: 0;
95+
border-bottom-right-radius: 0;
96+
}
97+
98+
.expand-icon {
99+
transform: rotateZ(180deg);
100+
}
101+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
'use strict';
2+
3+
import { Component } from '@angular/core';
4+
import {
5+
inject,
6+
async,
7+
TestBed
8+
} from '@angular/core/testing';
9+
10+
import { getChildDebugElement } from '../../../tests/helpers';
11+
12+
import { EndpointLink } from './endpoint-link';
13+
import { SpecManager } from '../../utils/spec-manager';
14+
15+
describe('Redoc components', () => {
16+
beforeEach(() => {
17+
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
18+
});
19+
describe('EndpointLink Component', () => {
20+
let builder;
21+
let component: EndpointLink;
22+
let specMgr: SpecManager;
23+
24+
beforeEach(async(inject([SpecManager], (_specMgr) => {
25+
specMgr = _specMgr;
26+
})));
27+
28+
beforeEach(() => {
29+
specMgr.apiUrl = 'http://test.com/v1';
30+
specMgr._schema = {
31+
info: {},
32+
host: 'petstore.swagger.io',
33+
baseName: '/v2',
34+
schemes: ['https', 'http'],
35+
'x-servers': [
36+
{
37+
url: '//test.com/v2'
38+
},
39+
{
40+
url: 'ws://test.com/v3',
41+
description: 'test'
42+
}
43+
]
44+
};
45+
specMgr.init();
46+
47+
component = new EndpointLink(specMgr);
48+
});
49+
50+
it('should replace // with appropriate protocol', () => {
51+
component.ngOnInit();
52+
component.servers[0].url.should.be.equal('https://test.com/v2');
53+
});
54+
55+
56+
it('should preserve other protocols', () => {
57+
component.ngOnInit();
58+
component.servers[1].url.should.be.equal('ws://test.com/v3');
59+
});
60+
61+
it('should fallback to host + basePath + schemas if no x-servers', () => {
62+
specMgr._schema['x-servers'] = null;
63+
specMgr.init();
64+
component.ngOnInit();
65+
component.servers.should.be.lengthOf(1);
66+
component.servers[0].url.should.be.equal('https://petstore.swagger.io');
67+
});
68+
});
69+
});
70+
71+
72+
/** Test component that contains a Method. */
73+
@Component({
74+
selector: 'test-app',
75+
template:
76+
`<method pointer='#/paths/~1user~1{username}/put'></method>`
77+
})
78+
class TestAppComponent {
79+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
'use strict';
2+
import { Component, ChangeDetectionStrategy, Input, OnInit, HostListener, HostBinding} from '@angular/core';
3+
import { BaseComponent, SpecManager } from '../base';
4+
import { trigger, state, animate, transition, style } from '@angular/core';
5+
6+
export interface ServerInfo {
7+
description: string;
8+
url: string;
9+
}
10+
11+
@Component({
12+
selector: 'endpoint-link',
13+
styleUrls: ['./endpoint-link.css'],
14+
templateUrl: './endpoint-link.html',
15+
changeDetection: ChangeDetectionStrategy.OnPush,
16+
animations: [
17+
trigger('overlayExpand', [
18+
state('collapsed, void',
19+
style({ height: '0px' })),
20+
state('expanded',
21+
style({ height: '*' })),
22+
transition('collapsed <=> expanded', [
23+
animate('200ms ease')
24+
])
25+
])
26+
]
27+
})
28+
export class EndpointLink implements OnInit {
29+
@Input() path:string;
30+
@Input() verb:string;
31+
32+
apiUrl: string;
33+
servers: ServerInfo[];
34+
@HostBinding('class.expanded') expanded: boolean = false;
35+
36+
// @HostListener('click')
37+
handleClick() {
38+
this.expanded = !this.expanded;
39+
}
40+
41+
constructor(public specMgr:SpecManager) {
42+
this.expanded = false;
43+
}
44+
45+
init() {
46+
let servers:ServerInfo[] = this.specMgr.schema['x-servers'];
47+
if (servers) {
48+
this.servers = servers.map(({url, description}) => ({
49+
description,
50+
url: url.startsWith('//') ? `${this.specMgr.apiProtocol}:${url}` : url
51+
}));
52+
} else {
53+
this.servers = [
54+
{
55+
description: 'Server URL',
56+
url: this.getBaseUrl()
57+
}
58+
];
59+
}
60+
}
61+
62+
getBaseUrl():string {
63+
return this.specMgr.apiUrl;
64+
}
65+
66+
ngOnInit() {
67+
this.init();
68+
}
69+
}

lib/components/Method/method.html

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,7 @@ <h2 class="method-header sharable-header">
1616
<div class="method-samples">
1717
<h4 class="method-params-subheader">Definition</h4>
1818

19-
<div class="method-endpoint">
20-
<h5 class="http-method" [ngClass]="method.httpMethod">{{method.httpMethod}}</h5>
21-
<span select-on-click><!--
22-
--><span class="method-api-url">{{method.apiUrl}}</span><span class="method-api-url-path">{{method.path}}</span><!--
23-
--></span>
24-
</div>
19+
<endpoint-link [verb]="method.verb" [path]="method.path"> </endpoint-link>
2520

2621
<div>
2722
<request-samples [pointer]="pointer" [schemaPointer]="method.bodyParam?._pointer">

lib/components/Method/method.scss

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -18,45 +18,6 @@
1818
margin-bottom: calc(1em - 6px);
1919
}
2020

21-
.method-endpoint {
22-
//margin: 0 0 2px 0;
23-
padding: 10px 20px;
24-
border-radius: $border-radius*2;
25-
background-color: darken($black, 2%);
26-
display: block;
27-
font-weight: $light;
28-
white-space: nowrap;
29-
overflow-x: auto;
30-
}
31-
32-
.method-endpoint > .method-params-subheader {
33-
padding-top: 1px;
34-
padding-bottom: 0;
35-
margin: 0;
36-
font-size: 12/14em;
37-
color: $black;
38-
vertical-align: middle;
39-
display: inline-block;
40-
border-radius: $border-radius;
41-
}
42-
43-
.method-api-url {
44-
color: rgba(#ffffff, .6);
45-
margin-left: 10px;
46-
margin-top: 2px;
47-
position: relative;
48-
top: 1px;
49-
font-family: $headers-font, $headers-font-family;
50-
font-size: 0.929em;
51-
52-
&-path {
53-
font-family: $headers-font, $headers-font-family;
54-
position: relative;
55-
top: 1px;
56-
color: #ffffff;
57-
}
58-
}
59-
6021
.method-tags {
6122
margin-top: 20px;
6223

@@ -121,15 +82,6 @@
12182
margin: 0;
12283
}
12384

124-
.http-method {
125-
color: $black;
126-
background: #ffffff;
127-
padding: 3px 10px;
128-
text-transform: uppercase;
129-
display: inline-block;
130-
margin: 0;
131-
}
132-
13385
[select-on-click] {
13486
cursor: pointer;
13587
}

lib/components/Method/method.spec.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ describe('Redoc components', () => {
1919
});
2020
describe('Method Component', () => {
2121
let builder;
22-
let component;
22+
let component: Method;
2323
let specMgr;
2424

2525
beforeEach(async(inject([SpecManager, LazyTasksService], (_specMgr, lazyTasks) => {
@@ -43,8 +43,7 @@ describe('Redoc components', () => {
4343
});
4444

4545
it('should init basic component data', () => {
46-
component.method.apiUrl.should.be.equal('http://petstore.swagger.io/v2');
47-
component.method.httpMethod.should.be.equal('put');
46+
component.method.verb.should.be.equal('put');
4847
component.method.path.should.be.equal('/user/{username}');
4948
});
5049

0 commit comments

Comments
 (0)