Skip to content

Commit

Permalink
AsyncAPI support
Browse files Browse the repository at this point in the history
  • Loading branch information
arno-di-loreto committed Apr 18, 2023
1 parent 27917d5 commit d1b7279
Show file tree
Hide file tree
Showing 58 changed files with 94,512 additions and 54 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 32 additions & 7 deletions oas-data-viewer-web-component/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>OpenAPI Specification Map v2</title>
<title>API Specifications Map</title>
<script type="module" src="specification-viewer.js"></script>
<!-- icons -->
<link rel="apple-touch-icon-precomposed" sizes="57x57" href="images/icons/apple-touch-icon-57x57.png" />
Expand Down Expand Up @@ -30,6 +30,20 @@
font-family: monaco, Consolas, 'Lucida Console', monospace;
font-size: 0.8rem;
}

.specification {
font-size: 2rem;
background-color: black;
color: white;
padding: 0.3rem;
margin-left: 0.4rem;
border-radius: 8px;
vertical-align: middle;
text-transform: uppercase;
padding: 0.4rem;
cursor: pointer;
}

.version {
font-size: 1.2rem;
background-color: lightgreen;
Expand Down Expand Up @@ -77,21 +91,32 @@
</head>
<body>
<div class="header">
<h1><img alt="Brand" id="icon" src="images/icons/favicon-32x32.png">OpenAPI Map (v2 alpha)</h1>
<h1>API Specifications Map (alpha)</h1>
<div class="selector">
<span class="version v2">2.0</span><span class="version v30">3.0.3</span><span class="version v31 selected">3.1.0</span>
<span class="specification"><img alt="Brand" id="icon" src="images/icons/favicon-32x32.png">OpenAPI</span>
<span class="version v2" id="2.0">2.0</span>
<span class="version v30" id="3.0.3">3.0.3</span>
<span class="version v31 selected" id="3.1.0">3.1.0</span>
<span class="specification"><img src="images/asyncapi-logo-32x32.png">AsyncAPI</span>
<span class="version" id="asyncapi-2.0.0">2.0.0</span>
<span class="version" id="asyncapi-2.1.0">2.1.0</span>
<span class="version" id="asyncapi-2.2.0">2.2.0</span>
<span class="version" id="asyncapi-2.3.0">2.3.0</span>
<span class="version" id="asyncapi-2.4.0">2.4.0</span>
<span class="version" id="asyncapi-2.5.0">2.5.0</span>
<span class="version" id="asyncapi-2.6.0">2.6.0</span>
</div>
</div>
<div class="content">
<div class="intro">
<p>
The is a work-in-progress demo of OpenAPI Map v2.
The OpenAPI Map v2 renders data extracted from the <a href="https://github.com/OAI/OpenAPI-Specification" target="_blank">OpenAPI Specification</a> documentation (markdown file).
The is a work-in-progress demo of OpenAPI Map v2 which became the API Specification Map.
It renders data extracted from the <a href="https://www.openapis.org/" target="_blank">OpenAPI Specification</a> and <a href="https://www.asyncapi.com/" target="_blank">AsyncAPI Specification</a> documentations.
</p>
<p>
It has been created by <a href="http://apihandyman.io" target="_blank">Arnaud Lauret, the API Handyman</a>&nbsp;<a href="https://twitter.com/apihandyman"><svg class="svg-inline--fa fa-twitter fa-w-16" aria-hidden="true" data-prefix="fab" data-icon="twitter" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" data-fa-i2svg=""><path fill="currentColor" d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"></path></svg><!-- <i class="fab fa-twitter"></i> --></a>.</p>
<p>
<p>You'll find the previous version (which only support versions 2. and 3.0) <a href="https://openapi-map.apihandyman.io/">here</a>.</p>
<p>You'll find the initial OpenAPI Map (which only support OpenAPI 2.0 and 3.0) <a href="https://openapi-map.apihandyman.io/">here</a>.</p>
<p><a href="https://github.com/arno-di-loreto/openapi-specification-documentation-as-data">Github repository</a></p>
</div>
<div class="viewer">
Expand All @@ -107,7 +132,7 @@ <h1><img alt="Brand" id="icon" src="images/icons/favicon-32x32.png">OpenAPI Map
console.log('onVersionClick', event);
const version = event.composedPath()[0];
const viewer = document.querySelector('specification-viewer');
const dataFile = '../specifications-data/'+version.textContent+'.json';
const dataFile = '../specifications-data/'+version.id+'.json';
console.log('data file', dataFile);
viewer.setAttribute('src', dataFile);
const versions = document.querySelectorAll('.selected');
Expand Down
52 changes: 38 additions & 14 deletions oas-data-viewer-web-component/specification-viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class SpecificationViewer extends HTMLElement {
border-radius: .4em;
padding: 0.5rem;
margin-top: 0.5rem;
max-width: 600px;
max-width: 500px;
z-index:1;
}
Expand Down Expand Up @@ -371,6 +371,7 @@ class SpecificationViewer extends HTMLElement {

_getHtmlReferenceLinks(data){
const links = document.createElement('div');
/*
links.classList.add('reference-links')
let list = '<h2>Reference Links</h2><ul>'
let count = 0;
Expand All @@ -384,6 +385,7 @@ class SpecificationViewer extends HTMLElement {
if(count > 0){
links.innerHTML = list;
}
*/
return links;
}

Expand All @@ -409,7 +411,9 @@ class SpecificationViewer extends HTMLElement {
`;

const sections = this._createNodes();
sections.appendChild(this._getHtmlHistorySection());
if(this.specification.history.length > 0){
sections.appendChild(this._getHtmlHistorySection());
}
sections.appendChild(this._getHtmlSchemaSection());
sections.appendChild(this._getHtmlClassDiagramSection());
sections.appendChild(this._getHtmlConceptsSection());
Expand Down Expand Up @@ -544,7 +548,7 @@ class SpecificationViewer extends HTMLElement {
}

_getHtmlSchemaSection() {
return this._getHtmlSection('schema', 'Schemas', this.specification.history);
return this._getHtmlSection('schema', 'Schemas', this.specification.schemas);
}

_getHtmlClassDiagramSection() {
Expand All @@ -565,6 +569,7 @@ class SpecificationViewer extends HTMLElement {
})
types.sort();
types.unshift(rootName);
console.log(types);
const htmlSchemas = this._getHtmlSchemas(types);
return htmlSchemas;
}
Expand All @@ -583,13 +588,19 @@ class SpecificationViewer extends HTMLElement {
}
const url_md = schema.urls.find(url => url.name === 'markdown').url;
const url_html = schema.urls.find(url => url.name === 'html').url;
let navigation='';
if(schema.fields && schema.fields.length > 0){
navigation = `
<div class="navigation">
<span class="nav-button-children nav-button-children-schemas" data-action="children">→</span>
</div>
`
}
htmlSchema.innerHTML = `
<div>
<div class="node-title">
<h1>${schema.name}${root}${extensible}</h1>
<div class="navigation">
<span class="nav-button-children nav-button-children-schemas" data-action="children">→</span>
</div>
${navigation}
</div>
<div class="node-content${this.getNodeContentDefaultVisibility()}">
<div class="description">${schema.description}</div>
Expand Down Expand Up @@ -655,8 +666,6 @@ class SpecificationViewer extends HTMLElement {
`
}

const url_md = field.urls.find(url => url.name === 'markdown').url;
const url_html = field.urls.find(url => url.name === 'html').url;
const fieldTypes = [];
field.type.types.forEach(type => {
let typeClass = 'type'
Expand All @@ -676,6 +685,25 @@ class SpecificationViewer extends HTMLElement {
else if (field.type.parentType === 'map'){
fieldType = `<span class="pill pill-field pill-field-map">Map</span><span class="syntax">{ * : </span>${fieldType}<span class="syntax">}</span>`;
}

let htmlFieldLinks = '';
if(field.urls && field.urls.length > 0){
const url_md = field.urls.find(url => url.name === 'markdown').url;
const url_html = field.urls.find(url => url.name === 'html').url;
htmlFieldLinks = `
<div class="links">
<a href="${url_md}" target="MD_${this.specification.version}">MD Documentation&nbsp;🔗</a>
<a href="${url_html}" target="HTML_${this.specification.version}">HTML Documentation&nbsp;🔗</a>
</div>
${this._getHtmlReferenceLinks(field).outerHTML}
`
}

let description = ''
if(field.description){
description = field.description;
}

htmlField.innerHTML = `
<div>
<div class="node-title">
Expand All @@ -689,12 +717,8 @@ class SpecificationViewer extends HTMLElement {
</div>
</div>
<div class="node-content${this.getNodeContentDefaultVisibility()}">
<div class="description">${field.description}</div>
<div class="links">
<a href="${url_md}" target="MD_${this.specification.version}">MD Documentation&nbsp;🔗</a>
<a href="${url_html}" target="HTML_${this.specification.version}">HTML Documentation&nbsp;🔗</a>
</div>
${this._getHtmlReferenceLinks(field).outerHTML}
<div class="description">${description}</div>
${htmlFieldLinks}
</div>
</div>
`;
Expand Down
2 changes: 1 addition & 1 deletion oas-md-parser-python/src/OasData.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ def __init__(self, source, data_parent):
self.__init__ids()

def __init__ids(self):
self.ids = []
if len(self.get_source().anchors) > 0:
self.ids = []
for anchor in self.get_source().anchors:
self.ids.append(DataId(anchor))

Expand Down
9 changes: 5 additions & 4 deletions oas-md-parser-python/src/OasDataHistory.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ def __init__(self, source, specification):
def __init__events(self):
self.events = []
history_section = self.get_source().find_section_for_text(re.compile(r'Revision History$'))
for content in history_section.get_contents():
if content.sub_type == ContentSubType.TABLE:
for line in content.get_lines():
self.events.append(DataEvent(line, self))
if(history_section != None):
for content in history_section.get_contents():
if content.sub_type == ContentSubType.TABLE:
for line in content.get_lines():
self.events.append(DataEvent(line, self))
28 changes: 26 additions & 2 deletions oas-md-parser-python/src/OasDataSchema.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ def __init__parent_type_and_types(self):
t = 'Any'
self.types.append(t.strip())

class DataFieldFixType(Data):
def __init__(self, parent, parent_type, types):
super().__init__(parent._source, parent)
self.parent_type = parent_type
self.types = types

class DataFieldFix(Data, DataWithUrls):
def __init__(self, parent, name, name_type, parent_type, types):
super().__init__(parent._source, parent)
self.name = name
self.name_type = name_type
self.is_required = False
self.type = DataFieldFixType(parent, parent_type, types)

class DataField(Data, DataWithUrls):

FIXED = 'fixed'
Expand Down Expand Up @@ -112,7 +126,7 @@ def __init__is_root(self):
self.is_root = False

def __init__name(self):
self.name = self.get_source().get_text()
self.name = self.get_source().get_text().strip()

def __init__description(self):
self.description = self.get_source().get_only_content_as_html()
Expand All @@ -124,7 +138,7 @@ def __init__description(self):

def __init__is_extensible(self):
self.is_extensible = False
if self.get_data_root()._version.is_version('2'):
if self.get_data_root()._version.name == 'Swagger':
patterned_fields_section = self.get_source().find_section_for_text(re.compile('^Patterned (Fields|Objects)$'))
if patterned_fields_section != None:
patterned_fields = self._get_fields_from_section(patterned_fields_section, DataField.PATTERNED)
Expand Down Expand Up @@ -162,6 +176,9 @@ def __init__fields(self):
if self.is_extensible:
self.fields += self._get_fields_from_section(specification_extensions_section, DataField.PATTERNED)

def add_field(self, name, name_type, parent_type, types):
self.fields.append(DataFieldFix(self, name, name_type, parent_type, types))

class DataSchemas(Data):
def __init__(self, source, specification):
super().__init__(source, specification)
Expand All @@ -178,8 +195,15 @@ def __init__schemas(self):
for content in schema_section.get_contents():
if content.type == ContentType.SECTION:
self.schemas.append(DataSchema(content, self))
self.__fix_schemas()
self.__init_schemas_usages()

def __fix_schemas(self):
if(self.get_data_root()._version.name == 'AsyncAPI'):
for schema in self.schemas:
if(schema.name == 'Tags Object'):
schema.add_field('[*]', 'fixed', None, ['Tag Object'])

def __init_schemas_usages(self):
for schema in self.schemas:
schema.usages = self.__get_schema_usages(schema)
Expand Down
53 changes: 40 additions & 13 deletions oas-md-parser-python/src/OasDataUrl.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,47 @@
from OasData import Data
from OasData import Data, DataId
from OasDataVersion import DataVersion
from MarkdownParser import ContentType
from UtilsClass import Dictable
import re


class SpecificationUrls:
_urls = {
_openapi_urls = {
'markdown': 'https://github.com/OAI/OpenAPI-Specification/tree/main/versions/{version.revision}.md',
'html': 'https://spec.openapis.org/oas/v{version.revision}',
'schema': 'https://github.com/OAI/OpenAPI-Specification/tree/main/schemas/v{version.minor}'
}

_asyncapi_urls = {
'markdown': 'https://github.com/asyncapi/website/blob/master/pages/docs/reference/specification/v{version.revision}.md',
'html': 'https://www.asyncapi.com/docs/reference/specification/v{version.revision}',
'schema': 'https://github.com/asyncapi/spec-json-schemas/blob/master/schemas/{version.revision}.json'
}

MARKDOWN='markdown'
HTML='html'
SCHEMA='schema'

_url_version_regex_search = r'.*{version.(?P<version>[a-z]*)}'
_url_version_regex_replace = r'{version.[a-z]*}'

def get_specification_urls(version):
def get_specification_urls_templates(specification):
result = None
if specification == 'OpenAPI':
result = SpecificationUrls._openapi_urls
elif specification == 'AsyncAPI':
result = SpecificationUrls._asyncapi_urls
return result

def get_specification_urls(specification, version):
result = []
for key in SpecificationUrls._urls.keys():
result.append(SpecificationUrls.get_specification_url(version, key))
templates = SpecificationUrls.get_specification_urls_templates(specification)
for key in templates.keys():
result.append(SpecificationUrls.get_specification_url(specification, version, key))
return result

def get_specification_url(version, name, anchor=None):
url_template = SpecificationUrls._urls[name]
def get_specification_url(specification, version, name, anchor=None):
url_template = SpecificationUrls.get_specification_urls_templates(specification)[name]
version_type_result = re.compile(SpecificationUrls._url_version_regex_search).search(url_template)
version = version.get_version(version_type_result.group('version'))
url = re.sub(SpecificationUrls._url_version_regex_replace, version, url_template)
Expand All @@ -46,18 +62,29 @@ def __init__(self, url, name, type):
class DataUrls(Data):
def __init__(self, source, parent):
super().__init__(source, parent)
self.__init_specification_name()
self.__init_specification_urls()
self.__init__other_urls()

def __init_specification_name(self):
title = self.get_data_root().get_source().get_parsed_html().find('h1')
title_regex = re.compile(r'(.*)\s*Specification')
self._specification_name = title_regex.search(title.get_text()).group(1).strip()

def __init_specification_urls(self):
if self.get_source().type == ContentType.DOCUMENT:
self.urls = SpecificationUrls.get_specification_urls(self.get_data_root()._version)
self.urls = SpecificationUrls.get_specification_urls(self._specification_name, self.get_data_root()._version)
else:
id = self.get_data_parent().get_id()
if id != None:
self.urls = []
self.urls.append(SpecificationUrls.get_specification_url(self.get_data_root()._version, SpecificationUrls.MARKDOWN, id))
self.urls.append(SpecificationUrls.get_specification_url(self.get_data_root()._version, SpecificationUrls.HTML, id))
self.urls = []
if(self._specification_name == 'AsyncAPI'):
html_id = self.get_data_parent().get_id(DataId.SECONDARY)
markdown_id = self.get_data_parent().get_id()
else:
markdown_id = self.get_data_parent().get_id()
html_id = markdown_id
if html_id != None:
self.urls.append(SpecificationUrls.get_specification_url(self._specification_name, self.get_data_root()._version, SpecificationUrls.MARKDOWN, markdown_id))
self.urls.append(SpecificationUrls.get_specification_url(self._specification_name, self.get_data_root()._version, SpecificationUrls.HTML, html_id))

def __get_all_links(self):
if self.get_source().type == ContentType.DOCUMENT:
Expand Down
Loading

0 comments on commit d1b7279

Please sign in to comment.