Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 23 additions & 21 deletions Sources/Falko.Talkie.Bridges.Telegram/Models/TextOrNumberValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,49 +5,51 @@ namespace Talkie.Bridges.Telegram.Models;

public readonly struct TextOrNumberValue
{
public static readonly TextOrNumberValue Empty = default;
private const string EmptyString = "empty";

private readonly string? _text;
private readonly string? _textValue;

private readonly long _number;
private readonly long _numberValue;

private readonly bool _containsTextOrNumber;
private readonly bool _hasValue;

public TextOrNumberValue(string text)
public TextOrNumberValue(string textValue)
{
ArgumentNullException.ThrowIfNull(text);
ArgumentNullException.ThrowIfNull(textValue);

_text = text;
_containsTextOrNumber = true;
_textValue = textValue;
_hasValue = true;
}

public TextOrNumberValue(long number)
public TextOrNumberValue(long numberValue)
{
_number = number;
_containsTextOrNumber = true;
_numberValue = numberValue;
_hasValue = true;
}

public bool IsEmpty => _containsTextOrNumber is false;
public static TextOrNumberValue Empty => default;

[MemberNotNullWhen(true, nameof(_text))]
public bool ContainsText() => _text is not null;
public bool IsEmpty => _hasValue is false;

[MemberNotNullWhen(false, nameof(_text))]
public bool ContainsNumber() => _containsTextOrNumber && _text is null;
[MemberNotNullWhen(true, nameof(_textValue))]
public bool ContainsText() => _textValue is not null;

public string GetText() => ContainsText() ? _text : throw new InvalidOperationException("Text is not set.");
[MemberNotNullWhen(false, nameof(_textValue))]
public bool ContainsNumber() => _hasValue && _textValue is null;

public long GetNumber() => ContainsNumber() ? _number : throw new InvalidOperationException("Number is not set.");
public string GetText() => ContainsText() ? _textValue : throw new InvalidOperationException("Text is not set.");

public long GetNumber() => ContainsNumber() ? _numberValue : throw new InvalidOperationException("Number is not set.");

public bool TryGetText([MaybeNullWhen(false)] out string text)
{
text = _text;
text = _textValue;
return ContainsText();
}

public bool TryGetNumber(out long number)
{
number = _number;
number = _numberValue;
return ContainsNumber();
}

Expand All @@ -63,6 +65,6 @@ public override string ToString()
return number.ToString(CultureInfo.InvariantCulture);
}

return nameof(Empty);
return EmptyString;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,77 +2,84 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Talkie.Bridges.Telegram.Models;
using Talkie.Sequences;

namespace Talkie.Bridges.Telegram.Serialization;

internal sealed class TextOrNumberValueDictionaryConverter
: JsonConverter<IReadOnlyDictionary<string, TextOrNumberValue>>
internal sealed class TextOrNumberValueDictionaryConverter : JsonConverter<IReadOnlyDictionary<string, TextOrNumberValue>>
{
public override IReadOnlyDictionary<string, TextOrNumberValue> Read(ref Utf8JsonReader reader,
public override IReadOnlyDictionary<string, TextOrNumberValue> Read
(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
JsonSerializerOptions options
)
{
if (reader.TokenType is not JsonTokenType.StartObject)
{
throw new JsonException("Expected start object");
throw new JsonException("Expected start object.");
}

var dictionary = new Dictionary<string, TextOrNumberValue>();
var pairs = new Sequence<KeyValuePair<string, TextOrNumberValue>>();

while (reader.Read())
{
if (reader.TokenType is JsonTokenType.EndObject)
{
return dictionary.ToFrozenDictionary();
return pairs.ToFrozenDictionary();
}

if (reader.TokenType is not JsonTokenType.PropertyName)
{
throw new JsonException("Expected property name");
throw new JsonException("Expected property name.");
}

var key = reader.GetString() ?? throw new JsonException("Key is null");
var key = reader.GetString() ?? throw new JsonException("Key is null.");

if (reader.Read() is false)
{
throw new JsonException("Expected string value");
throw new JsonException("Expected property value.");
}

if (reader.TokenType is JsonTokenType.String)
var value = reader.TokenType switch
{
var value = new TextOrNumberValue(reader.GetString() ?? throw new JsonException("Value is null"));
// We don't check for null here, because it will be checked in the constructor
JsonTokenType.String => new TextOrNumberValue(reader.GetString()!),
JsonTokenType.Number => new TextOrNumberValue(reader.GetInt64()),
_ => throw new JsonException($"Unexpected value type for property '{key}', expected string or number.")
};

dictionary.Add(key, value);
}
else if (reader.TokenType is JsonTokenType.Number)
{
var value = new TextOrNumberValue(reader.GetInt64());

dictionary.Add(key, value);
}
pairs.Add(new KeyValuePair<string, TextOrNumberValue>(key, value));
}

throw new JsonException("Expected end object");
throw new JsonException("Expected end object.");
}

public override void Write(Utf8JsonWriter writer,
public override void Write
(
Utf8JsonWriter writer,
IReadOnlyDictionary<string, TextOrNumberValue> dictionary,
JsonSerializerOptions options)
JsonSerializerOptions options
)
{
writer.WriteStartObject();

foreach (var (key, value) in dictionary)
foreach (var pair in dictionary)
{
var value = pair.Value;

if (value.TryGetNumber(out var number))
{
writer.WriteNumber(key, number);
writer.WriteNumber(pair.Key, number);
}
else if (value.TryGetText(out var text))
{
writer.WriteString(key, text);
writer.WriteString(pair.Key, text);
}
else
{
throw new JsonException($"Value for key '{pair.Key}' does not contain a number or text, or empty.");
}

throw new JsonException("Value is empty");
}

writer.WriteEndObject();
Expand Down