Compare updated template evaluator (#4092)
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.Actions.WorkflowParser.ObjectTemplating.Schema
|
||||
{
|
||||
internal sealed class BooleanDefinition : ScalarDefinition
|
||||
{
|
||||
internal BooleanDefinition()
|
||||
{
|
||||
}
|
||||
|
||||
internal BooleanDefinition(MappingToken definition)
|
||||
: base(definition)
|
||||
{
|
||||
foreach (var definitionPair in definition)
|
||||
{
|
||||
var definitionKey = definitionPair.Key.AssertString($"{TemplateConstants.Definition} key");
|
||||
switch (definitionKey.Value)
|
||||
{
|
||||
case TemplateConstants.Boolean:
|
||||
var mapping = definitionPair.Value.AssertMapping($"{TemplateConstants.Definition} {TemplateConstants.Boolean}");
|
||||
foreach (var mappingPair in mapping)
|
||||
{
|
||||
var mappingKey = mappingPair.Key.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Boolean} key");
|
||||
switch (mappingKey.Value)
|
||||
{
|
||||
default:
|
||||
mappingKey.AssertUnexpectedValue($"{TemplateConstants.Definition} {TemplateConstants.Boolean} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TemplateConstants.CoerceRaw:
|
||||
continue;
|
||||
|
||||
default:
|
||||
definitionKey.AssertUnexpectedValue($"{TemplateConstants.Definition} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal override DefinitionType DefinitionType => DefinitionType.Boolean;
|
||||
|
||||
internal override Boolean IsMatch(LiteralToken literal)
|
||||
{
|
||||
return literal is BooleanToken;
|
||||
}
|
||||
|
||||
internal override void Validate(
|
||||
TemplateSchema schema,
|
||||
String name)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.Actions.WorkflowParser.ObjectTemplating.Schema
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the allowable schema for a user defined type
|
||||
/// </summary>
|
||||
internal abstract class Definition
|
||||
{
|
||||
protected Definition()
|
||||
{
|
||||
}
|
||||
|
||||
protected Definition(MappingToken definition)
|
||||
{
|
||||
for (var i = 0; i < definition.Count; )
|
||||
{
|
||||
var definitionKey = definition[i].Key.AssertString($"{TemplateConstants.Definition} key");
|
||||
if (String.Equals(definitionKey.Value, TemplateConstants.Context, StringComparison.Ordinal))
|
||||
{
|
||||
var context = definition[i].Value.AssertSequence($"{TemplateConstants.Context}");
|
||||
definition.RemoveAt(i);
|
||||
var readerContext = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
||||
var evaluatorContext = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (TemplateToken item in context)
|
||||
{
|
||||
var itemStr = item.AssertString($"{TemplateConstants.Context} item").Value;
|
||||
readerContext.Add(itemStr);
|
||||
|
||||
// Remove min/max parameter info
|
||||
var paramIndex = itemStr.IndexOf('(');
|
||||
if (paramIndex > 0)
|
||||
{
|
||||
evaluatorContext.Add(String.Concat(itemStr.Substring(0, paramIndex + 1), ")"));
|
||||
}
|
||||
else
|
||||
{
|
||||
evaluatorContext.Add(itemStr);
|
||||
}
|
||||
}
|
||||
|
||||
ReaderContext = readerContext.ToArray();
|
||||
EvaluatorContext = evaluatorContext.ToArray();
|
||||
}
|
||||
else if (String.Equals(definitionKey.Value, TemplateConstants.Description, StringComparison.Ordinal))
|
||||
{
|
||||
definition.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract DefinitionType DefinitionType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Used by the template reader to determine allowed expression values and functions.
|
||||
/// Also used by the template reader to validate function min/max parameters.
|
||||
/// </summary>
|
||||
internal String[] ReaderContext { get; private set; } = new String[0];
|
||||
|
||||
/// <summary>
|
||||
/// Used by the template evaluator to determine allowed expression values and functions.
|
||||
/// The min/max parameter info is omitted.
|
||||
/// </summary>
|
||||
internal String[] EvaluatorContext { get; private set; } = new String[0];
|
||||
|
||||
internal abstract void Validate(
|
||||
TemplateSchema schema,
|
||||
String name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace GitHub.Actions.WorkflowParser.ObjectTemplating.Schema
|
||||
{
|
||||
internal enum DefinitionType
|
||||
{
|
||||
Null,
|
||||
Boolean,
|
||||
Number,
|
||||
String,
|
||||
Sequence,
|
||||
Mapping,
|
||||
OneOf,
|
||||
AllowedValues,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
#nullable disable // Consider removing in the future to minimize likelihood of NullReferenceException; refer https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.Actions.WorkflowParser.ObjectTemplating.Schema
|
||||
{
|
||||
internal sealed class MappingDefinition : Definition
|
||||
{
|
||||
internal MappingDefinition()
|
||||
{
|
||||
}
|
||||
|
||||
internal MappingDefinition(MappingToken definition)
|
||||
: base(definition)
|
||||
{
|
||||
foreach (var definitionPair in definition)
|
||||
{
|
||||
var definitionKey = definitionPair.Key.AssertString($"{TemplateConstants.Definition} key");
|
||||
switch (definitionKey.Value)
|
||||
{
|
||||
case TemplateConstants.Mapping:
|
||||
var mapping = definitionPair.Value.AssertMapping($"{TemplateConstants.Definition} {TemplateConstants.Mapping}");
|
||||
foreach (var mappingPair in mapping)
|
||||
{
|
||||
var mappingKey = mappingPair.Key.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Mapping} key");
|
||||
switch (mappingKey.Value)
|
||||
{
|
||||
case TemplateConstants.Properties:
|
||||
var properties = mappingPair.Value.AssertMapping($"{TemplateConstants.Definition} {TemplateConstants.Mapping} {TemplateConstants.Properties}");
|
||||
foreach (var propertiesPair in properties)
|
||||
{
|
||||
var propertyName = propertiesPair.Key.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Mapping} {TemplateConstants.Properties} key");
|
||||
Properties.Add(propertyName.Value, new PropertyDefinition(propertiesPair.Value));
|
||||
}
|
||||
break;
|
||||
|
||||
case TemplateConstants.LooseKeyType:
|
||||
var looseKeyType = mappingPair.Value.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Mapping} {TemplateConstants.LooseKeyType}");
|
||||
LooseKeyType = looseKeyType.Value;
|
||||
break;
|
||||
|
||||
case TemplateConstants.LooseValueType:
|
||||
var looseValueType = mappingPair.Value.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Mapping} {TemplateConstants.LooseValueType}");
|
||||
LooseValueType = looseValueType.Value;
|
||||
break;
|
||||
|
||||
default:
|
||||
definitionKey.AssertUnexpectedValue($"{TemplateConstants.Definition} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TemplateConstants.CoerceRaw:
|
||||
continue;
|
||||
|
||||
default:
|
||||
definitionKey.AssertUnexpectedValue($"{TemplateConstants.Definition} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal override DefinitionType DefinitionType => DefinitionType.Mapping;
|
||||
|
||||
internal String LooseKeyType { get; set; }
|
||||
|
||||
internal String LooseValueType { get; set; }
|
||||
|
||||
internal Dictionary<String, PropertyDefinition> Properties { get; } = new Dictionary<String, PropertyDefinition>(StringComparer.Ordinal);
|
||||
|
||||
internal override void Validate(
|
||||
TemplateSchema schema,
|
||||
String name)
|
||||
{
|
||||
// Lookup loose key type
|
||||
if (!String.IsNullOrEmpty(LooseKeyType))
|
||||
{
|
||||
schema.GetDefinition(LooseKeyType);
|
||||
|
||||
// Lookup loose value type
|
||||
if (!String.IsNullOrEmpty(LooseValueType))
|
||||
{
|
||||
schema.GetDefinition(LooseValueType);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Property '{TemplateConstants.LooseKeyType}' is defined but '{TemplateConstants.LooseValueType}' is not defined on '{name}'");
|
||||
}
|
||||
}
|
||||
// Otherwise validate loose value type not be defined
|
||||
else if (!String.IsNullOrEmpty(LooseValueType))
|
||||
{
|
||||
throw new ArgumentException($"Property '{TemplateConstants.LooseValueType}' is defined but '{TemplateConstants.LooseKeyType}' is not defined");
|
||||
}
|
||||
|
||||
// Lookup each property
|
||||
foreach (var property in Properties)
|
||||
{
|
||||
if (String.IsNullOrEmpty(property.Value.Type))
|
||||
{
|
||||
throw new ArgumentException($"Type not specified for the '{property.Key}' property on the '{name}' type");
|
||||
}
|
||||
|
||||
schema.GetDefinition(property.Value.Type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.Actions.WorkflowParser.ObjectTemplating.Schema
|
||||
{
|
||||
internal sealed class NullDefinition : ScalarDefinition
|
||||
{
|
||||
internal NullDefinition()
|
||||
{
|
||||
}
|
||||
|
||||
internal NullDefinition(MappingToken definition)
|
||||
: base(definition)
|
||||
{
|
||||
foreach (var definitionPair in definition)
|
||||
{
|
||||
var definitionKey = definitionPair.Key.AssertString($"{TemplateConstants.Definition} key");
|
||||
switch (definitionKey.Value)
|
||||
{
|
||||
case TemplateConstants.Null:
|
||||
var mapping = definitionPair.Value.AssertMapping($"{TemplateConstants.Definition} {TemplateConstants.Null}");
|
||||
foreach (var mappingPair in mapping)
|
||||
{
|
||||
var mappingKey = mappingPair.Key.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Null} key");
|
||||
switch (mappingKey.Value)
|
||||
{
|
||||
default:
|
||||
mappingKey.AssertUnexpectedValue($"{TemplateConstants.Definition} {TemplateConstants.Null} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TemplateConstants.AllowedValues:
|
||||
case TemplateConstants.CoerceRaw:
|
||||
continue;
|
||||
|
||||
default:
|
||||
definitionKey.AssertUnexpectedValue($"{TemplateConstants.Definition} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal override DefinitionType DefinitionType => DefinitionType.Null;
|
||||
|
||||
internal override Boolean IsMatch(LiteralToken literal)
|
||||
{
|
||||
return literal is NullToken;
|
||||
}
|
||||
|
||||
internal override void Validate(
|
||||
TemplateSchema schema,
|
||||
String name)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.Actions.WorkflowParser.ObjectTemplating.Schema
|
||||
{
|
||||
internal sealed class NumberDefinition : ScalarDefinition
|
||||
{
|
||||
internal NumberDefinition()
|
||||
{
|
||||
}
|
||||
|
||||
internal NumberDefinition(MappingToken definition)
|
||||
: base(definition)
|
||||
{
|
||||
foreach (var definitionPair in definition)
|
||||
{
|
||||
var definitionKey = definitionPair.Key.AssertString($"{TemplateConstants.Definition} key");
|
||||
switch (definitionKey.Value)
|
||||
{
|
||||
case TemplateConstants.Number:
|
||||
var mapping = definitionPair.Value.AssertMapping($"{TemplateConstants.Definition} {TemplateConstants.Number}");
|
||||
foreach (var mappingPair in mapping)
|
||||
{
|
||||
var mappingKey = mappingPair.Key.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Number} key");
|
||||
switch (mappingKey.Value)
|
||||
{
|
||||
default:
|
||||
mappingKey.AssertUnexpectedValue($"{TemplateConstants.Definition} {TemplateConstants.Number} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TemplateConstants.CoerceRaw:
|
||||
continue;
|
||||
|
||||
default:
|
||||
definitionKey.AssertUnexpectedValue($"{TemplateConstants.Definition} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal override DefinitionType DefinitionType => DefinitionType.Number;
|
||||
|
||||
internal override Boolean IsMatch(LiteralToken literal)
|
||||
{
|
||||
return literal is NumberToken;
|
||||
}
|
||||
|
||||
internal override void Validate(
|
||||
TemplateSchema schema,
|
||||
String name)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
#nullable disable // Consider removing in the future to minimize likelihood of NullReferenceException; refer https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.Actions.WorkflowParser.ObjectTemplating.Schema
|
||||
{
|
||||
/// <summary>
|
||||
/// Must resolve to exactly one of the referenced definitions
|
||||
/// </summary>
|
||||
internal sealed class OneOfDefinition : Definition
|
||||
{
|
||||
internal OneOfDefinition()
|
||||
{
|
||||
}
|
||||
|
||||
internal OneOfDefinition(MappingToken definition)
|
||||
: base(definition)
|
||||
{
|
||||
foreach (var definitionPair in definition)
|
||||
{
|
||||
var definitionKey = definitionPair.Key.AssertString($"{TemplateConstants.Definition} key");
|
||||
switch (definitionKey.Value)
|
||||
{
|
||||
case TemplateConstants.OneOf:
|
||||
var oneOf = definitionPair.Value.AssertSequence(TemplateConstants.OneOf);
|
||||
foreach (var oneOfItem in oneOf)
|
||||
{
|
||||
var reference = oneOfItem.AssertString(TemplateConstants.OneOf);
|
||||
OneOf.Add(reference.Value);
|
||||
}
|
||||
break;
|
||||
|
||||
case TemplateConstants.CoerceRaw:
|
||||
continue;
|
||||
|
||||
default:
|
||||
definitionKey.AssertUnexpectedValue($"{TemplateConstants.Definition} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal override DefinitionType DefinitionType => DefinitionType.OneOf;
|
||||
|
||||
internal List<String> OneOf { get; } = new List<String>();
|
||||
|
||||
internal override void Validate(
|
||||
TemplateSchema schema,
|
||||
String name)
|
||||
{
|
||||
if (OneOf.Count == 0)
|
||||
{
|
||||
throw new ArgumentException($"'{name}' does not contain any references");
|
||||
}
|
||||
|
||||
var foundLooseKeyType = false;
|
||||
var mappingDefinitions = default(List<MappingDefinition>);
|
||||
var sequenceDefinition = default(SequenceDefinition);
|
||||
var nullDefinition = default(NullDefinition);
|
||||
var booleanDefinition = default(BooleanDefinition);
|
||||
var numberDefinition = default(NumberDefinition);
|
||||
var stringDefinitions = default(List<StringDefinition>);
|
||||
|
||||
foreach (var nestedType in OneOf)
|
||||
{
|
||||
var nestedDefinition = schema.GetDefinition(nestedType);
|
||||
|
||||
if (nestedDefinition.ReaderContext.Length > 0)
|
||||
{
|
||||
throw new ArgumentException($"'{name}' is a one-of definition and references another definition that defines context. This is currently not supported.");
|
||||
}
|
||||
|
||||
if (nestedDefinition is MappingDefinition mappingDefinition)
|
||||
{
|
||||
if (mappingDefinitions == null)
|
||||
{
|
||||
mappingDefinitions = new List<MappingDefinition>();
|
||||
}
|
||||
|
||||
mappingDefinitions.Add(mappingDefinition);
|
||||
|
||||
if (!String.IsNullOrEmpty(mappingDefinition.LooseKeyType))
|
||||
{
|
||||
foundLooseKeyType = true;
|
||||
}
|
||||
}
|
||||
else if (nestedDefinition is SequenceDefinition s)
|
||||
{
|
||||
// Multiple sequence definitions not allowed
|
||||
if (sequenceDefinition != null)
|
||||
{
|
||||
throw new ArgumentException($"'{name}' refers to more than one '{TemplateConstants.Sequence}'");
|
||||
}
|
||||
|
||||
sequenceDefinition = s;
|
||||
}
|
||||
else if (nestedDefinition is NullDefinition n)
|
||||
{
|
||||
// Multiple sequence definitions not allowed
|
||||
if (nullDefinition != null)
|
||||
{
|
||||
throw new ArgumentException($"'{name}' refers to more than one '{TemplateConstants.Null}'");
|
||||
}
|
||||
|
||||
nullDefinition = n;
|
||||
}
|
||||
else if (nestedDefinition is BooleanDefinition b)
|
||||
{
|
||||
// Multiple boolean definitions not allowed
|
||||
if (booleanDefinition != null)
|
||||
{
|
||||
throw new ArgumentException($"'{name}' refers to more than one '{TemplateConstants.Boolean}'");
|
||||
}
|
||||
|
||||
booleanDefinition = b;
|
||||
}
|
||||
else if (nestedDefinition is NumberDefinition num)
|
||||
{
|
||||
// Multiple number definitions not allowed
|
||||
if (numberDefinition != null)
|
||||
{
|
||||
throw new ArgumentException($"'{name}' refers to more than one '{TemplateConstants.Number}'");
|
||||
}
|
||||
|
||||
numberDefinition = num;
|
||||
}
|
||||
else if (nestedDefinition is StringDefinition stringDefinition)
|
||||
{
|
||||
// First string definition
|
||||
if (stringDefinitions == null)
|
||||
{
|
||||
stringDefinitions = new List<StringDefinition>();
|
||||
}
|
||||
// Multiple string definitions, all must be 'Constant'
|
||||
else if ((stringDefinitions.Count == 1 && String.IsNullOrEmpty(stringDefinitions[0].Constant))
|
||||
|| String.IsNullOrEmpty(stringDefinition.Constant))
|
||||
{
|
||||
throw new ArgumentException($"'{name}' refers to more than one '{TemplateConstants.Scalar}', but some do not set '{TemplateConstants.Constant}'");
|
||||
}
|
||||
|
||||
stringDefinitions.Add(stringDefinition);
|
||||
}
|
||||
else if (nestedDefinition is OneOfDefinition oneOfDefinition)
|
||||
{
|
||||
// Allow one-of to reference another one-of
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"'{name}' refers to a '{nestedDefinition.DefinitionType}' definition");
|
||||
}
|
||||
}
|
||||
|
||||
if (mappingDefinitions?.Count > 1)
|
||||
{
|
||||
if (foundLooseKeyType)
|
||||
{
|
||||
throw new ArgumentException($"'{name}' refers to two mappings that both set '{TemplateConstants.LooseKeyType}'");
|
||||
}
|
||||
|
||||
var seenProperties = new Dictionary<String, PropertyDefinition>(StringComparer.Ordinal);
|
||||
|
||||
foreach (var mappingDefinition in mappingDefinitions)
|
||||
{
|
||||
foreach (var newProperty in mappingDefinition.Properties)
|
||||
{
|
||||
// Already seen
|
||||
if (seenProperties.TryGetValue(newProperty.Key, out PropertyDefinition existingProperty))
|
||||
{
|
||||
// Types match
|
||||
if (String.Equals(existingProperty.Type, newProperty.Value.Type, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Collision
|
||||
throw new ArgumentException($"'{name}' contains two mappings with the same property, but each refers to a different type. All matching properties must refer to the same type.");
|
||||
}
|
||||
// New
|
||||
else
|
||||
{
|
||||
seenProperties.Add(newProperty.Key, newProperty.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#nullable disable // Consider removing in the future to minimize likelihood of NullReferenceException; refer https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references
|
||||
|
||||
using System;
|
||||
using GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.Actions.WorkflowParser.ObjectTemplating.Schema
|
||||
{
|
||||
internal sealed class PropertyDefinition
|
||||
{
|
||||
internal PropertyDefinition(TemplateToken token)
|
||||
{
|
||||
if (token is StringToken stringToken)
|
||||
{
|
||||
Type = stringToken.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
var mapping = token.AssertMapping($"{TemplateConstants.MappingPropertyValue}");
|
||||
foreach (var mappingPair in mapping)
|
||||
{
|
||||
var mappingKey = mappingPair.Key.AssertString($"{TemplateConstants.MappingPropertyValue} key");
|
||||
switch (mappingKey.Value)
|
||||
{
|
||||
case TemplateConstants.Type:
|
||||
Type = mappingPair.Value.AssertString($"{TemplateConstants.MappingPropertyValue} {TemplateConstants.Type}").Value;
|
||||
break;
|
||||
case TemplateConstants.Required:
|
||||
Required = mappingPair.Value.AssertBoolean($"{TemplateConstants.MappingPropertyValue} {TemplateConstants.Required}").Value;
|
||||
break;
|
||||
case TemplateConstants.Description:
|
||||
Description = mappingPair.Value.AssertString($"{TemplateConstants.MappingPropertyValue} {TemplateConstants.Description}").Value;
|
||||
break;
|
||||
default:
|
||||
mappingKey.AssertUnexpectedValue($"{TemplateConstants.MappingPropertyValue} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal String Type { get; set; }
|
||||
|
||||
internal Boolean Required { get; set; }
|
||||
|
||||
internal String Description { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.Actions.WorkflowParser.ObjectTemplating.Schema
|
||||
{
|
||||
internal abstract class ScalarDefinition : Definition
|
||||
{
|
||||
internal ScalarDefinition()
|
||||
{
|
||||
}
|
||||
|
||||
internal ScalarDefinition(MappingToken definition)
|
||||
: base(definition)
|
||||
{
|
||||
}
|
||||
|
||||
internal abstract Boolean IsMatch(LiteralToken literal);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#nullable disable // Consider removing in the future to minimize likelihood of NullReferenceException; refer https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references
|
||||
|
||||
using System;
|
||||
using GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.Actions.WorkflowParser.ObjectTemplating.Schema
|
||||
{
|
||||
internal sealed class SequenceDefinition : Definition
|
||||
{
|
||||
internal SequenceDefinition()
|
||||
{
|
||||
}
|
||||
|
||||
internal SequenceDefinition(MappingToken definition)
|
||||
: base(definition)
|
||||
{
|
||||
foreach (var definitionPair in definition)
|
||||
{
|
||||
var definitionKey = definitionPair.Key.AssertString($"{TemplateConstants.Definition} key");
|
||||
|
||||
switch (definitionKey.Value)
|
||||
{
|
||||
case TemplateConstants.Sequence:
|
||||
var mapping = definitionPair.Value.AssertMapping($"{TemplateConstants.Definition} {TemplateConstants.Sequence}");
|
||||
foreach (var mappingPair in mapping)
|
||||
{
|
||||
var mappingKey = mappingPair.Key.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Sequence} key");
|
||||
switch (mappingKey.Value)
|
||||
{
|
||||
case TemplateConstants.ItemType:
|
||||
var itemType = mappingPair.Value.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Sequence} {TemplateConstants.ItemType}");
|
||||
ItemType = itemType.Value;
|
||||
break;
|
||||
|
||||
default:
|
||||
mappingKey.AssertUnexpectedValue($"{TemplateConstants.Definition} {TemplateConstants.Sequence} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TemplateConstants.CoerceRaw:
|
||||
continue;
|
||||
|
||||
default:
|
||||
definitionKey.AssertUnexpectedValue($"{TemplateConstants.Definition} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal override DefinitionType DefinitionType => DefinitionType.Sequence;
|
||||
|
||||
internal String ItemType { get; set; }
|
||||
|
||||
internal override void Validate(
|
||||
TemplateSchema schema,
|
||||
String name)
|
||||
{
|
||||
if (String.IsNullOrEmpty(ItemType))
|
||||
{
|
||||
throw new ArgumentException($"'{name}' does not define '{TemplateConstants.ItemType}'");
|
||||
}
|
||||
|
||||
// Lookup item type
|
||||
schema.GetDefinition(ItemType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
#nullable disable // Consider removing in the future to minimize likelihood of NullReferenceException; refer https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references
|
||||
|
||||
using System;
|
||||
using GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.Actions.WorkflowParser.ObjectTemplating.Schema
|
||||
{
|
||||
internal sealed class StringDefinition : ScalarDefinition
|
||||
{
|
||||
internal StringDefinition()
|
||||
{
|
||||
}
|
||||
|
||||
internal StringDefinition(MappingToken definition)
|
||||
: base(definition)
|
||||
{
|
||||
foreach (var definitionPair in definition)
|
||||
{
|
||||
var definitionKey = definitionPair.Key.AssertString($"{TemplateConstants.Definition} key");
|
||||
switch (definitionKey.Value)
|
||||
{
|
||||
case TemplateConstants.String:
|
||||
var mapping = definitionPair.Value.AssertMapping($"{TemplateConstants.Definition} {TemplateConstants.String}");
|
||||
foreach (var mappingPair in mapping)
|
||||
{
|
||||
var mappingKey = mappingPair.Key.AssertString($"{TemplateConstants.Definition} {TemplateConstants.String} key");
|
||||
switch (mappingKey.Value)
|
||||
{
|
||||
case TemplateConstants.Constant:
|
||||
var constantStringToken = mappingPair.Value.AssertString($"{TemplateConstants.Definition} {TemplateConstants.String} {TemplateConstants.Constant}");
|
||||
Constant = constantStringToken.Value;
|
||||
break;
|
||||
|
||||
case TemplateConstants.IgnoreCase:
|
||||
var ignoreCaseBooleanToken = mappingPair.Value.AssertBoolean($"{TemplateConstants.Definition} {TemplateConstants.String} {TemplateConstants.IgnoreCase}");
|
||||
IgnoreCase = ignoreCaseBooleanToken.Value;
|
||||
break;
|
||||
|
||||
case TemplateConstants.RequireNonEmpty:
|
||||
var requireNonEmptyBooleanToken = mappingPair.Value.AssertBoolean($"{TemplateConstants.Definition} {TemplateConstants.String} {TemplateConstants.RequireNonEmpty}");
|
||||
RequireNonEmpty = requireNonEmptyBooleanToken.Value;
|
||||
break;
|
||||
|
||||
case TemplateConstants.IsExpression:
|
||||
var isExpressionBooleanToken = mappingPair.Value.AssertBoolean($"{TemplateConstants.Definition} {TemplateConstants.String} {TemplateConstants.IsExpression}");
|
||||
IsExpression = isExpressionBooleanToken.Value;
|
||||
break;
|
||||
|
||||
default:
|
||||
mappingKey.AssertUnexpectedValue($"{TemplateConstants.Definition} {TemplateConstants.String} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TemplateConstants.CoerceRaw:
|
||||
continue;
|
||||
|
||||
default:
|
||||
definitionKey.AssertUnexpectedValue($"{TemplateConstants.Definition} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal override DefinitionType DefinitionType => DefinitionType.String;
|
||||
|
||||
internal String Constant { get; set; }
|
||||
|
||||
internal Boolean IgnoreCase { get; set; }
|
||||
|
||||
internal Boolean RequireNonEmpty { get; set; }
|
||||
|
||||
internal Boolean IsExpression { get; set; }
|
||||
|
||||
internal override Boolean IsMatch(LiteralToken literal)
|
||||
{
|
||||
if (literal is StringToken str)
|
||||
{
|
||||
var value = str.Value;
|
||||
if (!String.IsNullOrEmpty(Constant))
|
||||
{
|
||||
var comparison = IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
|
||||
|
||||
if (String.Equals(Constant, value, comparison))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (RequireNonEmpty)
|
||||
{
|
||||
if (!String.IsNullOrEmpty(value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override void Validate(
|
||||
TemplateSchema schema,
|
||||
String name)
|
||||
{
|
||||
if (!String.IsNullOrEmpty(Constant) && RequireNonEmpty)
|
||||
{
|
||||
throw new ArgumentException($"Properties '{TemplateConstants.Constant}' and '{TemplateConstants.RequireNonEmpty}' cannot both be set");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
#nullable disable // Consider removing in the future to minimize likelihood of NullReferenceException; refer https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.Actions.WorkflowParser.ObjectTemplating.Schema
|
||||
{
|
||||
/// <summary>
|
||||
/// This models the root schema object and contains definitions
|
||||
/// </summary>
|
||||
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.AllowedValues:
|
||||
// Ignore allowed-values in CSharp parser, we don't need to support events here
|
||||
definition = new NullDefinition(definitionsValue);
|
||||
break;
|
||||
|
||||
case TemplateConstants.CoerceRaw:
|
||||
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)
|
||||
{
|
||||
return definition.Properties.Count > 0;
|
||||
}
|
||||
|
||||
internal Boolean TryGetProperty(
|
||||
MappingDefinition definition,
|
||||
String name,
|
||||
out String type)
|
||||
{
|
||||
if (definition.Properties.TryGetValue(name, out PropertyDefinition property))
|
||||
{
|
||||
type = property.Type;
|
||||
return true;
|
||||
}
|
||||
|
||||
type = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
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 PropertyDefinition(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Definitions, new PropertyDefinition(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 PropertyDefinition(new StringToken(null, null, null, TemplateConstants.String)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.CoerceRaw, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.Boolean)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Null, new PropertyDefinition(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 PropertyDefinition(new StringToken(null, null, null, TemplateConstants.String)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.CoerceRaw, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.Boolean)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Boolean, new PropertyDefinition(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 PropertyDefinition(new StringToken(null, null, null, TemplateConstants.String)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.CoerceRaw, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.Boolean)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Number, new PropertyDefinition(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 PropertyDefinition(new StringToken(null, null, null, TemplateConstants.String)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.CoerceRaw, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.Boolean)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.String, new PropertyDefinition(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 PropertyDefinition(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.IgnoreCase, new PropertyDefinition(new StringToken(null, null, null,TemplateConstants.Boolean)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.RequireNonEmpty, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.Boolean)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.IsExpression, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.Boolean)));
|
||||
schema.Definitions.Add(TemplateConstants.StringDefinitionProperties, mappingDefinition);
|
||||
|
||||
// sequence-definition
|
||||
mappingDefinition = new MappingDefinition();
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.String)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.CoerceRaw, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.Boolean)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Sequence, new PropertyDefinition(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 PropertyDefinition(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
|
||||
schema.Definitions.Add(TemplateConstants.SequenceDefinitionProperties, mappingDefinition);
|
||||
|
||||
// mapping-definition
|
||||
mappingDefinition = new MappingDefinition();
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.String)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.CoerceRaw, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.Boolean)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Mapping, new PropertyDefinition(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 PropertyDefinition(new StringToken(null, null, null, TemplateConstants.Properties)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.LooseKeyType, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.LooseValueType, new PropertyDefinition(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 PropertyDefinition(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Required, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.Boolean)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.String)));
|
||||
schema.Definitions.Add(TemplateConstants.MappingPropertyValue, mappingDefinition);
|
||||
|
||||
// one-of-definition
|
||||
mappingDefinition = new MappingDefinition();
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.String)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.CoerceRaw, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.Boolean)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.OneOf, new PropertyDefinition(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.AllowedValues, new PropertyDefinition(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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user