Files
runner/src/Sdk/DTObjectTemplating/ObjectTemplating/Schema/TemplateSchema.cs
Tingluo Huang 2d6042421f add support for job outputs. (#365)
* add support for job outputs.
2020-03-14 17:54:58 -04:00

493 lines
24 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text.RegularExpressions;
using System.Threading;
using GitHub.DistributedTask.ObjectTemplating.Tokens;
namespace GitHub.DistributedTask.ObjectTemplating.Schema
{
/// <summary>
/// This models the root schema object and contains definitions
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class TemplateSchema
{
internal TemplateSchema()
: this(null)
{
}
private TemplateSchema(MappingToken mapping)
{
// Add built-in type: null
var nullDefinition = new NullDefinition();
Definitions.Add(TemplateConstants.Null, nullDefinition);
// Add built-in type: boolean
var booleanDefinition = new BooleanDefinition();
Definitions.Add(TemplateConstants.Boolean, booleanDefinition);
// Add built-in type: number
var numberDefinition = new NumberDefinition();
Definitions.Add(TemplateConstants.Number, numberDefinition);
// Add built-in type: string
var stringDefinition = new StringDefinition();
Definitions.Add(TemplateConstants.String, stringDefinition);
// Add built-in type: sequence
var sequenceDefinition = new SequenceDefinition { ItemType = TemplateConstants.Any };
Definitions.Add(TemplateConstants.Sequence, sequenceDefinition);
// Add built-in type: mapping
var mappingDefinition = new MappingDefinition { LooseKeyType = TemplateConstants.String, LooseValueType = TemplateConstants.Any };
Definitions.Add(TemplateConstants.Mapping, mappingDefinition);
// Add built-in type: any
var anyDefinition = new OneOfDefinition();
anyDefinition.OneOf.Add(TemplateConstants.Null);
anyDefinition.OneOf.Add(TemplateConstants.Boolean);
anyDefinition.OneOf.Add(TemplateConstants.Number);
anyDefinition.OneOf.Add(TemplateConstants.String);
anyDefinition.OneOf.Add(TemplateConstants.Sequence);
anyDefinition.OneOf.Add(TemplateConstants.Mapping);
Definitions.Add(TemplateConstants.Any, anyDefinition);
if (mapping != null)
{
foreach (var pair in mapping)
{
var key = pair.Key.AssertString($"{TemplateConstants.TemplateSchema} key");
switch (key.Value)
{
case TemplateConstants.Version:
var version = pair.Value.AssertString(TemplateConstants.Version);
Version = version.Value;
break;
case TemplateConstants.Definitions:
var definitions = pair.Value.AssertMapping(TemplateConstants.Definitions);
foreach (var definitionsPair in definitions)
{
var definitionsKey = definitionsPair.Key.AssertString($"{TemplateConstants.Definitions} key");
var definitionsValue = definitionsPair.Value.AssertMapping(TemplateConstants.Definition);
var definition = default(Definition);
foreach (var definitionPair in definitionsValue)
{
var definitionKey = definitionPair.Key.AssertString($"{TemplateConstants.Definition} key");
switch (definitionKey.Value)
{
case TemplateConstants.Null:
definition = new NullDefinition(definitionsValue);
break;
case TemplateConstants.Boolean:
definition = new BooleanDefinition(definitionsValue);
break;
case TemplateConstants.Number:
definition = new NumberDefinition(definitionsValue);
break;
case TemplateConstants.String:
definition = new StringDefinition(definitionsValue);
break;
case TemplateConstants.Sequence:
definition = new SequenceDefinition(definitionsValue);
break;
case TemplateConstants.Mapping:
definition = new MappingDefinition(definitionsValue);
break;
case TemplateConstants.OneOf:
definition = new OneOfDefinition(definitionsValue);
break;
case TemplateConstants.Context:
case TemplateConstants.Description:
continue;
default:
definitionKey.AssertUnexpectedValue("definition mapping key"); // throws
break;
}
break;
}
if (definition == null)
{
throw new ArgumentException($"Unable to determine definition details. Specify the '{TemplateConstants.Structure}' property");
}
Definitions.Add(definitionsKey.Value, definition);
}
break;
default:
key.AssertUnexpectedValue($"{TemplateConstants.TemplateSchema} key"); // throws
break;
}
}
}
}
internal Dictionary<String, Definition> Definitions { get; } = new Dictionary<String, Definition>(StringComparer.Ordinal);
internal String Version { get; }
/// <summary>
/// Loads a user's schema file
/// </summary>
internal static TemplateSchema Load(IObjectReader objectReader)
{
var context = new TemplateContext
{
CancellationToken = CancellationToken.None,
Errors = new TemplateValidationErrors(maxErrors: 10, maxMessageLength: 500),
Memory = new TemplateMemory(
maxDepth: 50,
maxEvents: 1000000, // 1 million
maxBytes: 1024 * 1024), // 1 mb
TraceWriter = new EmptyTraceWriter(),
};
var value = TemplateReader.Read(context, TemplateConstants.TemplateSchema, objectReader, null, Schema, out _);
if (context.Errors.Count > 0)
{
throw new TemplateValidationException(context.Errors);
}
var mapping = value.AssertMapping(TemplateConstants.TemplateSchema);
var schema = new TemplateSchema(mapping);
schema.Validate();
return schema;
}
internal IEnumerable<T> Get<T>(Definition definition)
where T : Definition
{
if (definition is T match)
{
yield return match;
}
else if (definition is OneOfDefinition oneOf)
{
foreach (var reference in oneOf.OneOf)
{
var nestedDefinition = GetDefinition(reference);
if (nestedDefinition is T match2)
{
yield return match2;
}
}
}
}
internal Definition GetDefinition(String type)
{
if (Definitions.TryGetValue(type, out Definition value))
{
return value;
}
throw new ArgumentException($"Schema definition '{type}' not found");
}
internal Boolean HasProperties(MappingDefinition definition)
{
for (int i = 0; i < 10; i++)
{
if (definition.Properties.Count > 0)
{
return true;
}
if (String.IsNullOrEmpty(definition.Inherits))
{
return false;
}
definition = GetDefinition(definition.Inherits) as MappingDefinition;
}
throw new InvalidOperationException("Inheritance depth exceeded 10");
}
internal Boolean TryGetProperty(
MappingDefinition definition,
String name,
out String type)
{
for (int i = 0; i < 10; i++)
{
if (definition.Properties.TryGetValue(name, out PropertyValue property))
{
type = property.Type;
return true;
}
if (String.IsNullOrEmpty(definition.Inherits))
{
type = default;
return false;
}
definition = GetDefinition(definition.Inherits) as MappingDefinition;
}
throw new InvalidOperationException("Inheritance depth exceeded 10");
}
internal Boolean TryMatchKey(
List<MappingDefinition> definitions,
String key,
out String valueType)
{
valueType = null;
// Check for a matching well known property
var notFoundInSome = false;
for (var i = 0; i < definitions.Count; i++)
{
var definition = definitions[i];
if (TryGetProperty(definition, key, out String t))
{
if (valueType == null)
{
valueType = t;
}
}
else
{
notFoundInSome = true;
}
}
// Check if found
if (valueType != null)
{
// Filter the matched definitions if needed
if (notFoundInSome)
{
for (var i = 0; i < definitions.Count;)
{
if (TryGetProperty(definitions[i], key, out _))
{
i++;
}
else
{
definitions.RemoveAt(i);
}
}
}
return true;
}
return false;
}
/// <summary>
/// The built-in schema for reading schema files
/// </summary>
private static TemplateSchema Schema
{
get
{
if (s_schema == null)
{
var schema = new TemplateSchema();
StringDefinition stringDefinition;
SequenceDefinition sequenceDefinition;
MappingDefinition mappingDefinition;
OneOfDefinition oneOfDefinition;
// template-schema
mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.Version, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
mappingDefinition.Properties.Add(TemplateConstants.Definitions, new PropertyValue(new StringToken(null, null, null, TemplateConstants.Definitions)));
schema.Definitions.Add(TemplateConstants.TemplateSchema, mappingDefinition);
// definitions
mappingDefinition = new MappingDefinition();
mappingDefinition.LooseKeyType = TemplateConstants.NonEmptyString;
mappingDefinition.LooseValueType = TemplateConstants.Definition;
schema.Definitions.Add(TemplateConstants.Definitions, mappingDefinition);
// definition
oneOfDefinition = new OneOfDefinition();
oneOfDefinition.OneOf.Add(TemplateConstants.NullDefinition);
oneOfDefinition.OneOf.Add(TemplateConstants.BooleanDefinition);
oneOfDefinition.OneOf.Add(TemplateConstants.NumberDefinition);
oneOfDefinition.OneOf.Add(TemplateConstants.StringDefinition);
oneOfDefinition.OneOf.Add(TemplateConstants.SequenceDefinition);
oneOfDefinition.OneOf.Add(TemplateConstants.MappingDefinition);
oneOfDefinition.OneOf.Add(TemplateConstants.OneOfDefinition);
schema.Definitions.Add(TemplateConstants.Definition, oneOfDefinition);
// null-definition
mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String)));
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
mappingDefinition.Properties.Add(TemplateConstants.Null, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NullDefinitionProperties)));
schema.Definitions.Add(TemplateConstants.NullDefinition, mappingDefinition);
// null-definition-properties
mappingDefinition = new MappingDefinition();
schema.Definitions.Add(TemplateConstants.NullDefinitionProperties, mappingDefinition);
// boolean-definition
mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String)));
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
mappingDefinition.Properties.Add(TemplateConstants.Boolean, new PropertyValue(new StringToken(null, null, null, TemplateConstants.BooleanDefinitionProperties)));
schema.Definitions.Add(TemplateConstants.BooleanDefinition, mappingDefinition);
// boolean-definition-properties
mappingDefinition = new MappingDefinition();
schema.Definitions.Add(TemplateConstants.BooleanDefinitionProperties, mappingDefinition);
// number-definition
mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String)));
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
mappingDefinition.Properties.Add(TemplateConstants.Number, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NumberDefinitionProperties)));
schema.Definitions.Add(TemplateConstants.NumberDefinition, mappingDefinition);
// number-definition-properties
mappingDefinition = new MappingDefinition();
schema.Definitions.Add(TemplateConstants.NumberDefinitionProperties, mappingDefinition);
// string-definition
mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String)));
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
mappingDefinition.Properties.Add(TemplateConstants.String, new PropertyValue(new StringToken(null, null, null, TemplateConstants.StringDefinitionProperties)));
schema.Definitions.Add(TemplateConstants.StringDefinition, mappingDefinition);
// string-definition-properties
mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.Constant, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
mappingDefinition.Properties.Add(TemplateConstants.IgnoreCase, new PropertyValue(new StringToken(null, null, null,TemplateConstants.Boolean)));
mappingDefinition.Properties.Add(TemplateConstants.RequireNonEmpty, new PropertyValue(new StringToken(null, null, null, TemplateConstants.Boolean)));
schema.Definitions.Add(TemplateConstants.StringDefinitionProperties, mappingDefinition);
// sequence-definition
mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String)));
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
mappingDefinition.Properties.Add(TemplateConstants.Sequence, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceDefinitionProperties)));
schema.Definitions.Add(TemplateConstants.SequenceDefinition, mappingDefinition);
// sequence-definition-properties
mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.ItemType, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
schema.Definitions.Add(TemplateConstants.SequenceDefinitionProperties, mappingDefinition);
// mapping-definition
mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String)));
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
mappingDefinition.Properties.Add(TemplateConstants.Mapping, new PropertyValue(new StringToken(null, null, null, TemplateConstants.MappingDefinitionProperties)));
schema.Definitions.Add(TemplateConstants.MappingDefinition, mappingDefinition);
// mapping-definition-properties
mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.Properties, new PropertyValue(new StringToken(null, null, null, TemplateConstants.Properties)));
mappingDefinition.Properties.Add(TemplateConstants.LooseKeyType, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
mappingDefinition.Properties.Add(TemplateConstants.LooseValueType, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
schema.Definitions.Add(TemplateConstants.MappingDefinitionProperties, mappingDefinition);
// properties
mappingDefinition = new MappingDefinition();
mappingDefinition.LooseKeyType = TemplateConstants.NonEmptyString;
mappingDefinition.LooseValueType = TemplateConstants.PropertyValue;
schema.Definitions.Add(TemplateConstants.Properties, mappingDefinition);
// property-value
oneOfDefinition = new OneOfDefinition();
oneOfDefinition.OneOf.Add(TemplateConstants.NonEmptyString);
oneOfDefinition.OneOf.Add(TemplateConstants.MappingPropertyValue);
schema.Definitions.Add(TemplateConstants.PropertyValue, oneOfDefinition);
// mapping-property-value
mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.Type, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
mappingDefinition.Properties.Add(TemplateConstants.Required, new PropertyValue(new StringToken(null, null, null, TemplateConstants.Boolean)));
schema.Definitions.Add(TemplateConstants.MappingPropertyValue, mappingDefinition);
// one-of-definition
mappingDefinition = new MappingDefinition();
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String)));
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
mappingDefinition.Properties.Add(TemplateConstants.OneOf, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
schema.Definitions.Add(TemplateConstants.OneOfDefinition, mappingDefinition);
// non-empty-string
stringDefinition = new StringDefinition();
stringDefinition.RequireNonEmpty = true;
schema.Definitions.Add(TemplateConstants.NonEmptyString, stringDefinition);
// sequence-of-non-empty-string
sequenceDefinition = new SequenceDefinition();
sequenceDefinition.ItemType = TemplateConstants.NonEmptyString;
schema.Definitions.Add(TemplateConstants.SequenceOfNonEmptyString, sequenceDefinition);
schema.Validate();
Interlocked.CompareExchange(ref s_schema, schema, null);
}
return s_schema;
}
}
private void Validate()
{
var oneOfPairs = new List<KeyValuePair<String, OneOfDefinition>>();
foreach (var pair in Definitions)
{
var name = pair.Key;
if (!s_definitionNameRegex.IsMatch(name ?? String.Empty))
{
throw new ArgumentException($"Invalid definition name '{name}'");
}
var definition = pair.Value;
// Delay validation for 'one-of' definitions
if (definition is OneOfDefinition oneOf)
{
oneOfPairs.Add(new KeyValuePair<String, OneOfDefinition>(name, oneOf));
}
// Otherwise validate now
else
{
definition.Validate(this, name);
}
}
// Validate 'one-of' definitions
foreach (var pair in oneOfPairs)
{
var name = pair.Key;
var oneOf = pair.Value;
oneOf.Validate(this, name);
}
}
private static readonly Regex s_definitionNameRegex = new Regex("^[a-zA-Z_][a-zA-Z0-9_-]*$", RegexOptions.Compiled);
private static TemplateSchema s_schema;
}
}