UNPKG

openapi-gui

Version:

GUI / visual editor for creating and editing OpenApi / Swagger definitions

346 lines (330 loc) 10.5 kB
function clone(obj) { try { return JSON.parse(JSON.stringify(obj)); } catch (ex) { console.log(ex.message); console.log(typeof obj); return obj; } } function getParameterByName(name, url) { if (!url) url = window.location.href; name = name.replace(/[\[\]]/g, "\\$&"); var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), results = regex.exec(url); if (!results) return null; if (!results[2]) return ''; return decodeURIComponent(results[2].replace(/\+/g, " ")); } function recurse(obj,path,cache,callback) { if (typeof obj == 'object') { callback(obj,path); for (var p in obj){ if (cache.indexOf(obj[p])<0) { //cache.push(obj[p]); recurse(obj[p],path+p+'/',cache,callback); //cache.pop(); } } } } function deref(obj,defs) { var result = clone(obj); var changes = 1; while (changes>0) { changes = 0; var cache = []; recurse(result,'#/',cache,function(o,path){ cache.push(o); if ((typeof o == 'object') && (o["$ref"])) { var ptr = o["$ref"]; //console.log(' '+ptr+' @ '+path); var target = (ptr.indexOf('#/components/') === 0) ? defs : result; try { var def = new JSONPointer(ptr.substr(1)).get(target); changes++; // rewrite local $refs recurse(def,'#/',cache,function(o,dpath){ if (o["$ref"]) { var newPtr = o["$ref"]; if ((ptr+'/').indexOf(newPtr+'/')>=0) { var fixPtr = (newPtr+'/').replace(ptr+'/',path); fixPtr = fixPtr.substr(0,fixPtr.length-1); o["$ref"] = fixPtr; } } }); for (var p in def) { o[p] = def[p]; } delete o["$ref"]; } catch (ex) { console.log(ex.message); console.log('Could not find $ref '+o["$ref"]); } } }); } return result; } function preProcessDefinition(openapi) { if (!openapi) openapi = {}; for (var t in openapi.tags) { var tag = openapi.tags[t]; if (!tag.externalDocs) tag.externalDocs = {}; } if (!openapi.info) { openapi.info = {version:"1.0.0",title:"Untitled"}; } if (!openapi.info.contact) { openapi.info.contact = {}; } if (!openapi.info.license) { openapi.info.license = {}; } if (!openapi.externalDocs) { openapi.externalDocs = {}; } if (!openapi.security) openapi.security = []; if (!openapi.servers) openapi.servers = []; if (!openapi.paths) { openapi.paths = {}; } if (!openapi.components) { openapi.components = {}; } if (!openapi.components.links) { openapi.components.links = {}; } if (!openapi.components.callbacks) { openapi.components.callbacks = {}; } if (!openapi.components.schemas) { openapi.components.schemas = {}; } for (var p in openapi.paths) { var path = openapi.paths[p]; for (var o in path) { if ('get.post.put.patch.delete.options.head.trace'.indexOf(o)>=0) { var op = path[o]; if (!op.tags) op.tags = []; if (!op.parameters) op.parameters = []; if (!op.externalDocs) op.externalDocs = {}; if (path.parameters && path.parameters.length > 0) { for (var pp in path.parameters) { var shared = path.parameters[pp]; var seen = false; for (var cp in op.parameters) { var child = op.parameters[cp]; if (child && child.name == shared.name && child.in == shared.in) { seen = true; break; } } if (!seen) { op.parameters.push(shared); // TODO resolve whether we should clone it? } } } } } delete path.parameters; // other non-HTTP verb properties are excluded from the nav menu } return openapi; } function postProcessPathItem(pi) { for (var o in pi) { var op = pi[o]; if (op.externalDocs && !op.externalDocs.url) { Vue.delete(op, 'externalDocs'); } if (op.tags) { if (op.tags.length === 0) { Vue.delete(op, 'tags'); } else { Vue.set(op, 'tags', op.tags.filter(onlyUnique)); } } if (op.callbacks) { for (var c in op.callbacks) { var callback = op.callbacks[c]; for (var e in callback) { var exp = callback[e]; postProcessPathItem(exp); } } } } return pi; } function postProcessDefinition(openapi) { var def = clone(openapi); for (var p in def.paths) { postProcessPathItem(def.paths[p]); } for (var t in def.tags) { var tag = def.tags[t]; if (tag.externalDocs && !tag.externalDocs.url) { Vue.delete(tag, 'externalDocs'); } } if (def.externalDocs && !def.externalDocs.url) { Vue.delete(def, 'externalDocs'); } if (def.info && def.info.license && !def.info.license.name) { Vue.delete(def.info, 'license'); } return def; } function convertOpenApi2(schema,callback) { var convertUrl; if (window.intelligentBackend) convertUrl = '/api/v1/convert'; else convertUrl = 'https://mermade.org.uk/openapi-converter/api/v1/convert'; var data = new FormData(); data.append('source',JSON.stringify(schema)); $.ajax({ url:convertUrl, type:"POST", contentType: false, processData: false, data:data, dataType:"json", success: function(schema) { callback(schema); } }); } function onlyUnique(value, index, self) { return self.indexOf(value) === index; } var openapi; if (window.localStorage) { var o = window.localStorage.getItem('openapi3'); if (o) { try { openapi = JSON.parse(o); } catch (ex) {} } } if (typeof openapi === 'undefined') { openapi = clone(petstore); } openapi = preProcessDefinition(openapi); var importschema = {}; importschema.text = JSON.stringify(openapi, null, 2); // or we could wrap jsoneditor in a Vue.js component? var schemaEditorSave = function() {}; var schemaEditorClose = function() {}; function app_main() { Vue.use(Buefy, { defaultIconPack: 'fa' }); Vue.component(Buefy.default.Input.name, Buefy.default.Input); Vue.component(Buefy.default.Autocomplete.name, Buefy.default.Autocomplete); Vue.component(Buefy.default.Tag.name, Buefy.default.Tag); Vue.component(Buefy.default.Taginput.name, Buefy.default.Taginput); Vue.use(window.vuelidate.default); var vm = new Vue({ data: { container: { openapi: openapi }, importschema : importschema, specVersion: 'master' }, el: '#main-container', validations: {}, methods : { specLink : function(fragment) { return 'https://github.com/OAI/OpenAPI-Specification/blob/'+this.specVersion+'/versions/3.0.0.md'+(fragment ? fragment : ''); }, save : function() { if (window.localStorage) { window.localStorage.setItem('openapi3', JSON.stringify(this.container.openapi)); } if (window.intelligentBackend) { var data = new FormData(); data.append('source',JSON.stringify(this.container.openapi)); $.ajax({ url:'/store', type:"POST", contentType: false, processData: false, data:data, success: function(result) { } }); } }, postProcessDefinition : function() { return postProcessDefinition(this.container.openapi); } } }); $(document).ajaxError(function(e, jqxhr, settings, thrownError){ console.log(JSON.stringify(jqxhr)); }); $('#aValidate').click(function(){ $('.wizDetails').addClass('hidden'); $('#txtValidation').text('Loading...'); $('#txtValidation').removeClass('hidden'); var convertUrl; if (window.intelligentBackend) convertUrl = '/api/v1/validate'; else convertUrl = 'https://mermade.org.uk/openapi-converter/api/v1/validate'; var data = new FormData(); data.append('source',JSON.stringify(postProcessDefinition(openapi))); $.ajax({ url:convertUrl, type:"POST", contentType: false, processData: false, data:data, dataType:"json", success: function(data) { $('#txtValidation').text(JSON.stringify(data,null,2)); } }); }); $('#aShinola').click(function(){ var shinolaUrl = 'https://shinola.herokuapp.com/openapi'; //var shinolaUrl = 'http://localhost:5678/openapi'; $.ajax({ url:shinolaUrl, type:"POST", data:JSON.stringify(openapi), contentType:"application/json; charset=utf-8", dataType:"text", success: function(data) { var newWindow = window.open("", "API Documentation"); //, "width=950, height=750"); newWindow.document.write(data); newWindow.document.close(); } }); }); $('#aCRUD').click(function(){ $('.wizDetails').addClass('hidden'); $('#divCRUD').removeClass('hidden'); }); $('#aImportSchema').click(function(){ $('.wizDetails').addClass('hidden'); $('#divImportSchema').removeClass('hidden'); }); $('#btnImportSchema').click(function(){ var schemaName = $('#txtSchemaName').val(); if (!schemaName) { alert('Schema name is required'); } else { var obj = {}; try { obj = jsyaml.safeLoad($('#txtSchema').val(),{json:true}); Vue.set(openapi.components.schemas,schemaName,obj); } catch (ex) { alert('Error parsing schema'+ex.message); } } }); }