Preparation fo Mystery rolling
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
namespace ALttPRandomizer.Serialization {
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using ALttPRandomizer.Model;
|
||||
|
||||
public class RandomizableWeightsJsonConverter : JsonConverterFactory {
|
||||
public override bool CanConvert(Type typeToConvert) {
|
||||
if (!typeToConvert.IsGenericType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeToConvert.GetGenericTypeDefinition() != typeof(RandomizableWeights<>)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) {
|
||||
Type itemType = typeToConvert.GetGenericArguments()[0];
|
||||
Type innerType = typeof(RandomizableWeightsConverterInner<>).MakeGenericType(itemType);
|
||||
|
||||
return (JsonConverter) Activator.CreateInstance(innerType, args: options)!;
|
||||
}
|
||||
|
||||
private class RandomizableWeightsConverterInner<T> : JsonConverter<RandomizableWeights<T>> where T : struct, Enum {
|
||||
private readonly JsonConverter<IDictionary<T, int>> dictionaryConverter;
|
||||
private readonly JsonConverter<T> valueConverter;
|
||||
private readonly Type valueType;
|
||||
private readonly Type dictionaryType;
|
||||
|
||||
public RandomizableWeightsConverterInner(JsonSerializerOptions options) {
|
||||
dictionaryConverter = (JsonConverter<IDictionary<T, int>>) options.GetConverter(typeof(IDictionary<T, int>));
|
||||
valueConverter = (JsonConverter<T>) options.GetConverter(typeof(T));
|
||||
|
||||
this.valueType = typeof(T);
|
||||
this.dictionaryType = typeof(IDictionary<T, int>);
|
||||
}
|
||||
|
||||
public override RandomizableWeights<T>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
||||
switch (reader.TokenType) {
|
||||
case JsonTokenType.Null:
|
||||
return RandomizableWeights<T>.EmptyWeights;
|
||||
case JsonTokenType.String:
|
||||
case JsonTokenType.Number:
|
||||
var value = valueConverter.Read(ref reader, this.valueType, options);
|
||||
return RandomizableWeights<T>.ConstantWeights(value);
|
||||
case JsonTokenType.StartObject:
|
||||
var dict = dictionaryConverter.Read(ref reader, this.dictionaryType, options);
|
||||
if (dict == null) {
|
||||
return RandomizableWeights<T>.EmptyWeights;
|
||||
} else {
|
||||
return RandomizableWeights<T>.FromDictionary(dict);
|
||||
}
|
||||
default:
|
||||
throw new JsonException();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, RandomizableWeights<T> value, JsonSerializerOptions options) {
|
||||
switch (value.Options.Count) {
|
||||
case 0:
|
||||
writer.WriteNullValue();
|
||||
break;
|
||||
case 1:
|
||||
this.valueConverter.Write(writer, value.Options[0].Item, options);
|
||||
break;
|
||||
default:
|
||||
writer.WriteStartObject();
|
||||
foreach (var (item, weight) in value.Options) {
|
||||
this.valueConverter.WriteAsPropertyName(writer, item, options);
|
||||
writer.WriteNumberValue(weight);
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
namespace ALttPRandomizer.Serialization {
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ALttPRandomizer.Model;
|
||||
using YamlDotNet.Core;
|
||||
using YamlDotNet.Core.Events;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
public class RandomizableWeightsYamlConverters : IYamlTypeConverter {
|
||||
private readonly Dictionary<Type, IYamlTypeConverter> converters = new();
|
||||
|
||||
public bool Accepts(Type type) {
|
||||
if (this.converters.ContainsKey(type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!type.IsGenericType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type.GetGenericTypeDefinition() != typeof(RandomizableWeights<>)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Type itemType = type.GetGenericArguments()[0];
|
||||
Type innerType = typeof(RandomizableWeightsYamlConverter<>).MakeGenericType(itemType)!;
|
||||
|
||||
this.converters[type] = (IYamlTypeConverter) Activator.CreateInstance(innerType)!;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) {
|
||||
return this.converters[type].ReadYaml(parser, type, rootDeserializer);
|
||||
}
|
||||
|
||||
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) {
|
||||
this.converters[type].WriteYaml(emitter, value, type, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
internal class RandomizableWeightsYamlConverter<T> : IYamlTypeConverter where T : struct, Enum {
|
||||
private readonly Type valueType;
|
||||
private readonly Type dictionaryType;
|
||||
|
||||
public RandomizableWeightsYamlConverter() {
|
||||
this.valueType = typeof(T);
|
||||
this.dictionaryType = typeof(IDictionary<T, int>);
|
||||
}
|
||||
|
||||
public bool Accepts(Type type) {
|
||||
return type == typeof(RandomizableWeights<T>);
|
||||
}
|
||||
|
||||
public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) {
|
||||
if (parser.Accept<Scalar>(out var evt)) {
|
||||
if (evt.Value == "null") {
|
||||
parser.Consume<Scalar>();
|
||||
return RandomizableWeights<T>.EmptyWeights;
|
||||
}
|
||||
var value = rootDeserializer.Invoke(this.valueType);
|
||||
if (value is T t) {
|
||||
return RandomizableWeights<T>.ConstantWeights(t);
|
||||
} else {
|
||||
return RandomizableWeights<T>.EmptyWeights;
|
||||
}
|
||||
}
|
||||
|
||||
if (parser.Accept<MappingStart>(out _)) {
|
||||
var value = rootDeserializer.Invoke(this.dictionaryType);
|
||||
if (value is IDictionary<T, int> dict) {
|
||||
return RandomizableWeights<T>.FromDictionary(dict);
|
||||
} else {
|
||||
return RandomizableWeights<T>.EmptyWeights;
|
||||
}
|
||||
}
|
||||
|
||||
throw new YamlException($"Error reading type RandomizableWeights<{typeof(T).Name}>.");
|
||||
}
|
||||
|
||||
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) {
|
||||
if (value is RandomizableWeights<T> weights) {
|
||||
switch (weights.Options.Count) {
|
||||
case 0:
|
||||
emitter.Emit(new Scalar("null"));
|
||||
break;
|
||||
case 1:
|
||||
serializer.Invoke(weights.Options[0].Item, valueType);
|
||||
break;
|
||||
default:
|
||||
emitter.Emit(new MappingStart());
|
||||
foreach (var (item, weight) in weights.Options) {
|
||||
serializer.Invoke(item, valueType);
|
||||
emitter.Emit(new Scalar(weight.ToString()));
|
||||
}
|
||||
emitter.Emit(new MappingEnd());
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
emitter.Emit(new MappingStart());
|
||||
emitter.Emit(new MappingEnd());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
ALttPRandomizer/Serialization/YamlInputFormatter.cs
Normal file
40
ALttPRandomizer/Serialization/YamlInputFormatter.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
namespace ALttPRandomizer.Serialization {
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
public class YamlInputFormatter : TextInputFormatter {
|
||||
private IDeserializer Deserializer { get; }
|
||||
private ILogger<YamlInputFormatter> Logger { get; }
|
||||
|
||||
public YamlInputFormatter(IDeserializer deserializer, ILogger<YamlInputFormatter> logger) {
|
||||
this.Deserializer = deserializer;
|
||||
this.Logger = logger;
|
||||
|
||||
this.SupportedEncodings.Add(Encoding.UTF8);
|
||||
this.SupportedEncodings.Add(Encoding.Unicode);
|
||||
this.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/yaml"));
|
||||
this.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/yaml"));
|
||||
}
|
||||
|
||||
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding) {
|
||||
var request = context.HttpContext.Request;
|
||||
|
||||
using var reader = context.ReaderFactory(request.Body, encoding);
|
||||
var type = context.ModelType;
|
||||
|
||||
try {
|
||||
var content = await reader.ReadToEndAsync();
|
||||
var model = this.Deserializer.Deserialize(content, type);
|
||||
return await InputFormatterResult.SuccessAsync(model);
|
||||
} catch (Exception ex) {
|
||||
this.Logger.LogInformation(ex, "Error parsing YAML");
|
||||
return await InputFormatterResult.FailureAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
72
ALttPRandomizer/Serialization/YamlStringEnumConverter.cs
Normal file
72
ALttPRandomizer/Serialization/YamlStringEnumConverter.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
namespace ALttPRandomizer.Serialization {
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text.Json.Serialization;
|
||||
using YamlDotNet.Core;
|
||||
using YamlDotNet.Core.Events;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
public class YamlStringEnumConverter : IYamlTypeConverter {
|
||||
private readonly Dictionary<Type, Dictionary<string, object>> deserializationMap;
|
||||
private readonly Dictionary<Type, Dictionary<object, string>> serializationMap;
|
||||
|
||||
public YamlStringEnumConverter() {
|
||||
this.deserializationMap = new();
|
||||
this.serializationMap = new();
|
||||
}
|
||||
|
||||
public bool Accepts(Type type) => type.IsEnum;
|
||||
|
||||
private void RegisterType(Type type) {
|
||||
if (this.serializationMap.ContainsKey(type) && this.deserializationMap.ContainsKey(type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.deserializationMap[type] = new();
|
||||
this.serializationMap[type] = new();
|
||||
|
||||
var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static);
|
||||
|
||||
foreach (var field in fields) {
|
||||
var alias = UnderscoredNamingConvention.Instance.Apply(field.Name);
|
||||
var value = Enum.Parse(type, field.Name);
|
||||
var att = field.GetCustomAttribute<JsonStringEnumMemberNameAttribute>();
|
||||
if (att != null) {
|
||||
alias = att.Name;
|
||||
}
|
||||
|
||||
this.deserializationMap[type][alias] = value;
|
||||
this.serializationMap[type][value] = alias;
|
||||
}
|
||||
}
|
||||
|
||||
public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) {
|
||||
this.RegisterType(type);
|
||||
|
||||
var stringValue = parser.Consume<Scalar>().Value;
|
||||
|
||||
if (this.deserializationMap[type].TryGetValue(stringValue, out var value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new YamlException($"Invalid value \"{stringValue}\" for type {type}");
|
||||
}
|
||||
|
||||
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) {
|
||||
this.RegisterType(type);
|
||||
|
||||
if (value is null) {
|
||||
throw new YamlException($"Unexpected null value of type {type}");
|
||||
}
|
||||
|
||||
if (this.serializationMap[type].TryGetValue(value, out var stringValue)) {
|
||||
emitter.Emit(new Scalar(stringValue));
|
||||
return;
|
||||
}
|
||||
|
||||
throw new YamlException($"Invalid value \"{value}\" for type {type}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user