Merge branch 'master' into feature/remove-express-dep

This commit is contained in:
Raymond Feng 2015-08-06 16:36:01 -07:00
commit 04e058922b
17 changed files with 758 additions and 1750 deletions

3
.gitignore vendored
View File

@ -10,9 +10,12 @@ lib-cov
*.iml
*.tgz
.idea
pids
logs
results
npm-debug.log
node_modules
LoopBackExplorer.iml

View File

@ -1,3 +1,23 @@
2015-06-25, Version 1.8.0
=========================
* Add opts.omitProtocolInBaseUrl (Miroslav Bajtoš)
* Fix tests broken by fa3035c (#96) (Miroslav Bajtoš)
* Fix model description getting lost (bkniffler)
2015-03-30, Version 1.7.2
=========================
* Allow submitting token input with empty value to remove token. (Samuel Reed)
* Fix duplicate stylesheet issue (Pradnya Baviskar)
* Fix explorer tests for different line endings on Windows (Pradnya Baviskar)
2015-02-23, Version 1.7.1
=========================

View File

@ -7,7 +7,7 @@ var path = require('path');
var urlJoin = require('./lib/url-join');
var _defaults = require('lodash').defaults;
var swagger = require('./lib/swagger');
var SWAGGER_UI_ROOT = require('swagger-ui').dist;
var SWAGGER_UI_ROOT = require('strong-swagger-ui').dist;
var STATIC_ROOT = path.join(__dirname, 'public');
module.exports = explorer;

View File

@ -29,7 +29,7 @@ var classHelper = module.exports = {
}
return {
apiVersion: opts.version,
apiVersion: opts.version || '1',
swaggerVersion: opts.swaggerVersion,
basePath: opts.basePath,
resourcePath: urlJoin('/', resourcePath),

View File

@ -6,6 +6,7 @@
var _cloneDeep = require('lodash').cloneDeep;
var _pick = require('lodash').pick;
var translateDataTypeKeys = require('./translate-data-type-keys');
var TYPES_PRIMITIVE = ['array', 'boolean', 'integer', 'number', 'null', 'object', 'string', 'any'];
var typeConverter = require('./type-converter');
/**
@ -20,6 +21,25 @@ var modelHelper = module.exports = {
* @return {Object} Associated model definition.
*/
generateModelDefinition: function generateModelDefinition(modelClass, definitions) {
var processType = function(app, modelName, referencedModels) {
if (app && modelName) {
if (modelName.indexOf('[') == 0) {
modelName = modelName.replace(/[\[\]]/g, '');
}
var model = app.models[modelName];
if (model && referencedModels.indexOf(model) === -1) {
referencedModels.push(model);
}
}
};
var convertTypeTo$Ref = function convertTypeTo$Ref(prop){
if (prop.type && TYPES_PRIMITIVE.indexOf(prop.type) === -1 ){
prop.$ref = prop.type;
delete prop.type;
}
};
var def = modelClass.definition;
var out = definitions || {};
@ -36,7 +56,7 @@ var modelHelper = module.exports = {
}
var required = [];
// Don't modify original properties.
var properties = _cloneDeep(def.properties);
var properties = _cloneDeep(def.rawProperties || def.properties);
var referencedModels = [];
// Add models from settings
@ -44,7 +64,7 @@ var modelHelper = module.exports = {
for (var m in def.settings.models) {
var model = modelClass[m];
if (typeof model === 'function' && model.modelName) {
if (referencedModels.indexOf(model) === -1) {
if (model && referencedModels.indexOf(model) === -1) {
referencedModels.push(model);
}
}
@ -67,6 +87,12 @@ var modelHelper = module.exports = {
// Eke a type out of the constructors we were passed.
var swaggerType = modelHelper.LDLPropToSwaggerDataType(prop);
processType(modelClass.app, swaggerType.type, referencedModels);
convertTypeTo$Ref(swaggerType);
if (swaggerType.items) {
processType(modelClass.app, swaggerType.items.type, referencedModels);
convertTypeTo$Ref(swaggerType.items);
}
var desc = typeConverter.convertText(prop.description || prop.doc);
if (desc) swaggerType.description = desc;
@ -76,6 +102,16 @@ var modelHelper = module.exports = {
required.push(key);
}
// Change mismatched keys.
prop = translateDataTypeKeys(prop);
delete prop.required;
delete prop.id;
if (prop.description){
prop.description = typeConverter.convertText(prop.description);
}
// Assign this back to the properties object.
properties[key] = swaggerType;
@ -95,25 +131,69 @@ var modelHelper = module.exports = {
}
});
var additionalProperties = undefined;
if (def.settings){
var strict = def.settings.strict;
additionalProperties = def.settings.additionalProperties;
var notAllowAdditionalProperties = strict || (additionalProperties !== true);
if (notAllowAdditionalProperties){
additionalProperties = false;
}
}
out[name] = {
id: name,
description: typeConverter.convertText(def.description),
additionalProperties: additionalProperties,
description: typeConverter.convertText(
def.description || (def.settings && def.settings.description)),
properties: properties,
required: required
};
if (def.description){
out[name].description = typeConverter.convertText(def.description);
}
// Generate model definitions for related models
for (var r in modelClass.relations) {
var rel = modelClass.relations[r];
if (rel.modelTo){
generateModelDefinition(rel.modelTo, out);
if (rel.modelTo && referencedModels.indexOf(rel.modelTo) === -1) {
referencedModels.push(rel.modelTo);
}
if (rel.modelThrough) {
generateModelDefinition(rel.modelThrough, out);
if (rel.modelThrough && referencedModels.indexOf(rel.modelThrough) === -1) {
referencedModels.push(rel.modelThrough);
}
}
if (modelClass.sharedClass) {
var remotes = modelClass.sharedClass.methods();
for (var remoteIdx in remotes) {
var remote = remotes[remoteIdx];
var accepts = remote.accepts;
if (accepts) {
for (var acceptIdx in accepts) {
processType(modelClass.app, accepts[acceptIdx].type, referencedModels);
}
}
var returns = remote.returns;
if (returns) {
for (var returnIdx in returns) {
processType(modelClass.app, returns[returnIdx].type, referencedModels);
}
}
var errors = remote.errors;
if (errors) {
for (var errorIdx in errors) {
processType(modelClass.app, errors[errorIdx].responseModel, referencedModels);
}
}
}
}
for (var i = 0, n = referencedModels.length; i < n; i++) {
generateModelDefinition(referencedModels[i], out);
if (referencedModels[i].definition) {
generateModelDefinition(referencedModels[i], out);
}
}
return out;
},
@ -152,8 +232,13 @@ var modelHelper = module.exports = {
'format',
'defaultValue',
'enum',
'items',
'minimum',
'minItems',
'minLength',
'maximum',
'maxItems',
'maxLength',
'uniqueItems',
// loopback-explorer extensions
'length',
@ -167,21 +252,28 @@ var modelHelper = module.exports = {
// Pick only keys supported by Swagger
var swaggerType = _pick(ldlType, SWAGGER_DATA_TYPE_FIELDS);
swaggerType.type = modelHelper.getPropType(ldlType.type);
swaggerType.type = modelHelper.getPropType(ldlType.type || ldlType);
if (swaggerType.type === 'array') {
var hasItemType = Array.isArray(ldlType.type) && ldlType.type.length;
var arrayItem = hasItemType && ldlType.type[0];
var newItems = null;
if (arrayItem) {
if(typeof arrayItem === 'object') {
swaggerType.items = modelHelper.LDLPropToSwaggerDataType(arrayItem);
newItems = modelHelper.LDLPropToSwaggerDataType(arrayItem);
} else {
swaggerType.items = { type: modelHelper.getPropType(arrayItem) };
newItems = { type: modelHelper.getPropType(arrayItem) };
}
} else {
// NOTE: `any` is not a supported type in swagger 1.2
swaggerType.items = { type: 'any' };
newItems = { type: 'any' };
}
if (typeof swaggerType.items !== 'object') {
swaggerType.items = {};
}
for (var key in newItems) {
swaggerType.items[key] = newItems[key];
}
} else if (swaggerType.type === 'date') {
swaggerType.type = 'string';
@ -195,6 +287,3 @@ var modelHelper = module.exports = {
return swaggerType;
}
};

View File

@ -116,24 +116,76 @@ var routeHelper = module.exports = {
* See https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#523-operation-object
*/
routeToAPIDoc: function routeToAPIDoc(route, classDef) {
/**
* Converts from an sl-remoting data type to a Swagger dataType.
*/
function prepareDataType(type) {
if (!type) {
return 'void';
}
if(Array.isArray(type)) {
if (type.length > 0) {
if (typeof type[0] === 'string') {
return '[' + type[0] + ']';
} else if (typeof type[0] === 'function') {
return '[' + type[0].name + ']';
} else if (typeof type[0] === 'object') {
if (typeof type[0].type === 'function') {
return '[' + type[0].type.name + ']';
} else {
return '[' + type[0].type + ']';
}
} else {
return '[' + type + ']';
}
}
return 'array';
}
// TODO(schoon) - Add support for complex dataTypes, "models", etc.
switch (type) {
case 'Array':
return 'array';
case 'Boolean':
return 'boolean';
case 'buffer':
return 'string';
case 'Date':
return 'date';
case 'number':
case 'Number':
return 'double';
case 'Object':
return 'object';
case 'String':
return 'string';
}
return type;
}
var returnDesc;
// Some parameters need to be altered; eventually most of this should
// be removed.
var accepts = routeHelper.convertAcceptsToSwagger(route, classDef);
var returns = routeHelper.convertReturnsToSwagger(route, classDef);
var responseMessages = [
{
code: route.returns && route.returns.length ? 200 : 204,
message: 'Request was successful',
responseModel: returns.model || prepareDataType(returns.type) || 'void'
}
];
if (route.errors) {
responseMessages.push.apply(responseMessages, route.errors);
}
debug('route %j', route);
var responseDoc = modelHelper.LDLPropToSwaggerDataType(returns);
var responseMessages = [{
code: route.returns && route.returns.length ? 200 : 204,
message: 'Request was successful'
}];
if (route.errors) {
responseMessages.push.apply(responseMessages, route.errors);
}
var apiDoc = {
path: routeHelper.convertPathFragments(route.path),
// Create the operation doc.
@ -146,11 +198,14 @@ var routeHelper = module.exports = {
// path as class name so it remains unique between models.
// route.method is always #{className}.#{methodName}
nickname: route.method.replace(/.*?\./, ''),
deprecated: route.deprecated,
consumes: ['application/json', 'application/xml', 'text/xml'],
produces: ['application/json', 'application/javascript', 'application/xml', 'text/javascript', 'text/xml'],
parameters: accepts,
responseMessages: responseMessages,
type: returns.model || returns.type || 'void',
summary: typeConverter.convertText(route.description),
notes: typeConverter.convertText(route.notes),
deprecated: route.deprecated
notes: typeConverter.convertText(route.notes)
}, returns)]
};
@ -204,11 +259,21 @@ var routeHelper = module.exports = {
}
var out = {
paramType: paramType || type,
name: name,
description: typeConverter.convertText(accepts.description),
required: !!accepts.required,
allowMultiple: false
paramType: paramType || type,
type: accepts.type,
$ref: accepts.model,
items: accepts.items,
uniqueItems: accepts.uniqueItems,
format: accepts.format,
pattern: accepts.pattern,
defaultValue: accepts.defaultValue,
enum: accepts.enum,
minimum: accepts.minimum,
maximum: accepts.maximum,
allowMultiple: accepts.allowMultiple,
description: typeConverter.convertText(accepts.description)
};
out = routeHelper.extendWithType(out, accepts);
@ -238,6 +303,25 @@ var routeHelper = module.exports = {
// The `typeDesc` may have additional attributes, such as
// `format` for non-primitive types.
Object.keys(typeDesc).forEach(function(key){
obj[key] = typeDesc[key];
});
//Ensure brief properties are first
if (typeof obj === 'object') {
var keysToSink = ['authorizations', 'consumes', 'notes', 'produces',
'parameters', 'responseMessages', 'summary'];
var outKeys = Object.keys(obj);
for (var outKeyIdx in outKeys) {
var outKey = outKeys[outKeyIdx];
if (keysToSink.indexOf(outKey) != -1) {
var outValue = obj[outKey];
delete obj[outKey];
obj[outKey] = outValue;
}
}
}
_assign(obj, typeDesc);
return obj;

View File

@ -12,8 +12,10 @@ var urlJoin = require('./url-join');
var _defaults = require('lodash').defaults;
var classHelper = require('./class-helper');
var routeHelper = require('./route-helper');
var _cloneDeep = require('lodash').cloneDeep;
var modelHelper = require('./model-helper');
var cors = require('cors');
var typeConverter = require('./type-converter');
/**
* Create a remotable Swagger module for plugging into `RemoteObjects`.
@ -61,7 +63,17 @@ function Swagger(loopbackApplication, swaggerApp, opts) {
// A class is an endpoint root; e.g. /users, /products, and so on.
classes.forEach(function (aClass) {
var doc = apiDocs[aClass.name] = classHelper.generateAPIDoc(aClass, opts);
resourceDoc.apis.push(classHelper.generateResourceDocAPIEntry(aClass));
var hasDocumented = false;
var methods = aClass.methods()
for (var methodKey in methods) {
hasDocumented = methods[methodKey].documented;
if (hasDocumented) {
break;
}
}
if (hasDocumented) {
resourceDoc.apis.push(classHelper.generateResourceDocAPIEntry(aClass));
}
// Add the getter for this doc.
var docPath = urlJoin(opts.resourcePath, aClass.http.path);
@ -82,7 +94,9 @@ function Swagger(loopbackApplication, swaggerApp, opts) {
return item.name === className;
})[0];
routeHelper.addRouteToAPIDeclaration(route, classDef, doc);
if (route.documented) {
routeHelper.addRouteToAPIDeclaration(route, classDef, doc);
}
});
// Add models referenced from routes (e.g. accepts/returns)
@ -136,6 +150,8 @@ function Swagger(loopbackApplication, swaggerApp, opts) {
* information about them.
*/
addRoute(swaggerApp, opts.resourcePath, resourceDoc, opts);
loopbackApplication.emit('swaggerResources', resourceDoc);
}
function setupCors(swaggerApp, remotes) {
@ -176,8 +192,9 @@ function addRoute(app, uri, doc, opts) {
var headers = req.headers;
// NOTE header names (keys) are always all-lowercase
var proto = headers['x-forwarded-proto'] || opts.protocol || req.protocol;
var host = headers['x-forwarded-host'] || headers.host;
doc.basePath = proto + '://' + host + initialPath;
var prefix = opts.omitProtocolInBaseUrl ? '//' : proto + '://';
var host = headers['x-forwarded-host'] || opts.host || headers.host;
doc.basePath = prefix + host + initialPath;
}
res.status(200).send(doc);
});
@ -190,16 +207,23 @@ function addRoute(app, uri, doc, opts) {
* @return {Object} Resource doc.
*/
function generateResourceDoc(opts) {
var apiInfo = _cloneDeep(opts.apiInfo);
for (var propertyName in apiInfo) {
var property = apiInfo[propertyName];
apiInfo[propertyName] = typeConverter.convertText(property);
}
return {
swaggerVersion: opts.swaggerVersion,
apiVersion: opts.version,
apis: [],
// See https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#513-info-object
info: opts.apiInfo
info: apiInfo,
// TODO Authorizations
// https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#514-authorizations-object
// TODO Produces/Consumes
// https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#52-api-declaration
consumes: ['application/json', 'application/xml', 'text/xml'],
produces: ['application/json', 'application/javascript', 'application/xml', 'text/javascript', 'text/xml'],
apis: [],
models: opts.models
};
}

View File

@ -1,6 +1,6 @@
{
"name": "loopback-explorer",
"version": "1.7.1",
"version": "1.8.2",
"description": "Browse and test your LoopBack app's APIs",
"main": "index.js",
"scripts": {
@ -21,19 +21,19 @@
"url": "https://github.com/strongloop/loopback-explorer/issues"
},
"devDependencies": {
"loopback": "^2.14.0",
"mocha": "^2.1.0",
"supertest": "^0.15.0",
"chai": "^2.1.1"
"loopback": "^2.20.0",
"mocha": "^2.2.5",
"supertest": "^1.0.1",
"chai": "^3.2.0"
},
"license": {
"name": "Dual MIT/StrongLoop",
"url": "https://github.com/strongloop/loopback-explorer/blob/master/LICENSE"
},
"dependencies": {
"cors": "^2.5.3",
"debug": "~2.1.2",
"lodash": "^3.0.0",
"swagger-ui": "~2.0.24"
"cors": "^2.7.1",
"debug": "^2.2.0",
"lodash": "^3.10.0",
"strong-swagger-ui": "^20.0.2"
}
}

View File

@ -1,4 +1,320 @@
/* Styles used for loopback explorer customizations */
/* Custom font */
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 300;
src: local('Ubuntu Light'),
local('Ubuntu-Light'),
url(../fonts/_aijTyevf54tkVDLy-dlnLO3LdcAZYWl9Si6vvxL-qU.woff)
format('woff');
}
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 500;
src: local('Ubuntu Medium'),
local('Ubuntu-Medium'),
url(../fonts/OsJ2DjdpjqFRVUSto6IffLO3LdcAZYWl9Si6vvxL-qU.woff)
format('woff');
}
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 700;
src: local('Ubuntu Bold'),
local('Ubuntu-Bold'),
url(../fonts/0ihfXUL2emPh0ROJezvraLO3LdcAZYWl9Si6vvxL-qU.woff)
format('woff');
}
.swagger-section .swagger-ui-wrap,
.swagger-section .swagger-ui-wrap b,
.swagger-section .swagger-ui-wrap strong,
.swagger-section .swagger-ui-wrap .model-signature
.swagger-section .swagger-ui-wrap h1,
.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.text textarea,
.swagger-section .swagger-ui-wrap ul#resources,
.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2,
.swagger-section .swagger-ui-wrap p#colophon,
.swagger-section .swagger-ui-wrap .markdown ul
.model-signature {
font-family: "Ubuntu", sans-serif !important;
}
/* layout spacing and global colors */
body {
padding-top: 60px;
font-family: "Ubuntu", sans-serif;
}
body #header {
background-color: #08592b !important;
position: fixed;
width: 100%;
top: 0;
}
body #header a#logo {
padding: 20px 0 20px 60px !important;
}
body #header form#api_selector .input a#explore {
background-color: #7dbd33 !important;
}
body #header form#api_selector .input a#explore:hover {
background-color: #808080 !important;
}
/* HTTP GET */
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading {
background-color: #e6f3f6 !important;
border: 1px solid #bfe1e8 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading h3 span.http_method a {
background-color: #0085a1 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li {
color: #0085a1 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content {
background-color: #e9f5f7 !important;
border: 1px solid #bfe1e8 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content h4 {
color: #0085a1 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content form input[type='text'].error {
outline: 2px solid #cc0000 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content div.sandbox_header a {
color: #66b6c7 !important;
}
li.operation.get .content > .content-type > div > label {
color: #0085a1 !important;
}
/* HTTP POST */
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading {
background-color: #f4effa !important;
border: 1px solid #e3d8f3 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading h3 span.http_method a {
background-color: #9063cd !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li {
border-right-color: #e3d8f3 !important;
color: #9063cd !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li a {
color: #9063cd !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content {
background-color: #f6f2fb !important;
border: 1px solid #e3d8f3 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content h4 {
color: #9063cd !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content form input[type='text'].error {
outline: 2px solid #cc0000 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content div.sandbox_header a {
color: #bca1e1 !important;
}
li.operation.post .content > .content-type > div > label {
color: #9063cd !important;
}
/* HTTP PUT */
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading {
background-color: #ede7ee !important;
border: 1px solid #d1c2d6 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading h3 span.http_method a {
background-color: #470a59;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li {
border-right-color: #d1c2d6 !important;
color: #470a59 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li a {
color: #470a59 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content {
background-color: #efeaf1 !important;
border: 1px solid #d1c2d6 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content h4 {
color: #470a59 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content form input[type='text'].error {
outline: 2px solid #cc0000 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content div.sandbox_header a {
color: #916c9b !important;
}
li.operation.put .content > .content-type > div > label {
color: #470a59 !important;
}
/* HTTP PATCH */
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading {
background-color: #e6f9fb !important;
border: 1px solid #bff0f5 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading h3 span.http_method a {
background-color: #00c1d5 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li {
border-right-color: #bff0f5 !important;
color: #00c1d5 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li a {
color: #00c1d5 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content {
background-color: #e9fafb !important;
border: 1px solid #bff0f5 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content h4 {
color: #00c1d5 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content form input[type='text'].error {
outline: 2px solid #cc0000 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content div.sandbox_header a {
color: #66dae6 !important;
}
li.operation.patch .content > .content-type > div > label {
color: #00c1d5 !important;
}
/* HTTP HEAD */
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading {
background-color: #fff2ec !important;
border: 1px solid #ffdfd0 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading h3 span.http_method a {
background-color: #ff7f41 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li {
border-right-color: #ffdfd0 !important;
color: #ff7f41 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li a {
color: #ff7f41 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content {
background-color: #fff4ef !important;
border: 1px solid #ffdfd0 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content h4 {
color: #ff7f41 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content form input[type='text'].error {
outline: 2px solid #cc0000 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content div.sandbox_header a {
color: #ffb28d !important;
}
li.operation.head .content > .content-type > div > label {
color: #ff7f41 !important;
}
/* HTTP DELETE */
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading {
background-color: #fbede7 !important
border: 1px solid #f4d1c3 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading h3 span.http_method a {
background-color: #d4470f !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li {
border-right-color: #f4d1c3 !important;
color: #d4470f !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li a {
color: #d4470f !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content {
background-color: #fbefeb !important;
border: 1px solid #f4d1c3 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content h4 {
color: #d4470f !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content form input[type='text'].error {
outline: 2px solid #cc0000 !important;
}
body ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content div.sandbox_header a {
color: #e5916f !important;
}
li.operation.delete .content > .content-type > div > label {
color: #d4470f !important;
}
/* Access Token widgets */
.accessTokenDisplay {
color: white;
margin-right: 10px;
@ -34,6 +350,14 @@
color: #080;
}
.contentWell {
padding-left: 30px;
padding-right: 30px;
}
/*
FIXME: Separate the overrides from the rest of the styles, rather than override screen.css entirely.
*/
/* Improve spacing when the browser window is small */
#message-bar, #swagger-ui-container {
padding-left: 30px;

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -25,21 +25,23 @@
<script src='lib/loadSwaggerUI.js' type="text/javascript"></script>
</head>
<body class="swagger-section">
<body class="swagger-section standalone">
<div id='header'>
<div class="swagger-ui-wrap">
<a id="logo">StrongLoop API Explorer</a>
<form id='api_selector'>
<div class='input'>
<span class='accessTokenDisplay'>Token Not Set</span>
<input placeholder="accessToken" id="input_accessToken" name="accessToken" type="text"/>
</div>
<div class='input'><a id="explore" type="submit">Set Access Token</a></div>
</form>
</div>
<div class="swagger-ui-wrap">
<a id="logo">StrongLoop API Explorer</a>
<form id='api_selector'>
<div class='input'>
<span class='accessTokenDisplay'>Token Not Set</span>
<input placeholder="accessToken" id="input_accessToken" name="accessToken" type="text"/>
</div>
<div class='input'><a id="explore" type="submit">Set Access Token</a></div>
</form>
</div>
</div>
<div id="message-bar" class="swagger-ui-wrap">&nbsp;</div>
<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
<div class="contentWell">
<div id="message-bar" class="swagger-ui-wrap">&nbsp;</div>
<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
</div>
</body>
</html>

View File

@ -64,6 +64,17 @@ $(function() {
window.localStorage.setItem(lsKey, key);
}
}
// If submitted with an empty token, remove the current token. Can be
// useful to intentionally remove authorization.
else {
log('removed accessToken.');
$('.accessTokenDisplay').text('Token Not Set.').removeClass('set');
$('.accessTokenDisplay').removeAttr('data-tooltip');
window.authorizations.remove('key');
if (window.localStorage) {
window.localStorage.removeItem(lsKey);
}
}
}
function onInputChange(e) {

View File

@ -24,6 +24,45 @@ describe('class-helper', function() {
expect(doc.resourcePath).to.equal('/test');
});
describe('#generateResourceDocAPIEntry', function() {
describe('when ctor.settings.description is an array of string', function() {
it('should return description as a string', function() {
var aClass = {
ctor: {
settings: {
description: ['1','2','3']
}
},
http:{
path: 'path'
}
};
var result = classHelper.generateResourceDocAPIEntry(aClass);
expect(result.description).to.eql("1\n2\n3");
});
});
describe('when ctor.sharedCtor.description is an array of string', function() {
it('should return description as a string', function() {
var aClass = {
ctor: {
settings: {},
sharedCtor: {
description: ['1','2','3']
}
},
http:{
path: 'path'
}
};
var result = classHelper.generateResourceDocAPIEntry(aClass);
expect(result.description).to.eql("1\n2\n3");
});
});
});
});
// Easy wrapper around createRoute

View File

@ -112,14 +112,14 @@ describe('model-helper', function() {
expect(prop).to.eql({ type: 'array', items: { type: 'any' } });
});
it('converts Model type', function() {
it('converts Model type to $ref', function() {
var Address = loopback.createModel('Address', {street: String});
var def = buildSwaggerModels({
str: String,
address: Address
});
var prop = def.properties.address;
expect(prop).to.eql({ type: 'Address' });
expect(prop).to.eql({ $ref: 'Address' });
});
});
@ -160,7 +160,7 @@ describe('model-helper', function() {
var Model1 = loopback.createModel('Model1', {
str: String, // 'string'
address: Model2
});
}, { models: { Model2: Model2 } });
var defs = modelHelper.generateModelDefinition(Model1, {});
expect(defs).has.property('Model1');
expect(defs).has.property('Model2');
@ -169,7 +169,7 @@ describe('model-helper', function() {
it('should include used models', function() {
var Model4 = loopback.createModel('Model4', {street: String});
var Model3 = loopback.createModel('Model3', {
str: String, // 'string'
str: String // 'string'
}, {models: {model4: 'Model4'}});
var defs = modelHelper.generateModelDefinition(Model3, {});
expect(defs).has.property('Model3');
@ -181,7 +181,7 @@ describe('model-helper', function() {
var Model5 = loopback.createModel('Model5', {
str: String, // 'string'
addresses: [Model6]
});
}, { models: { Model6: Model6 } });
var defs = modelHelper.generateModelDefinition(Model5, {});
expect(defs).has.property('Model5');
expect(defs).has.property('Model6');
@ -227,6 +227,36 @@ describe('model-helper', function() {
});
});
describe('#generateModelDefinition', function() {
it('should convert top level array description to string', function () {
var model = {};
model.definition = {
name: 'test',
description: ['1', '2', '3'],
properties: {}
};
var models = {};
modelHelper.generateModelDefinition(model, models);
expect(models.test.description).to.equal("1\n2\n3");
});
it('should convert property level array description to string', function () {
var model = {};
model.definition = {
name: 'test',
properties: {
prop1: {
type: 'string',
description: ['1', '2', '3']
}
}
};
var models = {};
modelHelper.generateModelDefinition(model, models);
expect(models.test.properties.prop1.description).to.equal("1\n2\n3");
});
});
describe('getPropType', function() {
it('converts anonymous object types', function() {
var type = modelHelper.getPropType({ name: 'string', value: 'string' });

View File

@ -14,7 +14,7 @@ describe('route-helper', function() {
]
});
expect(doc.operations[0].type).to.equal('object');
expect(getResponseType(doc.operations[0])).to.equal(undefined);
expect(getResponseType(doc.operations[0])).to.equal('object');
});
it('converts path params when they exist in the route name', function() {
@ -61,7 +61,7 @@ describe('route-helper', function() {
]
});
var opDoc = doc.operations[0];
expect(getResponseType(opDoc)).to.equal(undefined);
expect(getResponseType(opDoc)).to.eql('[customType]');
// NOTE(bajtos) this would be the case if there was a single response type
expect(opDoc.type).to.equal('array');
@ -86,6 +86,36 @@ describe('route-helper', function() {
expect(doc.operations[0].notes).to.equal('some notes');
});
describe('#acceptToParameter', function(){
it('should return function that converts accepts.description from array of string to string', function(){
var f = routeHelper.acceptToParameter({verb: 'get', path: 'path'});
var result = f({description: ['1','2','3']});
expect(result.description).to.eql("1\n2\n3");
});
});
describe('#routeToAPIDoc', function() {
it('should convert route.description from array of string to string', function () {
var result = routeHelper.routeToAPIDoc({
method: 'someMethod',
verb: 'get',
path: 'path',
description: ['1', '2', '3']
});
expect(result.operations[0].summary).to.eql("1\n2\n3");
});
it('should convert route.notes from array of string to string', function () {
var result = routeHelper.routeToAPIDoc({
method: 'someMethod',
verb: 'get',
path: 'path',
notes: ['1', '2', '3']
});
expect(result.operations[0].notes).to.eql("1\n2\n3");
});
});
it('includes `deprecated` metadata', function() {
var doc = createAPIDoc({
deprecated: 'true'
@ -163,7 +193,8 @@ describe('route-helper', function() {
expect(doc.operations[0].responseMessages).to.eql([
{
code: 200,
message: 'Request was successful'
message: 'Request was successful',
responseModel: 'object'
}
]);
});
@ -176,7 +207,8 @@ describe('route-helper', function() {
expect(doc.operations[0].responseMessages).to.eql([
{
code: 204,
message: 'Request was successful'
message: 'Request was successful',
responseModel: 'void'
}
]);
});

View File

@ -104,6 +104,31 @@ describe('swagger definition', function() {
done();
});
});
it('supports options.omitProtocolInBaseUrl', function(done) {
var app = givenAppWithSwagger({ omitProtocolInBaseUrl: true });
var getReq = getAPIDeclaration(app, 'products');
getReq.end(function(err, res) {
if (err) return done(err);
var basePath = res.body.basePath;
expect(basePath).to.match(/^\/\//);
var parsed = url.parse(res.body.basePath);
expect(parsed.protocol).to.equal(null);
done();
});
});
it('supports opts.header', function(done) {
var app = givenAppWithSwagger({ host: 'example.com:8080' });
getAPIDeclaration(app, 'products')
.end(function(err, res) {
if (err) return done(err);
var baseUrl = url.parse(res.body.basePath);
expect(baseUrl.host).to.equal('example.com:8080');
done();
});
});
});
describe('Model definition attributes', function() {