All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.reprezen.genflow.openapi3.doc.StructureTable.xtend Maven / Gradle / Ivy

package com.reprezen.genflow.openapi3.doc

import com.reprezen.kaizen.oasparser.model3.Parameter
import com.reprezen.kaizen.oasparser.model3.Schema
import java.util.List
import org.apache.commons.lang3.StringUtils

abstract class StructureTable {

	extension protected HtmlHelper = HelperHelper.htmlHelper
	extension protected AttributeHelper = HelperHelper.attributeHelper
	extension protected DocHelper = HelperHelper.docHelper
	extension RecursionHelper = HelperHelper.recursionHelper
	extension protected ArrayHelper = HelperHelper.arrayHelper
	extension protected RefHelper = HelperHelper.refHelper
	extension protected OptionHelper = HelperHelper.optionHelper
	extension protected KaiZenParserHelper = new KaiZenParserHelper

	protected val String[][] cols
	protected val T obj

	protected new(T obj, String[]... cols) {
		this.cols = cols
		this.obj = obj
	}

	/* Public method for rendering a table describing an object */
	def String render(String name) {
		'''
			
			    «renderHeaderRow»
			    «renderObject(name, null, new Indentation())»
			
''' } def protected renderObject(String name, Object referrer, Indentation ind) { var Activation activation = null try { activation = obj.use render(name, referrer, ind) } catch (BadReferenceException e) { new ExceptionStructureTable(e, cols).render(name, referrer, ind) } catch (RecursiveRenderException e) { new ExceptionStructureTable(e, cols).render(name, referrer, ind) } catch (BadArrayException e) { new ExceptionStructureTable(e, cols).render(name, referrer, ind) } finally { activation?.close } } def protected String renderColumn(String colAttr, String name, Object referrer, Indentation ind, AttrDetails det) { val chosenName = obj.chooseName(name) switch colAttr { case "name": chosenName?.htmlEscape?.formatName(chosenName, obj, referrer)?.indentCode(ind) case "type": getTypeSpec(det)?.code case "doc": getDoc() case "details": det?.details(false)?.toString default: obj.getAttribute(colAttr).valueForDisplay } } def protected abstract String render(String name, Object referrer, Indentation ind); def protected abstract String getTypeSpec(AttrDetails det); def protected abstract String getDoc(); static class SchemaStructureTable extends StructureTable { new(Schema obj, String[]... cols) { super(obj, cols) } override protected render(String name, Object referrer, Indentation ind) { if (obj.type == "array") { val det = new AttrDetails(obj.elementType) val itemsTable = new SchemaStructureTable(obj.elementType, cols) return ''' «defaultRender(name, referrer, ind, det)» «itemsTable.renderObject(null, null, ind.advance2)» ''' } val det = new AttrDetails(obj) val additionalPropertiesSchema = try { // See https://github.com/RepreZen/KaiZen-OpenApi-Parser/issues/104 for why this can throw NPE obj.getAdditionalPropertiesSchema()?.asNullIfMissing } catch (NullPointerException e) { null } val allOfSchemas = obj.allOfSchemas // FIXME asNullIfMissing is always true val oneOfSchemas = obj.oneOfSchemas val anyOfSchemas = obj.anyOfSchemas ind.use2 ind.advance2 // if (obj.properties.empty && additionalPropertiesSchema === null && oneOfSchemas.empty) { // defaultRender(name, referrer, ind, det) // } else { val modelRow = if (name !== null) defaultRender(name, referrer, ind, det) ''' «modelRow»«FOR prop : obj.properties.entrySet»«new SchemaStructureTable(prop.value, cols).renderObject("!" + prop.key, obj, ind.advance2)»«ENDFOR» «IF additionalPropertiesSchema !== null» «new SchemaStructureTable(additionalPropertiesSchema, cols).renderObject("![additional properties]", obj, ind.advance2)» «ENDIF» «IF !allOfSchemas.isEmpty» «FOR schema: allOfSchemas» «renderMemberModel(schema, "allOf", ind.advance2, det)» «ENDFOR» «ENDIF» «IF !oneOfSchemas.isEmpty» «FOR schema: oneOfSchemas» «renderMemberModel(schema, "oneOf", ind.advance2, det)» «ENDFOR» «ENDIF» «IF !anyOfSchemas.isEmpty» «FOR schema: anyOfSchemas» «renderMemberModel(schema, "anyOf", ind.advance2, det)» «ENDFOR» «ENDIF» ''' // } } override protected getTypeSpec(AttrDetails det) { if (obj.type == "array") { return '''«obj.arrayTypeSpec»«det.infoButton»''' } getTypeSpec(obj, det) } def protected getTypeSpec(Schema model, AttrDetails det) { val modelType = model.getDefaultTypeSpec() return '''«modelType»«det.infoButton»''' // if (model.properties.empty) { // val namedType = (if(modelType !== null && !modelType.empty) modelType + ": " else "") + model.type // return '''«namedType»«det.infoButton»''' // } else { // modelType // } } def protected String renderMemberModel(Schema memberModel, String label, Indentation ind, AttrDetails det) { ''' «memberModel.renderMemberRow(label, ind.copy, det)» «new SchemaStructureTable(memberModel, cols).renderObject(null, null, ind.copy)» ''' } def private String renderMemberRow(Schema member, String label, Indentation ind, AttrDetails det) { if (showComponentModels) { val text = '''«label»: «member.getTypeSpec(det)?.htmlEscape?.samp»''' #[text.toString.indentTextToCode(ind)].wrapHeaderRow } } override protected getDoc() { obj.description?.docHtml } } static class ParameterStructureTable extends StructureTable { new(Parameter obj, String[]... cols) { super(obj, cols) } override protected render(String name, Object referrer, Indentation ind) { val Schema detailType = if(obj.schema.type == "array") obj.schema.elementType else obj.schema val det = new AttrDetails(detailType) ''' «defaultRender(name, referrer, ind, det)» ''' } override protected getTypeSpec(AttrDetails det) { val param = obj if (param.schema.type == "array") { '''«param.schema.arrayTypeSpec»«det.infoButton»''' } else { '''«param.schema.getDefaultTypeSpec()»«det.infoButton»''' } } override protected getDoc() { obj.description?.docHtml } } static class ParametersStructureTable extends StructureTable> { new(List obj, String[]... cols) { super(obj, cols) } override protected render(String name, Object referrer, Indentation ind) { if (!obj.empty) { '''«FOR param : obj»«new ParameterStructureTable(param, cols).renderObject(null, referrer, ind.advance2)»«ENDFOR»''' } } override protected getTypeSpec(AttrDetails det) { throw new UnsupportedOperationException("TODO: auto-generated method stub") } override protected getDoc() { throw new UnsupportedOperationException("TODO: auto-generated method stub") } } static class ExceptionStructureTable extends StructureTable { protected new(Exception obj, String[]... cols) { super(obj, cols) } override protected render(String name, Object referrer, Indentation ind) { defaultRender(name, referrer, ind, null) } override protected getTypeSpec(AttrDetails det) { return getTypeSpec(obj, det) } /***************** * Attempt to render nonsensical array *****************/ def private dispatch String getTypeSpec(BadArrayException e, AttrDetails det) { "???[]" } def private dispatch getTypeSpec(BadReferenceException e, AttrDetails det) { "ref" } def private dispatch getTypeSpec(RecursiveRenderException e, AttrDetails det) { "(recursive)" } override protected renderColumn(String colAttr, String name, Object referrer, Indentation ind, AttrDetails det) { if (obj instanceof RecursiveRenderException) { renderColumn(obj, colAttr, name, referrer, ind, det) } else { super.renderColumn(colAttr, name, referrer, ind, det) } } /***************** * Recursive rendering attempt *****************/ def protected String renderColumn(RecursiveRenderException e, String colAttr, String name, Object referrer, Indentation ind, AttrDetails det) { val obj = e.object.safeResolve switch colAttr { case "name": { val tooltip = ' ' (obj.chooseName(name).htmlEscape + tooltip).indentCode(ind) } case "type": getTypeSpec(det)?.code default: obj.getAttribute(colAttr).valueForDisplay } } override protected getDoc() { getDoc(obj) } def private dispatch String getDoc(BadReferenceException e) { val refString = e.refString.replaceAll("#/_UNRESOLVABLE/", "") '''Invalid Reference: «refString.htmlEscape»''' } def private dispatch String getDoc(BadArrayException e) { e.message } } def protected String renderHeaderRow() { cols.map[it.get(1)].wrapHeaderRow } def protected String defaultRender(String name, Object referrer, Indentation ind, AttrDetails det) { ''' «cols.map[col|renderColumn(col.get(0), name, referrer, ind, det)].wrapRow» «renderAttrDetails(name, referrer, ind, det)?.wrapRow(true)» ''' } def private String renderAttrDetails(String name, Object referrer, Indentation ind, AttrDetails det) { renderColumn("details", name, referrer, ind, det) } def protected dispatch String chooseName(Object obj, String offeredName) { if (offeredName !== null && offeredName.startsWith("!")) { offeredName.substring(1) } else { #[offeredName, obj.rzveTypeName, obj.name].filter[it !== null].last } } def private String formatName(String formattedName, String rawName, Object obj, Object referrer) { if (isRequired(obj, rawName, referrer)) { '''«formattedName»''' } else { '''«formattedName»''' } } def private isRequired(Object obj, String name, Object referrer) { // obj = value of named property or parameter in referrer switch (obj) { Parameter: // same for parameters return obj.isRequired Schema: // all other named things are references to models, and the referrer determines requiredness return referrer.requiredProperties.contains(name) default: throw new IllegalArgumentException( "Named item is represented by neither a Property, a Parameter, nor a Model") } } def private List getRequiredProperties(Object referrer) { if (referrer !== null) { switch (referrer) { Schema: return referrer.requiredFields.toList } } return #[] } def protected String getDefaultTypeSpec(Schema obj) { #[obj.kaiZenSchemaName, obj.type, obj.rzveTypeName].filter[it !== null].last } /***************** * ArrayModel covers array schemas *****************/ // FIXME adapt to OAS3 // // /***************** // * ComposedModel covers allOf schemas // *****************/ // def private dispatch String render(ComposedModel model, String name, Object referrer, Indentation ind, // AttrDetails det) { // ind.use2 // val componentInd = ind.advance2 // ''' // «model.defaultRender(name, referrer, ind, det)» // «FOR member : model.allOf»«member.safeResolve.renderMemberModel(componentInd, det)»«ENDFOR» // ''' // } // // /***************** // * ObjectProperty // *****************/ // def private dispatch String render(ObjectProperty prop, String name, Object referrer, Indentation ind, // AttrDetails det) { // ''' // «IF name !== null && !name.empty»«prop.defaultRender(name, referrer, ind, det)»«ENDIF» // «FOR field : prop.properties.entrySet»«field.value.renderObject("!" + field.key, prop, ind.advance2)»«ENDFOR» // ''' // } /***************** * ArrayProperty *****************/ // def private dispatch String render(ArrayProperty prop, String name, Object referrer, Indentation ind, // AttrDetails det) { // det.setObject(prop.elementType) // ''' // «prop.defaultRender(name, referrer, ind, det)» // «prop.elementType.renderObject(null, null, ind.advance2)» // ''' // } // // def private dispatch String getTypeSpec(ArrayProperty prop, AttrDetails det) { // '''«prop.arrayTypeSpec»«det.infoButton»''' // } // // /***************** // * MapProperty // *****************/ // def private dispatch String render(MapProperty prop, String name, Object referrer, Indentation ind, // AttrDetails det) { // val apSchema = prop.additionalProperties?.safeResolve // ''' // «prop.defaultRender(name, referrer, ind, det)» // «apSchema?.renderObject("![additional properties]", referrer, ind.advance2)» // ''' // } /***************** * All other property types (properties with primitive types) *****************/ // def private dispatch String render(AbstractProperty prop, String name, Object referrer, Indentation ind, // AttrDetails det) { // ''' // «IF name !== null»«prop.defaultRender(name, referrer, ind, det)»«ENDIF» // ''' // } // // def private dispatch String getTypeSpec(AbstractProperty prop, AttrDetails det) { // '''«prop.getDefaultTypeSpec(det)»«det.infoButton»''' // } def protected dispatch String chooseName(Parameter param, String offeredName) { param.name } /***************** * Unresolvable Ref *****************/ def protected dispatch chooseName(BadReferenceException e, String offeredName) { if(offeredName !== null && offeredName.startsWith("!")) offeredName.substring(1) else offeredName } /***************** * Utility methods *****************/ def private String wrapRow(String value, boolean noBorder) { #[value].wrapRow(noBorder) } def private String wrapRow(List values) { values.wrapRow(false) } def private String wrapRow(List values, boolean noBorder) { val style = if (noBorder) { ' style="border-top:0px"' } '''«FOR value : values»«value»«ENDFOR»''' } def protected String wrapHeaderRow(List values) { '''«FOR value : values»«value»«ENDFOR»''' } def getColSpan(List values) { if (values.size == 1 && cols.size > 1) { ''' colspan="«cols.size»"''' } } def protected String indentTextToCode(String s, Indentation ind) { ind.use1 getIndentation(ind.n1 + ind.n2).samp + s } def protected String indentCode(String s, Indentation ind) { ind.use1 ind.use2 ind.n1.indentation.samp + (ind.n2.indentation + s).code } def private getIndentation(int n) { StringUtils::repeat("  ", n) } def protected String getValueForDisplay(Object o) { o?.formatValue } def private dispatch String formatValue(String s) { s?.htmlEscape } def private dispatch String formatValue(List list) { '''«FOR item : list SEPARATOR "
"»item?.toString?.htmlEscape«ENDFOR»''' } } /** * Surprisingly, indentation was one of the hardest things to get right in this module! Here's how it works. *

* There are two sorts of indented texts used in the tables: *

    *
  • code items: blank indentation followed by shaded indentation followed by shaded monospaced text. Blank * indentation is in a "samp" element to produce monospaced text. Second indentation and text are joined within * a "code" element, which provides monospacing and shading. *
  • text item: blank indentation followed by text. Indentation is in a "samp" block for monospacing, text is * left as-is. *
      * The Indentation class maintains two indentation levels, called n1 and n2, which control the width of the * plain and shaded indentations. For a new table, both start out at zero. *

      * Certain structures call for advancing these indentation levels. In all cases, this results in a new * Indentation object which is passsed in nested calls, so when those calls unwind the prevailing indentation * object is unchanged. *

      * The tricky part turned out to be knowing whether to ignore indentation changes. The answer is keeping * track of whether anything's actually been output using the current indentation levels. If so, the advance * is performed, and either way, a new Indentation object is created. So, for example, the properties of a * top-level ObjectProperty will be at indentation level (0,0) even though its rendering method calls for * advancing the n2 value for the nested properties. *

      * Advancing the n1 value really means setting n1 to n1+n2 and n2 to zero. The n1 value is only used when * outputing the names of schemas that contribut to an allOf schema. */ package class Indentation { val int n1 val int n2 var boolean used1 = false var boolean used2 = false new() { this(0, 0) } new(int n1, int n2) { this.n1 = n1 this.n2 = n2 } new(Indentation ind) { this.n1 = ind.n1 this.n2 = ind.n2 this.used1 = ind.used1 this.used2 = ind.used2 } def copy() { new Indentation(this) } def advance1() { if(used1 || used2) new Indentation(n1 + n2 + 1, 0) else new Indentation(this) } def advance2() { if (used2) { val adv = new Indentation(n1, n2 + 1) if (used1) { adv.use1 } adv } else { copy } } def getN1() { n1 } def getN2() { n2 } def use1() { used1 = true } def use2() { used2 = true } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy