blob: 487bcdc0da607cbf6d102e951a776f6efc039b82 [file] [log] [blame]
// Copyright 2013-2018 Workiva Inc.
//
// Licensed under the Boost Software License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.boost.org/LICENSE_1_0.txt
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// This software or document includes material copied from or derived
// from JSON-Schema-Test-Suite (https://github.com/json-schema-org/JSON-Schema-Test-Suite),
// Copyright (c) 2012 Julian Berman, which is licensed under the following terms:
//
// Copyright (c) 2012 Julian Berman
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
/// Functionality to create Graphviz input dot file from schema
library json_schema.schema_dot;
import 'package:json_schema/json_schema.dart';
// custom <additional imports>
// end <additional imports>
/// Represents one node in the schema diagram
class SchemaNode {
/// Referenced schema this node portrays
Schema schema;
/// List of links (resulting in graph edge) from this node to another
List<String> links;
// custom <class SchemaNode>
SchemaNode(Schema this.schema, [this.links]) {
if (links == null) links = [];
}
static bool schemaShown(Schema schema) =>
schema.properties.length > 0 ||
schema.definitions.length > 0 ||
schema.anyOf.length > 0 ||
schema.oneOf.length > 0 ||
schema.allOf.length > 0 ||
schema.enumValues.length > 0 ||
schema.additionalPropertiesSchema != null ||
schema.minimum != null ||
schema.maximum != null ||
schema.ref != null;
String get nodes {
List lines = schema.refMap.values
.where((schema) => schemaShown(schema))
.map((schema) => new SchemaNode(schema, links).node)
.toList();
lines.addAll(links.map((link) => "$link;"));
return lines.join('\n');
}
String get node {
var data = ['"${schema.path}" [']..add(label.join('\n'))..add(']');
return data.join('\n');
}
static dynamic schemaType(Schema schema) {
dynamic result;
var schemaTypeList = schema.schemaTypeList;
if (schemaTypeList == null) {
if (schema.oneOf.length > 0) {
result = "oneOf:${schema.oneOf.map((schema) => schemaType(schema)).toList()}";
} else if (schema.anyOf.length > 0) {
result = "anyOf:${schema.anyOf.map((schema) => schemaType(schema)).toList()}";
} else if (schema.allOf.length > 0) {
result = "allOf:${schema.allOf.map((schema) => schemaType(schema)).toList()}";
} else if (schema.defaultValue != null) {
result = "default=${schema.defaultValue}";
} else if (schema.ref != null) {
result = "ref=${schema.ref}";
} else if (schema.enumValues != null && schema.enumValues.length > 0) {
result = "enum=${schema.enumValues}";
} else if (schema.schemaMap.length == 0) {
result = "{}";
} else {
result = "$schema";
}
} else {
result = schemaTypeList.length == 1 ? schemaTypeList[0] : schemaTypeList;
}
if ((result is List) && result.length == 0) result = {};
if (result == null) result = {};
return result;
}
List<String> get label {
return ['label =<']
..add('<table border="0" cellborder="0" cellpadding="1" bgcolor="white">')
..add(wrap(schema.path, port: "@path"))
..add(wrap(title))
..add(wrap(description))
..addAll(definitionEntries)
..addAll(propertyEntries)
..addAll(additionalPropertiesSchema)
..addAll(propertyDependencies)
..addAll(schemaDependencies)
..addAll(minimum)
..addAll(maximum)
..addAll(defaultValue)
..addAll(anyOf)
..addAll(oneOf)
..addAll(allOf)
..addAll(enumEntries)
..add('</table>')
..add('>');
}
List<String> get defaultValue {
List<String> result = [];
if (schema.defaultValue != null) {
result.add(wrapRowDistinct('default', "${schema.defaultValue}"));
}
return result;
}
List<String> get minimum {
List<String> result = [];
if (schema.minimum != null) {
result.add(wrapRowDistinct('minimum', "${schema.minimum}"));
}
return result;
}
List<String> get maximum {
List<String> result = [];
if (schema.maximum != null) {
result.add(wrapRowDistinct('maximum', "${schema.maximum}"));
}
return result;
}
List<String> get multipleOf {
List<String> result = [];
if (schema.multipleOf != null) {
result.add(wrapRowDistinct('multipleOf', "${schema.multipleOf}"));
}
return result;
}
List<String> get enumEntries {
List<String> enumValues = [];
if (schema.enumValues.length > 0) {
enumValues.add(wrap('Enum Values', color: 'beige'));
schema.enumValues.forEach((value) {
enumValues.add(wrap("$value", color: 'grey'));
});
}
return enumValues;
}
List<String> get anyOf {
List<String> anyOf = [];
if (schema.anyOf.length > 0) {
anyOf.add(wrap('Any Of', color: 'beige'));
int i = 0;
schema.anyOf.forEach((anyOfSchema) {
String port = "${i++}";
makeSchemaLink(port, schema, anyOfSchema);
anyOf.add(wrap(abbreviatedString("${schemaType(anyOfSchema)}", 30), color: 'grey', port: port));
});
}
return anyOf;
}
List<String> get oneOf {
List<String> oneOf = [];
if (schema.oneOf.length > 0) {
oneOf.add(wrap('One Of', color: 'beige'));
int i = 0;
schema.oneOf.forEach((oneOfSchema) {
String port = "${i++}";
makeSchemaLink(port, schema, oneOfSchema);
oneOf.add(wrap(abbreviatedString("${schemaType(oneOfSchema)}", 30), color: 'grey', port: port));
});
}
return oneOf;
}
List<String> get allOf {
List<String> allOf = [];
if (schema.allOf.length > 0) {
allOf.add(wrap('All Of', color: 'beige'));
int i = 0;
schema.allOf.forEach((allOfSchema) {
String port = "${i++}";
makeSchemaLink(port, schema, allOfSchema);
allOf.add(wrap(abbreviatedString("${schemaType(allOfSchema)}", 30), color: 'grey', port: port));
});
}
return allOf;
}
List<String> get propertyDependencies {
List<String> result = [];
if (schema.propertyDependencies.length > 0) {
result.add(wrap('Property Dependencies'));
schema.propertyDependencies.forEach((key, val) {
result.add(wrapRowDistinct(key, val.toString()));
});
}
return result;
}
List<String> get schemaDependencies {
List<String> result = [];
if (schema.schemaDependencies.length > 0) {
result.add('Property Dependencies');
schema.propertyDependencies.forEach((key, val) {
result.add(wrapRowDistinct(key, val.toString()));
});
}
return result;
}
makeSchemaPort(String port, Schema schema) => '"${schema.path}":"$port"';
makeSchemaLink(String port, Schema src, Schema target) {
if (schemaShown(target)) links.add('${makeSchemaPort(port, src)} -> ${makeSchemaPort("@path", target)}');
}
List<String> get additionalPropertiesSchema {
List<String> result = [];
Schema other = schema.additionalPropertiesSchema;
if (other != null) {
result.add(wrap('Additional Properties', color: 'lemonchiffon'));
String port = "mustBe";
makeSchemaLink(port, schema, other);
result.add(wrapRowDistinct('Must Be: ', abbreviatedString(schemaType(other).toString(), 30), port));
}
return result;
}
List<String> get propertyEntries {
List<String> props = [];
if (schema.properties.length > 0) {
props.add(wrap('Properties'));
var sortedProps = new List.from(schema.properties.keys)..sort();
sortedProps.forEach((prop) {
var propertySchema = schema.properties[prop];
String requiredPrefix = schema.propertyRequired(prop) ? '! ' : '? ';
String port = "@$prop";
if (schemaShown(propertySchema)) {
makeSchemaLink(port, schema, propertySchema);
} else if (propertySchema.items is Schema && schemaShown(propertySchema.items)) {
makeSchemaLink(port, schema, propertySchema.items);
}
props.add(wrapRowDistinct(
"$requiredPrefix$prop", abbreviatedString(schemaType(propertySchema).toString(), 30), port));
});
}
return props;
}
List<String> get definitionEntries {
List<String> definitions = [];
if (schema.definitions.length > 0) {
definitions
.add('<tr><td bgcolor="wheat" align="center" colspan="2"><font color="black">Definitions</font></td></tr>');
var sortedDefinitions = new List.from(schema.definitions.keys)..sort();
sortedDefinitions.forEach((key) {
definitions.add(wrapRowDistinct(key, '', "${schema.path}@$key"));
});
}
return definitions;
}
String wrap(String s, {String port: '', String color: 'wheat'}) => s == null
? ''
: '<tr><td bgcolor="$color" align="center" colspan="2" port="$port"><font color="black">$s</font></td></tr>';
String wrapRowDistinct(String first, String second, [String port = '']) =>
'<tr><td align="left" port="$port">$first</td>$first<td bgcolor="grey" align="right">$second</td></tr>';
String get title => schema.title != null ? abbreviatedString("title=${schema.title}", 30) : null;
String get description => schema.description != null ? abbreviatedString("descr=${schema.description}", 30) : null;
String abbreviatedString(String s, [int len = 15]) {
if (s == null) return s;
len -= 3;
if (len >= s.length) {
return s;
} else {
return s.substring(0, len) + '...';
}
}
// end <class SchemaNode>
}
// custom <library schema_dot>
/// Return a dot specification for [schema]
String createDot(Schema schema) => '''
digraph G {
fontname = "Bitstream Vera Sans"
fontsize = 8
node [
fontname = "Courier"
fontsize = 8
shape = "plaintext"
]
edge [
fontname = "Bitstream Vera Sans"
fontsize = 8
]
${new SchemaNode(schema).nodes}
}
''';
// end <library schema_dot>