io.muserver.rest.HtmlDocumentor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mu-server Show documentation
Show all versions of mu-server Show documentation
A simple but powerful web server framework
package io.muserver.rest;
import io.muserver.openapi.*;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static io.muserver.rest.SafeHtml.htmlEscape;
import static java.util.Collections.singletonMap;
class HtmlDocumentor {
private final BufferedWriter writer;
private final OpenAPIObject api;
private final String css;
HtmlDocumentor(BufferedWriter writer, OpenAPIObject api, String css) {
this.writer = writer;
this.api = api;
this.css = css;
}
void writeHtml() throws IOException {
writer.write("\n");
El html = new El("html").open();
El head = new El("head").open();
render("title", api.info.title);
new El("style").open().contentRaw(css).close();
head.close();
El body = new El("body").open();
new El("h1").open().content(api.info.title).close();
El preamble = new El("div").open(singletonMap("class", "preamble"));
new El("p").open().content(api.info.description).close();
renderExternalLinksParagraph(api.externalDocs);
El metaData = new El("p").open(singletonMap("class", "apiMetaData"));
metaData.content("Version " + api.info.version);
if (api.info.contact != null) {
ContactObject contact = api.info.contact;
metaData.content(" | Contact: ");
if (contact.url != null) {
String contactName = contact.name == null ? contact.url.toString() : contact.name;
new El("a").open(Collections.singletonMap("href", contact.url.toString())).content(contactName).close();
} else if (contact.name != null) {
metaData.content(" " + contact.name);
}
if (contact.email != null) {
metaData.content(" | ");
new El("a").open(Collections.singletonMap("href", "mailto:" + contact.email)).content(contact.email).close();
}
}
LicenseObject license = api.info.license;
if (license != null) {
metaData.content(" | License: ");
String name = license.name == null ? license.url.toString() : license.name;
new El("a").open(Collections.singletonMap("href", "mailto:" + license.url)).content(name).close();
}
if (api.info.termsOfService != null) {
metaData.content(" | ");
new El("a").open(Collections.singletonMap("href", api.info.termsOfService.toString())).content("Terms of service").close();
}
preamble.close();
El nav = new El("ul").open(singletonMap("class", "nav"));
for (TagObject tag : api.tags) {
El li = new El("li").open();
new El("a").open(singletonMap("href", "#" + htmlEscape(tag.name))).content(tag.name).close();
El subNav = new El("ul").open(singletonMap("class", "subNav"));
for (Map.Entry entry : api.paths.pathItemObjects.entrySet()) {
String url = entry.getKey();
PathItemObject item = entry.getValue();
for (Map.Entry operationObjectEntry : item.operations.entrySet()) {
String method = operationObjectEntry.getKey();
OperationObject operation = operationObjectEntry.getValue();
if (operation.tags.contains(tag.name)) {
El subNavLi = new El("li").open();
new El("a").open(singletonMap("href", "#" + htmlEscape(operation.operationId))).content(method.toUpperCase() + " " + url).close();
subNavLi.close();
}
}
}
subNav.close();
li.close();
}
nav.close();
for (TagObject tag : api.tags) {
El tagContainer = new El("div").open(singletonMap("class", "tagContainer"));
new El("h2").open(singletonMap("id", htmlEscape(tag.name))).content(tag.name).close();
renderIfValue("p", tag.description);
for (Map.Entry entry : api.paths.pathItemObjects.entrySet()) {
String url = entry.getKey();
PathItemObject item = entry.getValue();
for (Map.Entry operationObjectEntry : item.operations.entrySet()) {
String method = operationObjectEntry.getKey();
OperationObject operation = operationObjectEntry.getValue();
if (operation.tags.contains(tag.name)) {
Map operationAttributes = new HashMap<>();
operationAttributes.put("id", htmlEscape(operation.operationId));
operationAttributes.put("class", "operation");
El operationDiv = new El("div").open(operationAttributes);
new El("h3").open().content(method.toUpperCase() + " " + url).close();
renderIfValue("p", operation.summary);
renderIfValue("p", operation.description);
if (operation.deprecated) {
new El("p").open(singletonMap("class", "deprecated")).content("WARNING: This operation is marked as deprecated and may not be supported in future versions of this API.").close();
}
RequestBodyObject requestBody = operation.requestBody;
if (!operation.parameters.isEmpty()) {
render("h4", "Parameters");
El table = new El("table").open(singletonMap("class", "parameterTable"));
El thead = new El("thead").open();
El theadRow = new El("tr").open();
render("th", "Name");
render("th", "Type");
render("th", "Description");
theadRow.close();
thead.close();
El tbody = new El("tbody").open();
for (ParameterObject parameter : operation.parameters) {
El row = new El("tr").open();
render("td", parameter.name);
String type = parameter.in;
SchemaObject schema = parameter.schema;
Object defaultVal = null;
ExternalDocumentationObject externalDocs = null;
if (schema != null) {
type += " - " + schema.type;
if (schema.format != null) {
type += " (" + schema.format + ")";
}
defaultVal = schema.defaultValue;
externalDocs = schema.externalDocs;
}
render("td", type);
El paramDesc = new El("td").open();
if (parameter.deprecated) {
render("strong", "DEPRECATED. ");
}
if (parameter.required) {
render("strong", "REQUIRED. ");
}
paramDesc.content(parameter.description);
renderExamples(parameter.example, parameter.examples, defaultVal);
renderExternalLinksParagraph(externalDocs);
paramDesc.close();
row.close();
}
tbody.close();
table.close();
}
if (requestBody != null) {
render("h4", "Request body");
renderIfValue("p", requestBody.description);
for (Map.Entry bodyEntry : requestBody.content.entrySet()) {
String mediaType = bodyEntry.getKey();
MediaTypeObject value = bodyEntry.getValue();
render("h5", mediaType);
renderExamples(value.example, value.examples, value.schema == null ? null : value.schema.defaultValue);
if (value.schema == null) {
continue;
}
El table = new El("table").open(singletonMap("class", "parameterTable"));
El thead = new El("thead").open();
El theadRow = new El("tr").open();
render("th", "Name");
render("th", "Type");
render("th", "Description");
theadRow.close();
thead.close();
El tbody = new El("tbody").open();
List requiredParams = value.schema.required;
for (Map.Entry props : value.schema.properties.entrySet()) {
String formName = props.getKey();
SchemaObject schema = props.getValue();
El row = new El("tr").open();
render("td", formName);
String type = schema.type;
if (schema.format != null) {
type += " (" + schema.format + ")";
}
render("td", type);
El paramDesc = new El("td").open();
if (schema.deprecated) {
render("strong", "DEPRECATED. ");
}
if (requiredParams != null && requiredParams.contains(formName)) {
render("strong", "REQUIRED. ");
}
paramDesc.content(schema.description);
renderExamples(schema.example, null, schema.defaultValue);
paramDesc.close();
row.close();
}
tbody.close();
table.close();
}
}
if (!operation.responses.httpStatusCodes.isEmpty()) {
render("h4", "Responses");
El table = new El("table").open(singletonMap("class", "responseTable"));
El thead = new El("thead").open();
El theadRow = new El("tr").open();
render("th", "Code");
render("th", "Content Type");
render("th", "Description");
theadRow.close();
thead.close();
El tbody = new El("tbody").open();
for (Map.Entry respEntry : operation.responses.httpStatusCodes.entrySet()) {
El row = new El("tr").open();
String code = respEntry.getKey();
ResponseObject resp = respEntry.getValue();
render("td", code);
render("td", resp.content.keySet().stream().collect(Collectors.joining("\n")));
render("td", resp.description);
row.close();
}
tbody.close();
table.close();
}
operationDiv.close();
}
}
}
tagContainer.close();
}
body.close();
html.close();
}
private void renderExternalLinksParagraph(ExternalDocumentationObject externalDocs) throws IOException {
if (externalDocs != null) {
String url = externalDocs.url.toString();
String desc = externalDocs.description == null ? url : externalDocs.description;
El ext = new El("p").open().content("For more info, see ");
new El("a").open(Collections.singletonMap("href", url)).content(desc).close();
ext.close();
}
}
private void renderExamples(Object example, Map examples, Object defaultVal) throws IOException {
if (example != null) {
El div = new El("div").open().content("Example: ");
render("code", example.toString());
div.close();
} else if (examples != null) {
for (Map.Entry exampleEntry : examples.entrySet()) {
El div = new El("div").open();
new El("code").open().content(exampleEntry.getKey()).close();
ExampleObject ex = exampleEntry.getValue();
new El("span").open().content(" ", ex.summary, " ", ex.description).close();
new El("pre").open().content(ex.value).close();
div.close();
}
}
if (defaultVal != null) {
El div = new El("div").open().content("Default value: ");
render("code", defaultVal.toString());
div.close();
}
}
private void renderIfValue(String tag, String value) throws IOException {
if (value != null) {
new El(tag).open().content(value).close();
}
}
private void render(String tag, String value) throws IOException {
new El(tag).open().content(value).close();
}
class El implements AutoCloseable {
private final String tag;
private El(String tag) {
this.tag = tag;
}
El open() throws IOException {
return open(null);
}
El open(Map attributes) throws IOException {
writer.write("<" + tag);
if (attributes != null) {
for (Map.Entry entry : attributes.entrySet()) {
writer.write(" " + htmlEscape(entry.getKey()) + "=\"" + htmlEscape(entry.getValue()) + "\"");
}
}
writer.write('>');
return this;
}
El contentRaw(String val) throws IOException {
writer.write(val);
return this;
}
El content(Object... vals) throws IOException {
if (vals != null) {
for (Object val : vals) {
if (val != null) {
String stringVal = val.toString();
writer.write(htmlEscape(stringVal).replace("\n", "
"));
}
}
}
return this;
}
public void close() throws IOException {
writer.write("" + tag + ">");
}
}
}