Skip to content

Instantly share code, notes, and snippets.

@ferm10n
Last active September 11, 2018 18:09
Show Gist options
  • Save ferm10n/b45dcb175530757e7a9de4230bd39d14 to your computer and use it in GitHub Desktop.
Save ferm10n/b45dcb175530757e7a9de4230bd39d14 to your computer and use it in GitHub Desktop.
YAML Schema Verbosifier

YAML Schema Verbosifier

(is that a word? ... it is now!)

More lazyefficient way for creating schemas. Technically this is for JSON schemas, but YAML is easier to read and write.

Rules:

  • infer default value by looking at direct value
  • infer type from default value
  • infer type is 'object' if it has a properties object itself
  • assume properties ending with '?' are optional, and all others are required ('?' is trimmed)

Usage: cat srcYaml | npx https://gist.github.com/ferm10n/b45dcb175530757e7a9de4230bd39d14 > verboseYaml

#!/usr/bin/env node
const laziness = require('./index.js');
const jsyml = require('js-yaml');
const rl = require('readline').createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
const lines = [];
rl.on('line', l => lines.push(l));
rl.on('close', function () {
const spacing = process.argv[2] ? parseInt(process.argv[2]) : null;
const src = lines.join('\n');
const jsonSrc = jsyml.safeLoad(src);
console.log('# created with https://gist.github.com/ferm10n/b45dcb175530757e7a9de4230bd39d14');
console.log('# to recreate:');
console.log('# cat srcYaml | npx https://gist.github.com/ferm10n/b45dcb175530757e7a9de4230bd39d14 > newYaml');
console.log(jsyml.dump(laziness(jsonSrc), {
indent: spacing,
noRefs: true
}));
});
const _ = require('lodash');
// safe type checking because javascript is *weird*
function getType (x) {
if (x instanceof Array) { // typeof [] === 'object'
return 'array';
}
if (x instanceof Object) { // typeof null === 'object'
return 'object';
}
if (x === null) {
return null;
}
return typeof x;
}
function processCombination (combinations, schema) {
for (let combination of combinations) processProperties(combination, schema);
}
function processProperties (properties, schema) {
const definesRequired = _.has(schema, 'required');
schema.required = schema.required || [];
let allDefaulting = true; // until shown otherwise
for (let propertyName in properties) {
let propertyValue = properties[propertyName];
const propertyIsSchema = _.get(propertyValue, 'properties') || _.get(propertyValue, 'default') || _.get(propertyValue, 'type');
// convert direct values into a schema
if (!propertyIsSchema) {
propertyValue = properties[propertyName] = {
default: propertyValue // infer default value by looking at direct value
};
}
// propertyValue is now a schema
// infer type from default value
if (_.has(propertyValue, 'default') && !_.has(propertyValue, 'type') && propertyValue.default !== null) {
propertyValue.type = getType(propertyValue.default);
}
// infer type is 'object' if it has a properties object itself
if (_.has(propertyValue, 'properties') && !_.has(propertyValue, 'type')) {
propertyValue.type = 'object';
}
// apply rules on sub-properties
if (_.has(propertyValue, 'properties')) {
propertyValue = properties[propertyName] = checkProperties(propertyValue);
// don't allow additional properties
if (!_.has(propertyValue, 'additionalProperties')) {
propertyValue.additionalProperties = false;
}
}
// apply rules on items
if (_.has(propertyValue, 'items')) {
if (getType(propertyValue.items) === 'array') { // multiple items?
// apply rules on each item
for (let item of propertyValue.items) checkProperties(item);
} else checkProperties(propertyValue.items); // single item?
}
// Assume properties ending with '?' are optional, and all others are required
if (!definesRequired) { // don't modify required if it's already defined
if (propertyName.match(/.+\?$/)) {
propertyName = propertyName.slice(0, -1); // trim trailing '?'
propertyValue = properties[propertyName] = properties[propertyName + '?']; // copy over to replacement property name
delete properties[propertyName + '?']; // remove old property name
} else {
schema.required.push(propertyName);
}
}
if (!_.has(propertyValue, 'default')) {
allDefaulting = false;
}
}
// all properties have been checked
// if an objects properties all have defaults, then aggregate those defaults up
if (schema.type === 'object' && !_.has(schema, 'default') && allDefaulting) {
schema.default = {};
for (let propertyName in properties) {
schema.default[propertyName] = properties[propertyName].default;
}
}
}
// call this on a schema object. It will look for a properties object and process those
function checkProperties (schema) {
if (!schema) return schema;
if (_.has(schema, 'properties')) processProperties(schema.properties, schema);
// apply rules on combinations
if (_.has(schema, 'allOf')) processProperties(schema.allOf, schema);
if (_.has(schema, 'anyOf')) processProperties(schema.anyOf, schema);
if (_.has(schema, 'oneOf')) processProperties(schema.oneOf, schema);
return schema;
}
module.exports = checkProperties;
{
"name": "simple-yaml-schemas",
"version": "1.0.2",
"bin": "./bin.js",
"dependencies": {
"js-yaml": "^3.3.0",
"lodash": "^4.17.10"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment