Skip to content

Commit

Permalink
Refactor schema gen to generically create discriminated subschemas (#704
Browse files Browse the repository at this point in the history
)
  • Loading branch information
jander-msft authored Aug 10, 2021
1 parent 5b9d43a commit 76c1467
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 99 deletions.
99 changes: 46 additions & 53 deletions documentation/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -716,15 +716,14 @@
]
},
"JsonConsoleFormatterOptions": {
"title": "JsonConsoleFormatterOptions",
"type": "object",
"additionalProperties": false,
"properties": {
"JsonWriterOptions": {
"description": "Gets or sets JsonWriterOptions.",
"oneOf": [
{
"$ref": "#/definitions/JsonConsoleFormatterOptions/definitions/JsonWriterOptions"
"$ref": "#/definitions/JsonWriterOptions"
}
]
},
Expand All @@ -744,59 +743,56 @@
"description": "Gets or sets whether or not UTC timezone should be used for timestamps in logging messages. Defaults to false.",
"default": false
}
},
"definitions": {
"JsonWriterOptions": {
"type": "object",
"additionalProperties": false,
"properties": {
"Encoder": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/definitions/JsonConsoleFormatterOptions/definitions/JavaScriptEncoder"
}
]
},
"Indented": {
"type": "boolean"
},
"SkipValidation": {
"type": "boolean"
}
}
},
"JavaScriptEncoder": {
"allOf": [
}
},
"JsonWriterOptions": {
"type": "object",
"additionalProperties": false,
"properties": {
"Encoder": {
"oneOf": [
{
"$ref": "#/definitions/JsonConsoleFormatterOptions/definitions/TextEncoder"
"type": "null"
},
{
"type": "object",
"x-abstract": true,
"additionalProperties": false
"$ref": "#/definitions/JavaScriptEncoder"
}
]
},
"TextEncoder": {
"Indented": {
"type": "boolean"
},
"SkipValidation": {
"type": "boolean"
}
}
},
"JavaScriptEncoder": {
"allOf": [
{
"$ref": "#/definitions/TextEncoder"
},
{
"type": "object",
"x-abstract": true,
"additionalProperties": false
}
}
]
},
"TextEncoder": {
"type": "object",
"x-abstract": true,
"additionalProperties": false
},
"SimpleConsoleFormatterOptions": {
"title": "SimpleConsoleFormatterOptions",
"type": "object",
"additionalProperties": false,
"properties": {
"ColorBehavior": {
"description": "Determines when to use color when logging messages.",
"oneOf": [
{
"$ref": "#/definitions/SimpleConsoleFormatterOptions/definitions/LoggerColorBehavior"
"$ref": "#/definitions/LoggerColorBehavior"
}
]
},
Expand All @@ -820,26 +816,23 @@
"description": "Gets or sets whether or not UTC timezone should be used for timestamps in logging messages. Defaults to false.",
"default": false
}
},
"definitions": {
"LoggerColorBehavior": {
"type": "string",
"description": "",
"x-enumNames": [
"Default",
"Enabled",
"Disabled"
],
"enum": [
"Default",
"Enabled",
"Disabled"
]
}
}
},
"LoggerColorBehavior": {
"type": "string",
"description": "",
"x-enumNames": [
"Default",
"Enabled",
"Disabled"
],
"enum": [
"Default",
"Enabled",
"Disabled"
]
},
"ConsoleFormatterOptions": {
"title": "ConsoleFormatterOptions",
"type": "object",
"additionalProperties": false,
"properties": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,18 @@ internal sealed class SchemaGenerator
{
public string GenerateSchema()
{
var settings = new JsonSchemaGeneratorSettings();
var schema = new JsonSchema();
var context = new GenerationContext(schema);
context.SetRoot<RootOptions>();

settings.SerializerSettings = new JsonSerializerSettings();
settings.SerializerSettings.Converters.Add(new StringEnumConverter());

JsonSchema schema = JsonSchema.FromType<RootOptions>(settings);
schema.Id = @"https://www.github.com/dotnet/dotnet-monitor";
schema.Title = "DotnetMonitorConfiguration";

JsonSchema jsonConsoleFormatterOptions = JsonSchema.FromType<JsonConsoleFormatterOptions>();
schema.Definitions.Add(nameof(JsonConsoleFormatterOptions), jsonConsoleFormatterOptions);

JsonSchema simpleConsoleFormatterOptions = JsonSchema.FromType<SimpleConsoleFormatterOptions>();
schema.Definitions.Add(nameof(SimpleConsoleFormatterOptions), simpleConsoleFormatterOptions);

JsonSchema systemdConsoleFormatterOptions = JsonSchema.FromType<ConsoleFormatterOptions>();
schema.Definitions.Add(nameof(ConsoleFormatterOptions), systemdConsoleFormatterOptions);

//Allow other properties in the schema.
schema.AdditionalPropertiesSchema = JsonSchema.CreateAnySchema();

AddConsoleLoggerFormatterSubSchemas(context);

//TODO Figure out a better way to add object defaults
schema.Definitions[nameof(EgressOptions)].Properties[nameof(EgressOptions.AzureBlobStorage)].Default = JsonSchema.CreateAnySchema();
schema.Definitions[nameof(EgressOptions)].Properties[nameof(EgressOptions.FileSystem)].Default = JsonSchema.CreateAnySchema();
Expand All @@ -57,64 +48,111 @@ public string GenerateSchema()
kvp.Value.Default = JsonSchema.CreateAnySchema();
}

JsonSchema jsonConsoleLoggerOptionsSchema = GenerateConsoleLoggerOptionsSchema(jsonConsoleFormatterOptions, ConsoleLoggerFormat.Json);
JsonSchema simpleConsoleLoggerOptionsSchema = GenerateConsoleLoggerOptionsSchema(simpleConsoleFormatterOptions, ConsoleLoggerFormat.Simple);
JsonSchema systemdConsoleLoggerOptionsSchema = GenerateConsoleLoggerOptionsSchema(systemdConsoleFormatterOptions, ConsoleLoggerFormat.Systemd);
JsonSchema defaultConsoleLoggerOptionsSchema = GenerateDefaultConsoleLoggerOptionsSchema(simpleConsoleFormatterOptions);

schema.Definitions[nameof(ConsoleLoggerOptions)].OneOf.Add(jsonConsoleLoggerOptionsSchema);
schema.Definitions[nameof(ConsoleLoggerOptions)].OneOf.Add(simpleConsoleLoggerOptionsSchema);
schema.Definitions[nameof(ConsoleLoggerOptions)].OneOf.Add(systemdConsoleLoggerOptionsSchema);
schema.Definitions[nameof(ConsoleLoggerOptions)].OneOf.Add(defaultConsoleLoggerOptionsSchema);

string schemaPayload = schema.ToJson();

//Normalize newlines embedded into json
schemaPayload = schemaPayload.Replace(@"\r\n", @"\n", StringComparison.Ordinal);
return schemaPayload;
}

public static JsonSchema GenerateConsoleLoggerOptionsSchema(JsonSchema consoleFormatterOptions, ConsoleLoggerFormat consoleLoggerFormat)
private static void AddConsoleLoggerFormatterSubSchemas(GenerationContext context)
{
JsonSchema consoleLoggerOptionsSchema = new JsonSchema();

JsonSchemaProperty formatterNameProperty = new JsonSchemaProperty();
JsonSchemaProperty formatterOptionsProperty = new JsonSchemaProperty();

JsonSchema formatterOptionsSchema = new JsonSchema();
formatterOptionsSchema.Reference = consoleFormatterOptions;

formatterOptionsProperty.Reference = formatterOptionsSchema;

formatterNameProperty.ExtensionData = new Dictionary<string, object>();
formatterNameProperty.ExtensionData.Add("const", consoleLoggerFormat.ToString());
AddConsoleLoggerOptionsSubSchema<JsonConsoleFormatterOptions>(context, ConsoleLoggerFormat.Json);
AddConsoleLoggerOptionsSubSchema<SimpleConsoleFormatterOptions>(context, ConsoleLoggerFormat.Simple);
AddConsoleLoggerOptionsSubSchema<ConsoleFormatterOptions>(context, ConsoleLoggerFormat.Systemd);
AddDefaultConsoleLoggerOptionsSubSchema(context);
}

consoleLoggerOptionsSchema.Properties.Add(nameof(ConsoleLoggerOptions.FormatterName), formatterNameProperty);
consoleLoggerOptionsSchema.Properties.Add(nameof(ConsoleLoggerOptions.FormatterOptions), formatterOptionsProperty);
private static void AddConsoleLoggerOptionsSubSchema<TOptions>(GenerationContext context, ConsoleLoggerFormat consoleLoggerFormat)
{
JsonSchema consoleLoggerOptionsSchema = new JsonSchema();
consoleLoggerOptionsSchema.RequiredProperties.Add(nameof(ConsoleLoggerOptions.FormatterName));

return consoleLoggerOptionsSchema;
JsonSchemaProperty formatterOptionsProperty = AddDiscriminatedSubSchema(
context.Schema.Definitions[nameof(ConsoleLoggerOptions)],
nameof(ConsoleLoggerOptions.FormatterName),
consoleLoggerFormat.ToString(),
nameof(ConsoleLoggerOptions.FormatterOptions),
consoleLoggerOptionsSchema);

formatterOptionsProperty.Reference = context.AddTypeIfNotExist<TOptions>();
}

public static JsonSchema GenerateDefaultConsoleLoggerOptionsSchema(JsonSchema consoleFormatterOptions)
private static void AddDefaultConsoleLoggerOptionsSubSchema(GenerationContext context)
{
JsonSchema consoleLoggerOptionsSchema = new JsonSchema();

JsonSchemaProperty formatterNameProperty = new JsonSchemaProperty();
JsonSchemaProperty formatterOptionsProperty = new JsonSchemaProperty();

JsonSchema formatterOptionsSchema = new JsonSchema();
formatterOptionsSchema.Reference = consoleFormatterOptions;

formatterOptionsProperty.Reference = formatterOptionsSchema;
formatterOptionsProperty.Reference = context.AddTypeIfNotExist<SimpleConsoleFormatterOptions>();

formatterNameProperty.Type = JsonObjectType.Null;
formatterNameProperty.Default = "Simple";

consoleLoggerOptionsSchema.Properties.Add(nameof(ConsoleLoggerOptions.FormatterName), formatterNameProperty);
consoleLoggerOptionsSchema.Properties.Add(nameof(ConsoleLoggerOptions.FormatterOptions), formatterOptionsProperty);

return consoleLoggerOptionsSchema;
context.Schema.Definitions[nameof(ConsoleLoggerOptions)].OneOf.Add(consoleLoggerOptionsSchema);
}

private static JsonSchemaProperty AddDiscriminatedSubSchema(
JsonSchema parentSchema,
string discriminatingPropertyName,
string discriminatingPropertyValue,
string discriminatedPropertyName,
JsonSchema subSchema = null)
{
if (null == subSchema)
{
subSchema = new JsonSchema();
}

JsonSchemaProperty discriminatingProperty = new JsonSchemaProperty();
discriminatingProperty.ExtensionData = new Dictionary<string, object>();
discriminatingProperty.ExtensionData.Add("const", discriminatingPropertyValue);

subSchema.Properties.Add(discriminatingPropertyName, discriminatingProperty);

JsonSchemaProperty discriminatedProperty = new JsonSchemaProperty();

subSchema.Properties.Add(discriminatedPropertyName, discriminatedProperty);

parentSchema.OneOf.Add(subSchema);

return discriminatedProperty;
}

private class GenerationContext
{
private readonly JsonSchemaGenerator _generator;
private readonly JsonSchemaResolver _resolver;
private readonly JsonSchemaGeneratorSettings _settings;

public GenerationContext(JsonSchema rootSchema)
{
Schema = rootSchema;

_settings = new JsonSchemaGeneratorSettings();
_settings.SerializerSettings = new JsonSerializerSettings();
_settings.SerializerSettings.Converters.Add(new StringEnumConverter());

_resolver = new JsonSchemaResolver(rootSchema, _settings);

_generator = new JsonSchemaGenerator(_settings);
}

public JsonSchema AddTypeIfNotExist<T>()
{
return _generator.Generate(typeof(T), _resolver);
}

public void SetRoot<T>()
{
_generator.Generate(Schema, typeof(T), _resolver);
}

public JsonSchema Schema { get; }
}
}
}

0 comments on commit 76c1467

Please sign in to comment.