Skip to content

Instantly share code, notes, and snippets.

@shadow-cs
Created September 30, 2020 07:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shadow-cs/24576043ac49421910ae7cdbabf3e8b3 to your computer and use it in GitHub Desktop.
Save shadow-cs/24576043ac49421910ae7cdbabf3e8b3 to your computer and use it in GitHub Desktop.
Loading external $ref schemas using json-everything
// 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&lt;T&gt;
/// 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&lt;T&gt; 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