
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
The newest version!
package io.muserver.rest;
import io.muserver.Mutils;
import io.muserver.openapi.*;
import jakarta.ws.rs.core.MediaType;
import java.io.BufferedWriter;
import java.io.IOException;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static io.muserver.Mutils.urlEncode;
import static java.util.Collections.singletonMap;
class HtmlDocumentor {
private final BufferedWriter writer;
private final OpenAPIObject api;
private final String css;
private final URI requestUri;
HtmlDocumentor(BufferedWriter writer, OpenAPIObject api, String css, URI requestUri) {
this.writer = writer;
this.api = api;
this.css = css;
this.requestUri = requestUri;
}
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"));
renderIfValue("p", api.info().description());
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();
}
String baseUri = "";
if (api.servers() != null && !api.servers().isEmpty()) {
baseUri = api.servers().get(0).url();
}
preamble.close();
El nav = new El("ul").open(singletonMap("class", "nav operation"));
for (TagObject tag : api.tags()) {
El li = new El("li").open();
new El("a").open(singletonMap("href", "#" + Mutils.htmlEncode(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", "#" + Mutils.htmlEncode(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", Mutils.htmlEncode(tag.name()))).content(tag.name()).close();
renderIfValue("p", tag.description());
renderExternalLinksParagraph(tag.externalDocs());
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", Mutils.htmlEncode(operation.operationId()));
operationAttributes.put("class", "operation");
El operationDiv = new El("div").open(operationAttributes);
El h3 = new El("h3").open().content(method.toUpperCase() + " ");
String urlWithContext = baseUri + url;
new El("a").open(Collections.singletonMap("href", urlWithContext)).content(url).close();
h3.close();
renderIfValue("p", operation.summary());
renderIfValue("p", operation.description());
renderExternalLinksParagraph(operation.externalDocs());
if (operation.isDeprecated()) {
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();
}
StringBuilder queryString = new StringBuilder();
StringBuilder curlHeaders = new StringBuilder();
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();
if ("query".equals(type)) {
queryString.append(queryString.length() == 0 ? '?' : '&');
queryString.append(urlEncode(parameter.name())).append('=')
.append(urlEncode(bashValue(parameter.example())));
} else if ("header".equals(type)) {
curlHeaders.append(" -H '").append(parameter.name()).append(": ")
.append(bashValue(parameter.example())).append('\'');
}
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();
}
StringBuilder curlBody = new StringBuilder();
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());
String curlFormParam = (mediaType.equalsIgnoreCase(MediaType.MULTIPART_FORM_DATA)) ? "-F" : "-d";
if (curlBody.length() == 0) {
curlBody = new StringBuilder(" -H 'content-type: " + mediaType + "'");
}
if (value.schema() == null || value.schema().properties() == null) {
curlBody.append(" --data '").append(bashValue(value.example())).append("'");
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.isDeprecated()) {
render("strong", "DEPRECATED. ");
}
if (requiredParams != null && requiredParams.contains(formName)) {
render("strong", "REQUIRED. ");
}
paramDesc.content(schema.description());
renderExamples(schema.example(), null, schema.defaultValue());
curlBody.append(" ").append(curlFormParam).append(" '").append(urlEncode(formName)).append("=").append(bashValue(schema.example())).append("'");
paramDesc.close();
row.close();
}
tbody.close();
table.close();
}
}
String curlAccept = "";
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);
String contentTypes = resp.content() == null ? "" : String.join("\n", resp.content().keySet());
render("td", contentTypes);
render("td", resp.description());
if (curlAccept.isEmpty() && !contentTypes.isEmpty()) {
curlAccept = " -H 'accept: " + contentTypes.split("\n", 2)[0] + "'";
}
row.close();
}
tbody.close();
table.close();
}
render("h4", "Curl");
String sampleUrl = urlWithContext.replace("{", "(").replace("}", ")")
+ queryString;
render("code", "curl -is -X " + method.toUpperCase() + curlHeaders + curlAccept +
curlBody + " '" + requestUri.resolve(sampleUrl) + "'");
operationDiv.close();
}
}
}
tagContainer.close();
}
body.close();
html.close();
}
private static String bashValue(Object value) {
return value == null || "".equals(value) ? "" : value.toString().replace("'", "'\\''");
}
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(" " + Mutils.htmlEncode(entry.getKey()) + "=\"" + Mutils.htmlEncode(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(Mutils.htmlEncode(stringVal).replace("\n", "
"));
}
}
}
return this;
}
public void close() throws IOException {
writer.write("" + tag + ">");
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy