1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
|
public KeywordConstraint GetConstraint(SchemaConstraint schemaConstraint, IReadOnlyList<KeywordConstraint> localConstraints, EvaluationContext context)
{
var newUri = new Uri(context.Scope.LocalScope, Reference);
var newBaseUri = new Uri(newUri.GetLeftPart(UriPartial.Query));
var anchorName = Reference.OriginalString.Split('#').Last();
JsonSchema? targetSchema = null;
var targetBase = context.Options.SchemaRegistry.Get(newBaseUri) ??
throw new JsonSchemaException($"Cannot resolve base schema from `{newUri}`");
foreach (var uri in context.Scope.Reverse())
{
var scopeRoot = context.Options.SchemaRegistry.Get(uri);
if (scopeRoot == null)
throw new Exception("This shouldn't happen");
if (scopeRoot is not JsonSchema schemaRoot)
throw new Exception("Does OpenAPI use anchors?");
if (!schemaRoot.Anchors.TryGetValue(anchorName, out var anchor) || !anchor.IsDynamic) continue;
if (targetBase is JsonSchema targetBaseSchema &&
context.EvaluatingAs == SpecVersion.Draft202012 &&
(!targetBaseSchema.Anchors.TryGetValue(anchorName, out var targetAnchor) || !targetAnchor.IsDynamic)) break;
targetSchema = anchor.Schema;
break;
}
if (targetSchema == null)
{
if (JsonPointer.TryParse(newUri.Fragment, out var pointerFragment))
{
if (targetBase == null)
throw new JsonSchemaException($"Cannot resolve base schema from `{newUri}`");
targetSchema = targetBase.FindSubschema(pointerFragment!, context.Options);
}
else
{
var anchorFragment = newUri.Fragment.Substring(1);
if (!AnchorKeyword.AnchorPattern.IsMatch(anchorFragment))
throw new JsonSchemaException($"Unrecognized fragment type `{newUri}`");
if (targetBase is JsonSchema targetBaseSchema &&
targetBaseSchema.Anchors.TryGetValue(anchorFragment, out var anchorDefinition))
targetSchema = anchorDefinition.Schema;
}
if (targetSchema == null)
throw new JsonSchemaException($"Cannot resolve schema `{newUri}`");
}
return new KeywordConstraint(Name, (e, c) => Evaluator(e, c, targetSchema));
}
Anchors
public EvaluationResults Evaluate(JsonNode? root, EvaluationOptions? options = null)
{
options = EvaluationOptions.From(options ?? EvaluationOptions.Default);
// BaseUri may change if $id is present
// TODO: remove options.EvaluatingAs
var evaluatingAs = DetermineSpecVersion(this, options.SchemaRegistry, options.EvaluateAs);
PopulateBaseUris(this, this, BaseUri, options.SchemaRegistry, evaluatingAs, true);
var context = new EvaluationContext(options, evaluatingAs, BaseUri);
var constraint = BuildConstraint(JsonPointer.Empty, JsonPointer.Empty, JsonPointer.Empty, context.Scope);
if (!BoolValue.HasValue)
PopulateConstraint(constraint, context);
var evaluation = constraint.BuildEvaluation(root, JsonPointer.Empty, JsonPointer.Empty, options);
evaluation.Evaluate(context);
if (options.AddAnnotationForUnknownKeywords && constraint.UnknownKeywords != null)
evaluation.Results.SetAnnotation(_unknownKeywordsAnnotationKey, constraint.UnknownKeywords);
var results = evaluation.Results;
switch (options.OutputFormat)
{
case OutputFormat.Flag:
results.ToFlag();
break;
case OutputFormat.List:
results.ToList();
break;
case OutputFormat.Hierarchical:
break;
default:
throw new ArgumentOutOfRangeException();
}
return results;
}
private static void PopulateBaseUris(JsonSchema schema, JsonSchema resourceRoot, Uri currentBaseUri, SchemaRegistry registry, SpecVersion evaluatingAs = SpecVersion.Unspecified, bool selfRegister = false)
{
if (schema.BoolValue.HasValue) return;
evaluatingAs = DetermineSpecVersion(schema, registry, evaluatingAs);
if (evaluatingAs is SpecVersion.Draft6 or SpecVersion.Draft7 &&
schema.TryGetKeyword<RefKeyword>(RefKeyword.Name, out _))
{
schema.BaseUri = currentBaseUri;
if (selfRegister)
registry.RegisterSchema(schema.BaseUri, schema);
}
else
{
var idKeyword = (IIdKeyword?)schema.Keywords!.FirstOrDefault(x => x is IIdKeyword);
if (idKeyword != null)
{
if (evaluatingAs <= SpecVersion.Draft7 &&
idKeyword.Id.OriginalString[0] == '#' &&
AnchorKeyword.AnchorPattern.IsMatch(idKeyword.Id.OriginalString.Substring(1)))
{
schema.BaseUri = currentBaseUri;
resourceRoot.Anchors[idKeyword.Id.OriginalString.Substring(1)] = (schema, false);
}
else
{
schema.IsResourceRoot = true;
schema.DeclaredVersion = evaluatingAs;
resourceRoot = schema;
schema.BaseUri = new Uri(currentBaseUri, idKeyword.Id);
registry.RegisterSchema(schema.BaseUri, schema);
}
}
else
{
schema.BaseUri = currentBaseUri;
if (selfRegister)
registry.RegisterSchema(schema.BaseUri, schema);
}
if (schema.TryGetKeyword<AnchorKeyword>(AnchorKeyword.Name, out var anchorKeyword))
{
resourceRoot.Anchors[anchorKeyword!.Anchor] = (schema, false);
}
if (schema.TryGetKeyword<DynamicAnchorKeyword>(DynamicAnchorKeyword.Name, out var dynamicAnchorKeyword))
{
resourceRoot.Anchors[dynamicAnchorKeyword!.Value] = (schema, true);
}
schema.TryGetKeyword<RecursiveAnchorKeyword>(RecursiveAnchorKeyword.Name, out var recursiveAnchorKeyword);
if (recursiveAnchorKeyword is { Value: true })
resourceRoot.RecursiveAnchor = schema;
}
var subschemas = schema.Keywords!.SelectMany(GetSubschemas);
foreach (var subschema in subschemas)
{
PopulateBaseUris(subschema, resourceRoot, schema.BaseUri, registry, evaluatingAs);
}
}
|