Created
September 30, 2020 07:13
-
-
Save shadow-cs/24576043ac49421910ae7cdbabf3e8b3 to your computer and use it in GitHub Desktop.
Loading external $ref schemas using json-everything
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// See https://github.com/gregsdennis/json-everything/issues/8 | |
public class JsonSchemaLoader | |
{ | |
private static JsonSchema Load(string schemaFile, ValidationOptions options) | |
{ | |
var schemaFullPath = Path.GetFullPath(schemaFile); | |
var schemaUri = new Uri(schemaFullPath); | |
var json = File.ReadAllText(schemaFile); | |
return Load(schemaUri, json, options); | |
} | |
private static JsonSchema Load(Uri schemaUri, string json, ValidationOptions options) | |
{ | |
var jToken = JToken.Parse(json); | |
jToken["$id"] = schemaUri.AbsoluteUri; | |
json = jToken.ToString(Newtonsoft.Json.Formatting.None); | |
var schema = JsonSchema.FromText(json); | |
if (options.SchemaRegistry.Get(schemaUri) is null) | |
{ | |
// Register first to prevent stack overflow if cyclic schemas | |
// are loaded. | |
options.SchemaRegistry.Register(schemaUri, schema); | |
LoadReferences(schema, options, schemaUri); | |
} | |
return schema; | |
} | |
private static void LoadReferences(JsonSchema schema, ValidationOptions options, Uri currentUri) | |
{ | |
void LoadReferencedSchema(Uri uri) | |
{ | |
if (!uri.IsAbsoluteUri) | |
{ | |
if (uri.OriginalString.StartsWith('#')) | |
{ | |
// Reference within current document. | |
return; | |
} | |
var uriFolder = currentUri.OriginalString.EndsWith("/", StringComparison.Ordinal) | |
? currentUri | |
: currentUri.GetParentUri(); | |
uri = new Uri(uriFolder, uri); | |
} | |
string json; | |
switch (uri.Scheme) | |
{ | |
case "http": | |
case "https": | |
json = new HttpClient().GetStringAsync(uri).Result; | |
Load(uri, json, options); | |
break; | |
case "file": | |
var filename = Uri.UnescapeDataString(uri.AbsolutePath); | |
Load(filename, options); | |
break; | |
default: | |
throw new Exception($"URI scheme '{uri.Scheme}' is not supported. Only HTTP(S) and local file system URIs are allowed."); | |
} | |
} | |
if (schema.Keywords is null) | |
{ | |
return; | |
} | |
foreach (var keyword in schema.Keywords) | |
{ | |
switch (keyword) | |
{ | |
case RecursiveRefKeyword recursiveRefKeyword: | |
LoadReferencedSchema(recursiveRefKeyword.Reference); | |
break; | |
case RefKeyword refKeyword: | |
LoadReferencedSchema(refKeyword.Reference); | |
break; | |
default: | |
foreach (var subSchema in keyword.GetSubSchemas()) | |
{ | |
LoadReferences(subSchema, options, currentUri); | |
} | |
break; | |
} | |
} | |
} | |
} | |
public static class JsonSchemaKeywordExtensions | |
{ | |
public static IEnumerable<JsonSchema> GetSubSchemas(this IJsonSchemaKeyword keyword) | |
{ | |
return keyword switch | |
{ | |
AdditionalItemsKeyword additionalItemsKeyword => additionalItemsKeyword.Schema.Yield(), | |
AdditionalPropertiesKeyword additionalPropertiesKeyword => additionalPropertiesKeyword.Schema.Yield(), | |
AllOfKeyword allOfKeyword => allOfKeyword.Schemas, | |
AnyOfKeyword anyOfKeyword => anyOfKeyword.Schemas, | |
ContainsKeyword containsKeyword => containsKeyword.Schema.Yield(), | |
ContentSchemaKeyword contentSchemaKeyword => contentSchemaKeyword.Schema.Yield(), | |
DefinitionsKeyword definitionsKeyword => definitionsKeyword.Definitions.Values, | |
DefsKeyword defsKeyword => defsKeyword.Definitions.Values, | |
DependenciesKeyword dependenciesKeyword => dependenciesKeyword.Requirements.Values.Where(r => r.Schema is { }).Select(r => r.Schema), | |
DependentSchemasKeyword dependentSchemasKeyword => dependentSchemasKeyword.Schemas.Values, | |
ElseKeyword elseKeyword => elseKeyword.Schema.Yield(), | |
IfKeyword ifKeyword => ifKeyword.Schema.Yield(), | |
ItemsKeyword itemsKeyword => itemsKeyword.SingleSchema?.Yield() ?? itemsKeyword.ArraySchemas, | |
NotKeyword notKeyword => notKeyword.Schema.Yield(), | |
OneOfKeyword oneOfKeyword => oneOfKeyword.Schemas, | |
PatternPropertiesKeyword patternPropertiesKeyword => patternPropertiesKeyword.Patterns.Values, | |
PropertiesKeyword propertiesKeyword => propertiesKeyword.Properties.Values, | |
PropertyNamesKeyword propertyNamesKeyword => propertyNamesKeyword.Schema.Yield(), | |
ThenKeyword thenKeyword => thenKeyword.Schema.Yield(), | |
UnevaluatedItemsKeyword unevaluatedItemsKeyword => unevaluatedItemsKeyword.Schema.Yield(), | |
UnevaluatedPropertiesKeyword unevaluatedPropertiesKeyword => unevaluatedPropertiesKeyword.Schema.Yield(), | |
_ => Enumerable.Empty<JsonSchema>(), | |
}; | |
} | |
} | |
// See https://stackoverflow.com/q/1577822/623392 | |
public static class IEnumerableExtensions | |
{ | |
/// <summary> | |
/// Wraps this object instance into an IEnumerable<T> | |
/// consisting of a single item. | |
/// </summary> | |
/// <typeparam name="T"> Type of the object. </typeparam> | |
/// <param name="item"> The instance that will be wrapped. </param> | |
/// <returns> An IEnumerable<T> consisting of a single item. </returns> | |
public static IEnumerable<T> Yield<T>(this T item) | |
{ | |
yield return item; | |
} | |
} | |
internal static class UriExtensions | |
{ | |
/// <summary> | |
/// Gets the Uri to the parent object. | |
/// </summary> | |
/// <param name="uri">The <see cref="Uri" /> of a resource, for which the parent Uri should be retrieved.</param> | |
/// <returns> | |
/// The parent <see cref="Uri" />. | |
/// </returns> | |
/// <exception cref="ArgumentNullException"><paramref name="uri" /> is <c>null</c>.</exception> | |
/// <exception cref="InvalidOperationException"><paramref name="uri" /> has no parent, it refers to a root resource.</exception> | |
public static Uri GetParentUri(this Uri uri) | |
{ | |
if (uri == null) | |
{ | |
throw new ArgumentNullException(nameof(uri)); | |
} | |
if (uri.IsAbsoluteUri && uri.Segments.Length == 1) | |
{ | |
throw new InvalidOperationException("Cannot get parent of root"); | |
} | |
var path = uri.AbsoluteUri.Remove(uri.AbsoluteUri.Length - uri.Segments.Last().Length); | |
return new Uri(path); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment