Skip to content

Commit

Permalink
feat(markdown): show value constraints
Browse files Browse the repository at this point in the history
  • Loading branch information
trieloff committed Dec 10, 2019
1 parent c8e8dfa commit 515969c
Showing 1 changed file with 231 additions and 5 deletions.
236 changes: 231 additions & 5 deletions lib/markdownBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const {
each, values, map, list: flist, iter, flat, filter, size, foldl,
} = require('ferrum');
const {
root, paragraph, text, heading, code, table, tableRow, tableCell, link, inlineCode, list, listItem
root, paragraph, text, heading, code, table, tableRow, tableCell, link, inlineCode, list, listItem, strong
} = require('mdast-builder');
const i18n = require('es2015-i18n-tag').default;
const s = require('./symbols');
Expand All @@ -22,7 +22,101 @@ const { basename } = require('path');
const ghslugger = require('github-slugger');

function build({ header, links = {}, includeproperties = [] } = {}) {
const formats = {
'date-time': {
label: i18n`date time`,
text: i18n`the string must be a date time string, according to `,
specname: 'RFC 3339, section 5.6',
speclink: 'https://tools.ietf.org/html/rfc3339'
},
'date': {
label: i18n`date`,
text: i18n`the string must be a date string, according to `,
specname: 'RFC 3339, section 5.6',
speclink: 'https://tools.ietf.org/html/rfc3339'
},
'time': {
label: i18n`time`,
text: i18n`the string must be a time string, according to `,
specname: 'RFC 3339, section 5.6',
speclink: 'https://tools.ietf.org/html/rfc3339'
},
'duration': {
label: i18n`duration`,
text: i18n`the string must be a duration string, according to `,
specname: 'RFC 3339, section 5.6',
speclink: 'https://tools.ietf.org/html/rfc3339'
},
'email': {
label: i18n`email`,
text: i18n`the string must be an email address, according to `,
specname: 'RFC 5322, section 3.4.1',
speclink: 'https://tools.ietf.org/html/rfc5322'
},
'idn-email': {
label: i18n`(international) email`,
text: i18n`the string must be an (international) email address, according to `,
specname: 'RFC 6531',
speclink: 'https://tools.ietf.org/html/rfc6531'
},
'hostname': {
label: i18n`hostname`,
text: i18n`the string must be a hostname, according to `,
specname: 'RFC 1123, section 2.1',
speclink: 'https://tools.ietf.org/html/rfc1123'
},
'idn-hostname': {
label: i18n`(international) hostname`,
text: i18n`the string must be an (IDN) hostname, according to `,
specname: 'RFC 5890, section 2.3.2.3',
speclink: 'https://tools.ietf.org/html/rfc5890'
},
'ipv4': {
label: i18n`IPv4`,
text: i18n`the string must be an IPv4 address (dotted quad), according to `,
specname: 'RFC 2673, section 3.2',
speclink: 'https://tools.ietf.org/html/rfc2673'
},
'ipv6': {
label: i18n`IPv6`,
text: i18n`the string must be an IPv6 address, according to `,
specname: 'RFC 4291, section 2.2',
speclink: 'https://tools.ietf.org/html/rfc4291'
},
'uri': {
label: i18n`URI`,
text: i18n`the string must be a URI, according to `,
specname: 'RFC 3986',
speclink: 'https://tools.ietf.org/html/rfc4291'
},
'iri': {
label: i18n`IRI`,
text: i18n`the string must be a IRI, according to `,
specname: 'RFC 3987',
speclink: 'https://tools.ietf.org/html/rfc4291'
},
'uri-reference': {
label: i18n`URI reference`,
text: i18n`the string must be a URI reference, according to `,
specname: 'RFC 3986',
speclink: 'https://tools.ietf.org/html/rfc4291'
},
'iri-reference': {
label: i18n`IRI reference`,
text: i18n`the string must be a IRI reference, according to `,
specname: 'RFC 3987',
speclink: 'https://tools.ietf.org/html/rfc4291'
},
'uuid': {
label: i18n`UUID`,
text: i18n`the string must be a UUID, according to `,
specname: 'RFC 4122',
speclink: 'https://tools.ietf.org/html/rfc4122'
},
}

const headerprops = [
/*
{
name: 'type',
title: i18n`Type`,
Expand All @@ -37,6 +131,7 @@ function build({ header, links = {}, includeproperties = [] } = {}) {
integerlabel: i18n`Integer`,
nulllabel: i18n`Null`,
},
*/
{
name: 'abstract',
title: i18n`Abstract`,
Expand Down Expand Up @@ -232,7 +327,7 @@ function build({ header, links = {}, includeproperties = [] } = {}) {
} else if (merged) {
return [text(isarray ? `an array of merged types` : i18n`merged type`)];
}
console.log('unknown type', realtypes, singletype, merged, definition[s.pointer]);
//console.log('unknown type', realtypes, singletype, merged, definition[s.pointer]);
return [text(i18n`unknown` + isarray)];
})();

Expand Down Expand Up @@ -308,6 +403,8 @@ function build({ header, links = {}, includeproperties = [] } = {}) {
description,
paragraph(inlineCode(name)),
makefactlist(name, definition, required),
...maketypesection(definition, level + 1),
...makeconstraintssection(definition, level + 1),
...makeexamples(definition, level + 1)
];
})));
Expand Down Expand Up @@ -354,16 +451,143 @@ function build({ header, links = {}, includeproperties = [] } = {}) {
return [];
}

