• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Aldaviva / Unfucked / 39571407865

21 Oct 2026 08:36AM UTC coverage: 43.396% (+1.3%) from 42.05%
39571407865

push

github

Aldaviva
HTTP: added MemoryBodyReader that can deserialize types with a constructor that takes a string, a static Parse(string) method, or which implement IParsable or IUtf8SpanParsable, allowing it to automatically deserialize responses into types like Version, bool, Uri, and PhysicalAddress. This changed the signature of the MessageBodyReader.CanRead method. Handle charsets that are missing the hyphen, such as "utf8". Unfucked: added AsyncEventHandler delegate variant that returns a Task<T>. DateTime: added Duration.StandardPeriod, DateTimeZone.Current, ZonedDateTime.PeriodSinceStartOfDay, and ZonedDateTime.DurationSinceStartOfDay.

751 of 2229 branches covered (33.69%)

76 of 96 new or added lines in 10 files covered. (79.17%)

1311 of 3021 relevant lines covered (43.4%)

157.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.41
/HTTP/Serialization/GenericBodyReader.cs
1
using System.Collections.Concurrent;
2
using System.Net.Http.Headers;
3
using System.Reflection;
4
using System.Runtime.Serialization;
5
using System.Text;
6
using Unfucked.HTTP.Config;
7
using ConstructorMethod =
8
#if NET8_0_OR_GREATER
9
    System.Reflection.ConstructorInvoker
10
#else
11
    System.Reflection.ConstructorInfo
12
#endif
13
    ;
14

15
namespace Unfucked.HTTP.Serialization;
16

17
/// <summary>Can parse types like <see cref="Version"/> or <see cref="Uri"/> from strings</summary>
18
public class GenericBodyReader: MessageBodyReader {
19

20
    private static readonly Lazy<ConcurrentDictionary<Type, ConstructorMethod?>> CONSTRUCTOR_CACHE  = new(LazyThreadSafetyMode.PublicationOnly);
1 ✔
21
    private static readonly Lazy<ConcurrentDictionary<Type, object?>>            PARSE_METHOD_CACHE = new(LazyThreadSafetyMode.PublicationOnly); // type of value is ParseString<T>?
1 ✔
22

23
#if NET8_0_OR_GREATER
24
    private delegate T ParseUtf8Span<out T>(ReadOnlySpan<byte> serialized, IFormatProvider? formatProvider);
25

26
    private static readonly Lazy<ConcurrentDictionary<Type, object?>> UTF8_SPAN_PARSER_CACHE = new(LazyThreadSafetyMode.PublicationOnly); // type of value is ParseUtf8Span<T>
1 ✔
27
#endif
28

29
#if NET7_0_OR_GREATER
30
    private delegate T ParseStringWithFormat<out T>(string serialized, IFormatProvider? formatProvider);
31

32
    private static readonly Lazy<ConcurrentDictionary<Type, object?>> STRING_PARSER_CACHE = new(LazyThreadSafetyMode.PublicationOnly); // type of value is ParseStringWithFormat<T>?
1 ✔
33
#endif
34

35
    private delegate T ParseString<out T>(string serialized);
36

37
    /// <inheritdoc />
38
    public bool CanRead<T>(MediaTypeHeaderValue? contentType, string? bodyPrefix) =>
39
#if NET8_0_OR_GREATER
40
        ((contentType?.CharSet?.Replace("-", string.Empty).Equals(Encoding.UTF8.BodyName, StringComparison.OrdinalIgnoreCase) ?? true) &&
4 !
41
            UTF8_SPAN_PARSER_CACHE.Value.GetOrAdd(typeof(T), _ => FindUtf8SpanParser<T>()) != null) ||
4 ✔
42
#endif
4 ✔
43
#if NET7_0_OR_GREATER
4 ✔
44
        STRING_PARSER_CACHE.Value.GetOrAdd(typeof(T), _ => FindStringParser<T>()) != null ||
3 ✔
45
#endif
4 ✔
46
        CONSTRUCTOR_CACHE.Value.GetOrAdd(typeof(T), FindConstructor) != null ||
4 !
47
        PARSE_METHOD_CACHE.Value.GetOrAdd(typeof(T), _ => FindParseMethod<T>()) != null;
5 ✔
48

49
    /// <inheritdoc />
50
    /// <exception cref="SerializationException"></exception>
51
    public async Task<T> Read<T>(HttpResponseMessage response, Encoding? responseEncoding, Configurable? clientConfig, CancellationToken cancellationToken) {
52
#if NET8_0_OR_GREATER
53
        if ((responseEncoding?.Equals(Encoding.UTF8) ?? true) && UTF8_SPAN_PARSER_CACHE.Value.GetOrAdd(typeof(T), _ => FindUtf8SpanParser<T>()) is ParseUtf8Span<T> utf8SpanParser) {
4 !
54
            await using Stream stream     = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
1 ✔
55
            MemoryStream       bodyBuffer = new();
1 ✔
56
            await stream.CopyToAsync(bodyBuffer, int.MaxValue, cancellationToken).ConfigureAwait(false);
1 ✔
57
            ReadOnlySpan<byte> bodySpan = bodyBuffer.GetBuffer().AsSpan(0, (int) bodyBuffer.Length);
1 ✔
58
            return utf8SpanParser(bodySpan, null);
1 ✔
59
        }
60
#endif
61

62
#if NET7_0_OR_GREATER
63
        if (STRING_PARSER_CACHE.Value.GetOrAdd(typeof(T), _ => FindStringParser<T>()) is ParseStringWithFormat<T> stringParser) {
3 ✔
64
            string stringBody = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
1 ✔
65
            return stringParser(stringBody, null);
1 ✔
66
        }
67
#endif
68

69
        if (CONSTRUCTOR_CACHE.Value.GetOrAdd(typeof(T), FindConstructor) is {} constructor) {
2 !
70
            string stringBody = await response.Content.ReadAsStringAsync(
1 ✔
71
#if NET5_0_OR_GREATER
1 ✔
72
                cancellationToken
1 ✔
73
#endif
1 ✔
74
            ).ConfigureAwait(false);
1 ✔
75

76
            return (T)
1 ✔
77
#if NET8_0_OR_GREATER
1 ✔
78
                constructor.Invoke(stringBody);
1 ✔
79
#else
80
                constructor.Invoke([stringBody]);
81
#endif
82
        }
83

84
        if (PARSE_METHOD_CACHE.Value.GetOrAdd(typeof(T), _ => FindParseMethod<T>()) is ParseString<T> parseMethod) {
1 !
85
            string stringBody = await response.Content.ReadAsStringAsync(
1 ✔
86
#if NET5_0_OR_GREATER
1 ✔
87
                cancellationToken
1 ✔
88
#endif
1 ✔
89
            ).ConfigureAwait(false);
1 ✔
90

91
            return parseMethod(stringBody);
1 ✔
92
        }
93

NEW
94
        throw new SerializationException($"{typeof(T).FullName} can be deserialized neither with IUtf8SpanParsable.Parse, IParsable.Parse, new(string), nor static Parse(string)");
×
95
    }
4 ✔
96

97
#if NET8_0_OR_GREATER
98
    private static ParseUtf8Span<T>? FindUtf8SpanParser<T>() {
99
        Type type = typeof(T);
4 ✔
100
        try {
101
            type.IsAssignableTo(typeof(IUtf8SpanParsable<>).MakeGenericType(type));
4 ✔
102
        } catch (ArgumentException) {
4 ✔
103
            return null;
3 ✔
104
        }
105
        return type.GetMethod($"System.IUtf8SpanParsable<{type.FullName}>.Parse",
1 !
106
                BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
1 ✔
107
                [typeof(ReadOnlySpan<byte>), typeof(IFormatProvider)])?
1 ✔
108
            .CreateDelegate<ParseUtf8Span<T>>();
1 ✔
109
    }
3 ✔
110

111
#endif
112

113
#if NET7_0_OR_GREATER
114
    private static ParseStringWithFormat<T>? FindStringParser<T>() {
115
        Type type = typeof(T);
3 ✔
116
        try {
117
            type.IsAssignableTo(typeof(IParsable<>).MakeGenericType(type));
3 ✔
118
        } catch (ArgumentException) {
3 ✔
119
            return null;
2 ✔
120
        }
121
        return type.GetMethod($"System.IParsable<{type.FullName}>.Parse",
1 !
122
            BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
1 ✔
123
            [typeof(string), typeof(IFormatProvider)])?.CreateDelegate<ParseStringWithFormat<T>>();
1 ✔
124
    }
2 ✔
125

126
#endif
127

128
    private static ConstructorMethod? FindConstructor(Type type) {
129
        var constructor = type.GetConstructor([typeof(string)]);
2 ✔
130
        return
2 ✔
131
#if NET8_0_OR_GREATER
2 ✔
132
            constructor != null ? ConstructorMethod.Create(constructor) : null;
2 ✔
133
#else
134
            constructor;
135
#endif
136
    }
137

138
    private static ParseString<T>? FindParseMethod<T>() =>
139
        typeof(T).GetMethod("Parse", BindingFlags.Static | BindingFlags.Public, null, [typeof(string)], null)?.CreateDelegate
1 !
140
#if NET5_0_OR_GREATER
1 ✔
141
            <ParseString<T>>();
1 ✔
142
#else
143
            (typeof(ParseString<T>)) as ParseString<T>;
144
#endif
145

146
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc