Kontrola vsech 180 operaci proti falesnemu iDoklad API a round trip 331 modelu SDK v obou smerech. - SdkNullablePropertyConverter doplnuje cteni NullableProperty<T>. Konvertor SDK umi jen zapis, takze PATCH s takovou polozkou koncil prazdnou 500 uz pri cteni tela. Tykalo se 29 modelu. Zapis zustava na konvertoru SDK, odchozi payload se nemeni. - Datum uvnitr NullableProperty se normalizuje na UTC stejne jako zbytek serializace. - ListModifiers parsuje datum ve filtru s AdjustToUniversal a AssumeUniversal. Filtr se zonou se drive posouval o offset. - POST /attachments kontroluje FileName a FileBytes, SDK na ne sahalo bez kontroly na null a vracelo neosetrenou 500. - ExceptionHandlingMiddleware ma posledni zachyt, zadny request uz nekonci prazdnou 500. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
67 lines
2.9 KiB
C#
67 lines
2.9 KiB
C#
using System.Reflection;
|
|
using Newtonsoft.Json;
|
|
|
|
namespace Idoklad.Infrastructure;
|
|
|
|
/// <summary>
|
|
/// Read support for the SDK's <c>NullableProperty<T></c>, used by Patch models to tell
|
|
/// "not set" apart from "set to null". The SDK converter only writes: its <c>ReadJson</c> throws
|
|
/// <see cref="NotImplementedException"/>, because the SDK never deserializes a Patch model. This
|
|
/// service does, so a PATCH body carrying such a member failed while binding. Writing stays with
|
|
/// the SDK converter so the payload sent upstream is unchanged.
|
|
/// </summary>
|
|
public sealed class SdkNullablePropertyConverter : JsonConverter
|
|
{
|
|
private readonly JsonConverter _sdkConverter;
|
|
|
|
public SdkNullablePropertyConverter(JsonConverter sdkConverter)
|
|
{
|
|
_sdkConverter = sdkConverter;
|
|
}
|
|
|
|
public override bool CanRead => true;
|
|
|
|
public override bool CanWrite => _sdkConverter.CanWrite;
|
|
|
|
public override bool CanConvert(Type objectType) => _sdkConverter.CanConvert(objectType);
|
|
|
|
/// <summary>
|
|
/// Reads the JSON scalar the SDK converter writes: a value, or null for an explicit reset.
|
|
/// A member missing from the body is never read at all and keeps its unset default.
|
|
/// </summary>
|
|
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
|
{
|
|
var propertyType = Nullable.GetUnderlyingType(objectType) ?? objectType;
|
|
var valueType = propertyType.GetGenericArguments()[0];
|
|
|
|
var value = reader.TokenType == JsonToken.Null
|
|
? null
|
|
: serializer.Deserialize(reader, typeof(Nullable<>).MakeGenericType(valueType));
|
|
|
|
// The member converter puts the reader in DateParseHandling.None, so the date arrives as a
|
|
// string and the serializer's DateTimeZoneHandling never gets applied to it. The SDK
|
|
// rejects a DateTime whose Kind is not Utc, so apply the same normalization here.
|
|
if (value is DateTime date)
|
|
{
|
|
value = ToUtc(date);
|
|
}
|
|
|
|
// NullableProperty<T>(T? value) marks the member as set, including when the value is null.
|
|
return Activator.CreateInstance(propertyType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance, null, new[] { value }, null);
|
|
}
|
|
|
|
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
|
=> _sdkConverter.WriteJson(writer, value, serializer);
|
|
|
|
/// <summary>
|
|
/// Matches <see cref="DateTimeZoneHandling.Utc"/>: a value that carries an offset is converted,
|
|
/// a value without one is taken as already being UTC and is only marked as such.
|
|
/// </summary>
|
|
private static DateTime ToUtc(DateTime value) => value.Kind switch
|
|
{
|
|
DateTimeKind.Utc => value,
|
|
DateTimeKind.Local => value.ToUniversalTime(),
|
|
_ => DateTime.SpecifyKind(value, DateTimeKind.Utc),
|
|
};
|
|
}
|