< Summary

Information
Class: NGql.Core.ValueFormatter
Assembly: NGql.Core
File(s): /home/runner/work/NGql/NGql/src/Core/ValueFormatter.cs
Line coverage
100%
Covered lines: 69
Uncovered lines: 0
Coverable lines: 69
Total lines: 181
Line coverage: 100%
Branch coverage
100%
Covered branches: 115
Total branches: 115
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
IsPrimitiveType(...)100%1212100%
IsString(...)100%11100%
IsBoolean(...)100%11100%
IsInteger(...)100%22100%
IsSignedInteger(...)100%88100%
IsUnsignedInteger(...)100%88100%
IsFloating(...)100%66100%
IsDate(...)100%44100%
IsEnumLike(...)100%44100%
TryAppendPrimitive(...)100%66100%
TryAppendScalar(...)100%88100%
TryAppendNamed(...)100%66100%
AppendString(...)100%22100%
NeedsEscape(...)100%88100%
AppendEscapedBody(...)100%1515100%
AppendBoolean(...)100%22100%
TryAppendInteger(...)100%22100%
TryAppendSignedInteger(...)100%88100%
TryAppendUnsignedInteger(...)100%88100%
TryAppendFloating(...)100%66100%
AppendFormattable(...)100%11100%
AppendQuotedFormattable(...)100%11100%

File(s)

/home/runner/work/NGql/NGql/src/Core/ValueFormatter.cs

