< Summary

Information
Class: TeleFlow.Telegram.Internal.TelegramMultipartContentBuilder
Assembly: TeleFlow.Telegram.Client
File(s): /_/src/TeleFlow.Telegram.Client/Internal/TelegramMultipartContentBuilder.cs
Line coverage
92%
Covered lines: 61
Uncovered lines: 5
Coverable lines: 66
Total lines: 160
Line coverage: 92.4%
Branch coverage
72%
Covered branches: 49
Total branches: 68
Branch coverage: 72%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Build(...)87.5%88100%
ToJsonNode(...)70.83%504890.62%
AddField(...)62.5%88100%
AddFile(...)75%4477.77%

File(s)

/_/src/TeleFlow.Telegram.Client/Internal/TelegramMultipartContentBuilder.cs

#LineLine coverage
 1using System.Collections;
 2using System.Globalization;
 3using System.Text.Json;
 4using System.Text.Json.Nodes;
 5using TeleFlow.Telegram.Schema.Types;
 6
 7namespace TeleFlow.Telegram.Internal;
 8
 9/// <summary>
 10/// Converts a Telegram request payload that contains uploads into multipart fields and files.
 11/// Nested files are exposed to Telegram JSON fields through attach:// references.
 12/// This path is used when a user sends local streams/files through generated Bot API methods.
 13/// </summary>
 14internal sealed class TelegramMultipartContentBuilder
 15{
 16    private readonly JsonSerializerOptions _serializerOptions;
 917    private readonly List<TelegramMultipartField> _fields = [];
 918    private readonly List<TelegramMultipartFile> _files = [];
 19    private int _fileIndex;
 20
 21    public TelegramMultipartContentBuilder(JsonSerializerOptions serializerOptions)
 22    {
 923        ArgumentNullException.ThrowIfNull(serializerOptions);
 924        _serializerOptions = serializerOptions;
 925    }
 26
 27    public TelegramMultipartTransportContent Build(object payload)
 28    {
 929        ArgumentNullException.ThrowIfNull(payload);
 30
 15331        foreach (var property in TelegramRequestTypeMetadataCache.Get(payload.GetType()).TelegramProperties)
 32        {
 6833            var value = property.GetValue(payload);
 6834            if (value is null)
 35            {
 36                continue;
 37            }
 38
 1939            var fieldName = property.TelegramName;
 1940            if (TelegramRequestUploadDetector.TryGetDirectInputFile(value, out var inputFile))
 41            {
 742                AddFile(fieldName, inputFile);
 643                continue;
 44            }
 45
 1246            var node = ToJsonNode(value);
 1247            if (node is not null)
 48            {
 1249                AddField(fieldName, node);
 50            }
 51        }
 52
 853        return new TelegramMultipartTransportContent(_fields.ToArray(), _files.ToArray());
 54    }
 55
 56    private JsonNode? ToJsonNode(object? value)
 57    {
 3858        if (value is null)
 59        {
 060            return null;
 61        }
 62
 63        // Nested files are represented in JSON fields by attach:// names.
 64        // The binary stream is added to the same multipart body under that generated name.
 3865        if (value is InputFile inputFile)
 66        {
 267            var fileName = "file" + _fileIndex.ToString(CultureInfo.InvariantCulture);
 268            _fileIndex++;
 269            AddFile(fileName, inputFile);
 270            return JsonValue.Create("attach://" + fileName);
 71        }
 72
 3673        if (value is string stringValue)
 74        {
 775            return JsonValue.Create(stringValue);
 76        }
 77
 2978        if (value is bool boolValue)
 79        {
 080            return JsonValue.Create(boolValue);
 81        }
 82
 2983        if (value is byte or sbyte or short or ushort or int or uint or long or ulong or float or double or decimal)
 84        {
 985            return JsonSerializer.SerializeToNode(value, value.GetType(), _serializerOptions);
 86        }
 87
 2088        if (value is IEnumerable enumerable and not string)
 89        {
 290            var array = new JsonArray();
 1091            foreach (var item in enumerable)
 92            {
 393                array.Add(ToJsonNode(item));
 94            }
 95
 296            return array;
 97        }
 98
 1899        var unionCase = TelegramRequestUploadDetector.GetActiveUnionCase(value);
 18100        if (unionCase is not null)
 101        {
 102            // Telegram unions serialize as their selected value, not as the wrapper object.
 15103            return ToJsonNode(unionCase.Value);
 104        }
 105
 3106        var metadata = TelegramRequestTypeMetadataCache.Get(value.GetType());
 3107        if (metadata.TelegramProperties.Length == 0)
 108        {
 0109            return JsonSerializer.SerializeToNode(value, value.GetType(), _serializerOptions);
 110        }
 111
 3112        var jsonObject = new JsonObject();
 48113        foreach (var property in metadata.TelegramProperties)
 114        {
 21115            var propertyValue = property.GetValue(value);
 21116            if (propertyValue is null)
 117            {
 118                continue;
 119            }
 120
 8121            var node = ToJsonNode(propertyValue);
 8122            if (node is not null)
 123            {
 8124                jsonObject[property.TelegramName] = node;
 125            }
 126        }
 127
 3128        return jsonObject;
 129    }
 130
 131    private void AddField(string name, JsonNode node)
 132    {
 12133        var value = node switch
 12134        {
 11135            JsonValue jsonValue when jsonValue.TryGetValue<string>(out var stringValue) => stringValue,
 9136            JsonValue jsonValue when jsonValue.TryGetValue<bool>(out var boolValue) => boolValue ? "true" : "false",
 11137            _ => node.ToJsonString(_serializerOptions)
 12138        };
 139
 12140        _fields.Add(new TelegramMultipartField(name, value));
 12141    }
 142
 143    private void AddFile(string name, InputFile inputFile)
 144    {
 9145        if (!inputFile.Content.CanRead)
 146        {
 0147            throw new InvalidOperationException(
 0148                $"InputFile '{inputFile.FileName}' cannot be uploaded because its stream is not readable.");
 149        }
 150
 9151        if (!inputFile.Content.CanSeek)
 152        {
 1153            throw new InvalidOperationException(
 1154                $"InputFile '{inputFile.FileName}' cannot be uploaded by the default Telegram executor because its strea
 155        }
 156
 8157        inputFile.Content.Position = 0;
 8158        _files.Add(new TelegramMultipartFile(name, inputFile.FileName, inputFile.Content));
 8159    }
 160}