function simpletitle(schema) {
return schema[s.parent]? schema[s.pointer].split('/').pop() : gentitle(schema[s.titles], schema.type)
}

function makeexamples(schema, level = 1) {
if (schema.examples && schema.examples.length > 0) {
return [
heading(level + 1, text(i18n`${schema[s.parent]? schema[s.pointer].split('/').pop() : gentitle(schema[s.titles], schema.type)} Examples`)),
heading(level + 1, text(i18n`${simpletitle(schema)} Examples`)),
...schema.examples.map(example => paragraph(code('json', JSON.stringify(example, undefined, 2))))
]
}
return [];
}

function maketypesection(schema, level = 1) {
const { children } = maketypefact(schema);
children[0].children.shift();
return [
paragraph(text(i18n`no examples provided`))
];
heading(level + 1, text(i18n`${simpletitle(schema)} Type`)),
...children
]
}

function makeconstraintssection(schema, level = 1) {
const constraints = [];
if (schema.const) {
//console.log('const!', schema[s.pointer]);
constraints.push(paragraph([strong(text(i18n`constant`)), text(': '), text(i18n`the value of this property must be equal to:`)]));
constraints.push(code('json', JSON.stringify(schema.const, undefined, 2)));
}

if (schema.enum) {
console.log('enum!', schema[s.filename], schema[s.pointer]);
const metas = schema['meta:enum'] || {};
constraints.push(paragraph([strong(text(i18n`constant`)), text(': '), text(i18n`the value of this property must be equal to one of the following values:`)]));
constraints.push(table('left', [
tableRow([
tableCell(text(i18n`Value`)),
tableCell(text(i18n`Explanation`))
]),
...schema.enum.map(value => {
return tableRow([
tableCell(inlineCode(JSON.stringify(value))),
tableCell(text(metas[value] || ''))
])
})
]))
}


// https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.6.2
if (schema.multipleOf) {
//console.log('multiple!', schema[s.filename], schema[s.pointer]);
constraints.push(paragraph([strong(text(i18n`multiple of`)), text(': '), text(i18n`the value of this number must be a multiple of: `), inlineCode(String(schema.multipleOf))]));
}
if (schema.maximum) {
//console.log('maximum!', schema[s.filename], schema[s.pointer]);
constraints.push(paragraph([strong(text(i18n`maximum`)), text(': '), text(i18n`the value of this number must smaller than or equal to: `), inlineCode(String(schema.maximum))]));
}
if (schema.exclusiveMaximum) {
//console.log('exclusiveMaximum!', schema[s.filename], schema[s.pointer]);
constraints.push(paragraph([strong(text(i18n`maximum (exclusive)`)), text(': '), text(i18n`the value of this number must be smaller than: `), inlineCode(String(schema.exclusiveMaximum))]));
}
if (schema.minimum) {
//console.log('minimum!', schema[s.filename], schema[s.pointer]);
constraints.push(paragraph([strong(text(i18n`minimum`)), text(': '), text(i18n`the value of this number must greater than or equal to: `), inlineCode(String(schema.minimum))]));
}
if (schema.exclusiveMinimum) {
//console.log('exclusiveMinimum!', schema[s.filename], schema[s.pointer]);
constraints.push(paragraph([strong(text(i18n`minimum (exclusive)`)), text(': '), text(i18n`the value of this number must be greater than: `), inlineCode(String(schema.exclusiveMinimum))]));
}

// https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.6.3
if (schema.maxLength) {
//console.log('maxLength!', schema[s.filename], schema[s.pointer]);
constraints.push(paragraph([strong(text(i18n`maximum length`)), text(': '), text(i18n`the maximum number of characters for this string is: `), inlineCode(String(schema.maxLength))]));
}
if (schema.minLength) {
//console.log('minLength!', schema[s.filename], schema[s.pointer]);
constraints.push(paragraph([strong(text(i18n`minimum length`)), text(': '), text(i18n`the minimum number of characters for this string is: `), inlineCode(String(schema.minLength))]));
}
if (schema.pattern) {
//console.log('pattern!', schema[s.filename], schema[s.pointer]);
constraints.push(paragraph([strong(text(i18n`pattern`)), text(': '), text(i18n`the string must match the following regular expression: `)]));
constraints.push(code('regexp', schema.pattern));
constraints.push(paragraph([link(`https://regexr.com/?expression=${encodeURIComponent(schema.pattern)}`, i18n`try regular expression with regexr.com`, text(i18n`try pattern`))]))
}
// https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.7.3
if (schema.format && formats[schema.format]) {
constraints.push(paragraph([
strong(text(formats[schema.format].label)),
text(': '),
text(formats[schema.format].text),
link(formats[schema.format].speclink, i18n`check the specification`, text(formats[schema.format].specname))
]));
} else if (schema.format) {
constraints.push(paragraph([strong(text(i18n`unknown format`)), text(': '), text(i18n`the value of this string must follow the format: `), inlineCode(String(schema.format))]));
}

// https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.6.4
if (schema.maxItems) {
//console.log('maxItems!', schema[s.filename], schema[s.pointer]);
constraints.push(paragraph([strong(text(i18n`maximum number of items`)), text(': '), text(i18n`the maximum number of items for this array is: `), inlineCode(String(schema.maxItems))]));
}
if (schema.minItems) {
//console.log('minItems!', schema[s.filename], schema[s.pointer]);
constraints.push(paragraph([strong(text(i18n`minimum number of items`)), text(': '), text(i18n`the minimum number of items for this array is: `), inlineCode(String(schema.minItems))]));
}
if (schema.uniqueItems) {
//console.log('uniqueItems!', schema[s.filename], schema[s.pointer]);
constraints.push(paragraph([strong(text(i18n`unique items`)), text(': '), text(i18n`all items in this array must be unique. Duplicates are not allowed.`)]));
}
if (schema.minContains && schema.contains) {
//console.log('minContains!', schema[s.filename], schema[s.pointer]);
constraints.push(paragraph([strong(text(i18n`minimum number of contained items`)), text(': '), text(i18n`this array may not contain more than ${schema.minContains} items that validate against the schema:`),
link(`${schema.contains[s.slug].md}`, i18n`check type definition`, gentitle(schema.contains[s.titles], schema.contains.type))]));
}
if (schema.maxContains && schema.contains) {
//console.log('maxContains!', schema[s.filename], schema[s.pointer]);
constraints.push(paragraph([strong(text(i18n`maximum number of contained items`)), text(': '), text(i18n`this array may not contain fewer than ${schema.maxContains} items that validate against the schema:`),
link(`${schema.contains[s.slug].md}`, i18n`check type definition`, gentitle(schema.contains[s.titles], schema.contains.type))]));
}

// https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.6.5
if (schema.maxProperties) {
//console.log('maxProperties!', schema[s.filename], schema[s.pointer]);
constraints.push(paragraph([strong(text(i18n`maximum number of properties`)), text(': '), text(i18n`the maximum number of properties for this object is: `), inlineCode(String(schema.maxProperties))]));
}
if (schema.minProperties) {
//console.log('minProperties!', schema[s.filename], schema[s.pointer]);
constraints.push(paragraph([strong(text(i18n`minimum number of properties`)), text(': '), text(i18n`the minimum number of properties for this object is: `), inlineCode(String(schema.minProperties))]));
}

if (constraints.length > 0) {
return [heading(level + 1, text(i18n`${simpletitle(schema)} Constraints`)), ...constraints];
}
return [];
}

console.log('generating markdown');
Expand All @@ -375,6 +599,8 @@ function build({ header, links = {}, includeproperties = [] } = {}) {
pv[schema[s.slug]] = root([
// todo add more elements
...makeheader(schema),
...maketypesection(schema, 1),
...makeconstraintssection(schema, 1),
...makeexamples(schema, 1),
...makedefinitions(schema, slugger),
...makeproperties(schema, slugger),
Expand Down

0 comments on commit 515969c

Please sign in to comment.