#LineLine coverage
 1using System.Globalization;
 2using System.Text;
 3
 4namespace NGql.Core;
 5
 6internal static class ValueFormatter
 7{
 8    internal const string DateFormat = "yyyy-MM-dd'T'HH:mm:ss.fffK";
 9
 10    // Pre-allocated strings for common boolean values to avoid repeated allocations
 11    private const string TrueString = "true";
 12    private const string FalseString = "false";
 13
 14    /// <summary>
 15    /// Returns true when <paramref name="value"/> is a primitive type this formatter handles.
 16    /// Single is-test per category keeps cyclomatic complexity flat.
 17    /// </summary>
 18    internal static bool IsPrimitiveType(object value)
 2892619        => IsString(value) || IsBoolean(value) || IsInteger(value) || IsFloating(value)
 2892620        || IsDate(value) || IsEnumLike(value) || value is Variable;
 21
 2892622    private static bool IsString(object v) => v is string;
 2760323    private static bool IsBoolean(object v) => v is bool;
 2628924    private static bool IsInteger(object v) => IsSignedInteger(v) || IsUnsignedInteger(v);
 2628925    private static bool IsSignedInteger(object v) => v is sbyte or short or int or long;
 137426    private static bool IsUnsignedInteger(object v) => v is byte or ushort or uint or ulong;
 135027    private static bool IsFloating(object v) => v is float or double or decimal;
 131128    private static bool IsDate(object v) => v is DateTime or DateTimeOffset;
 129929    private static bool IsEnumLike(object v) => v is EnumValue or Enum;
 30
 31    /// <summary>
 32    /// Writes the primitive representation of <paramref name="value"/> directly to
 33    /// <paramref name="builder"/> without allocating an intermediate string.
 34    /// Each value category dispatches to a small dedicated helper to keep this top-level
 35    /// switch's cyclomatic complexity low.
 36    /// </summary>
 37    /// <returns>True if the value was handled; false if it is not a known primitive.</returns>
 38    internal static bool TryAppendPrimitive(object value, StringBuilder builder)
 1747239        => TryAppendScalar(value, builder)
 1747240        || TryAppendNamed(value, builder)
 1747241        || TryAppendInteger(value, builder)
 1747242        || TryAppendFloating(value, builder);
 43
 44    private static bool TryAppendScalar(object value, StringBuilder builder)
 45    {
 46        switch (value)
 47        {
 337848            case string s: AppendString(builder, s); return true;
 133849            case bool b: AppendBoolean(builder, b); return true;
 650            case DateTime dt: AppendQuotedFormattable(builder, dt); return true;
 651            case DateTimeOffset dto: AppendQuotedFormattable(builder, dto); return true;
 1510852            default: return false;
 53        }
 54    }
 55
 56    private static bool TryAppendNamed(object value, StringBuilder builder)
 57    {
 58        switch (value)
 59        {
 2460            case Enum e: builder.Append(e.ToString()); return true;
 6661            case EnumValue ev: builder.Append(ev.Value); return true;
 15062            case Variable variable: builder.Append(variable.Name); return true;
 1498863            default: return false;
 64        }
 65    }
 66
 67    private static void AppendString(StringBuilder builder, string s)
 68    {
 168969        builder.Append('"');
 168970        if (NeedsEscape(s))
 71        {
 2472            AppendEscapedBody(builder, s);
 73        }
 74        else
 75        {
 166576            builder.Append(s);
 77        }
 168978        builder.Append('"');
 168979    }
 80
 81    /// <summary>
 82    /// Per GraphQL spec § 2.9.4 a regular string literal must escape <c>\</c>, <c>"</c>, and
 83    /// the C0 control characters. Non-control Unicode characters pass through verbatim — the
 84    /// transport carries UTF-8.
 85    /// </summary>
 86    private static bool NeedsEscape(string s)
 87    {
 2385688        for (var i = 0; i < s.Length; i++)
 89        {
 1026390            var c = s[i];
 1026391            if (c == '"' || c == '\\' || c < 0x20)
 2492                return true;
 93        }
 166594        return false;
 95    }
 96
 97    private static void AppendEscapedBody(StringBuilder builder, string s)
 98    {
 64899        for (var i = 0; i < s.Length; i++)
 100        {
 300101            var c = s[i];
 102            switch (c)
 103            {
 12104                case '"':  builder.Append("\\\""); break;
 12105                case '\\': builder.Append("\\\\"); break;
 6106                case '\b': builder.Append("\\b");  break;
 6107                case '\f': builder.Append("\\f");  break;
 6108                case '\n': builder.Append("\\n");  break;
 6109                case '\r': builder.Append("\\r");  break;
 6110                case '\t': builder.Append("\\t");  break;
 111                default:
 273112                    if (c < 0x20)
 113                    {
 114                        // Other C0 controls — emit as \u00XX (GraphQL accepts EscapedUnicode).
 3115                        builder.Append("\\u");
 3116                        builder.Append(((int)c).ToString("x4", CultureInfo.InvariantCulture));
 117                    }
 118                    else
 119                    {
 270120                        builder.Append(c);
 121                    }
 122                    break;
 123            }
 124        }
 24125    }
 126
 127    private static void AppendBoolean(StringBuilder builder, bool b)
 669128        => builder.Append(b ? TrueString : FalseString);
 129
 130    private static bool TryAppendInteger(object value, StringBuilder builder)
 14988131        => TryAppendSignedInteger(value, builder) || TryAppendUnsignedInteger(value, builder);
 132
 133    private static bool TryAppendSignedInteger(object value, StringBuilder builder)
 134    {
 135        switch (value)
 136        {
 20250137            case int v: builder.Append(v); return true;
 4944138            case long v: builder.Append(v); return true;
 24139            case short v: builder.Append(v); return true;
 24140            case sbyte v: builder.Append(v); return true;
 2367141            default: return false;
 142        }
 143    }
 144
 145    private static bool TryAppendUnsignedInteger(object value, StringBuilder builder)
 146    {
 147        switch (value)
 148        {
 24149            case uint v: builder.Append(v); return true;
 24150            case ulong v: builder.Append(v); return true;
 24151            case ushort v: builder.Append(v); return true;
 24152            case byte v: builder.Append(v); return true;
 2319153            default: return false;
 154        }
 155    }
 156
 157    private static bool TryAppendFloating(object value, StringBuilder builder)
 158    {
 159        switch (value)
 160        {
 24161            case float v: AppendFormattable(builder, v); return true;
 54162            case double v: AppendFormattable(builder, v); return true;
 24163            case decimal v: AppendFormattable(builder, v); return true;
 2268164            default: return false;
 165        }
 166    }
 167
 168    /// <summary>Formats <paramref name="value"/> with invariant culture. Used for
 169    /// float/double/decimal — none of those produce strings longer than the BCL guarantees.</summary>
 170    private static void AppendFormattable(StringBuilder builder, IFormattable value)
 51171        => builder.Append(value.ToString(null, CultureInfo.InvariantCulture));
 172
 173    /// <summary>Formats <paramref name="value"/> with the NGql DateFormat and quotes it.
 174    /// Used for DateTime/DateTimeOffset.</summary>
 175    private static void AppendQuotedFormattable(StringBuilder builder, IFormattable value)
 176    {
 6177        builder.Append('"');
 6178        builder.Append(value.ToString(DateFormat, CultureInfo.InvariantCulture));
 6179        builder.Append('"');
 6180    }
 181}