Skip to content
Closed
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
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<Nullable>enable</Nullable>
<Deterministic>true</Deterministic>
<TestTfm>net10.0</TestTfm>
<SerdeAssemblyVersion>0.9.1</SerdeAssemblyVersion>
<SerdePkgVersion>0.9.1</SerdePkgVersion>
<SerdeAssemblyVersion>0.10.0-fixedWidth.11</SerdeAssemblyVersion>
<SerdePkgVersion>0.10.0-fixedWidth.11</SerdePkgVersion>
</PropertyGroup>
</Project>
21 changes: 21 additions & 0 deletions src/serde/FixedWidth/FieldOverflowHandling.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using StaticCs;

namespace Serde.FixedWidth
{
/// <summary>
/// Enumerates the supported options for field values that are too long.
/// </summary>
[Closed]
public enum FieldOverflowHandling
{
/// <summary>
/// Indicates that an exception should be thrown if the field value is longer than the field length.
/// </summary>
Throw = 0,

/// <summary>
/// Indicates that the field value should be truncated if the value is longer than the field length.
/// </summary>
Truncate = 1,
}
}
102 changes: 102 additions & 0 deletions src/serde/FixedWidth/FixedFieldInfoAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;

namespace Serde.FixedWidth
{
/// <summary>
/// Decorates a field in a fixed-width file with meta information about that field.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class FixedFieldInfoAttribute : Attribute
{
/// <summary>
/// Gets the offset that indicates where the field begins.
/// </summary>
public int Offset { get; }

/// <summary>
/// Gets the length of the field.
/// </summary>
public int Length { get; }

/// <summary>
/// Gets the format string for providing to <c>TryParseExact</c>.
/// </summary>
/// <remarks>
/// Defaults to <see cref="string.Empty"/>.
/// </remarks>
public string Format { get; }

/// <summary>
/// Gets a value indicating how to handle field overflows.
/// </summary>
/// <remarks>
/// Defaults to <see cref="FieldOverflowHandling.Throw"/>.
/// </remarks>
public FieldOverflowHandling OverflowHandling { get; }

public FixedFieldInfoAttribute(int offset, int length, string format = "", FieldOverflowHandling overflowHandling = FieldOverflowHandling.Throw)
{
ArgumentOutOfRangeException.ThrowIfNegative(offset);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(length);
Offset = offset;
Length = length;
Format = string.IsNullOrWhiteSpace(format) ? string.Empty : format;
OverflowHandling = overflowHandling;
}

public static FixedFieldInfoAttribute FromCustomAttributeData(CustomAttributeData? customAttribute)
{
if (!TryGetFixedFieldInfoAttribute(customAttribute, out var attribute))
{
throw new InvalidOperationException($"Cannot write fixed field value without required '{nameof(FixedFieldInfoAttribute)}' annotation.");
}

return attribute;
}

public static bool TryGetFixedFieldInfoAttribute(CustomAttributeData? customAttribute, [NotNullWhen(true)] out FixedFieldInfoAttribute? fixedFieldInfoAttribute)
{
fixedFieldInfoAttribute = null;

if (customAttribute is null)
{
return false;
}

string format;

if (!TryGetNamedArgumentValue(customAttribute, 0, out int offset))
{
return false;
}

if (!TryGetNamedArgumentValue(customAttribute, 1, out int length))
{
return false;
}

if (!TryGetNamedArgumentValue(customAttribute, 2, out string? formatValue))
{
format = string.Empty;
}
format = formatValue ?? string.Empty;

if (!TryGetNamedArgumentValue(customAttribute, 3, out FieldOverflowHandling fieldOverflowHandling))
{
fieldOverflowHandling = FieldOverflowHandling.Throw;
}

fixedFieldInfoAttribute = new(offset, length, format, fieldOverflowHandling);
return true;

static bool TryGetNamedArgumentValue<T>(CustomAttributeData customAttribute, int argumentIndex, [NotNullWhen(true)] out T? value)
{
value = (T?)customAttribute.ConstructorArguments[argumentIndex].Value;
return value is { };
}
}
}
}
38 changes: 38 additions & 0 deletions src/serde/FixedWidth/FixedWidthDeserializer.Deserialize.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Serde.FixedWidth.Reader;
using System;
using System.Buffers;

namespace Serde.FixedWidth
{
internal sealed partial class FixedWidthDeserializer : IDeserializer
{
ITypeDeserializer IDeserializer.ReadType(ISerdeInfo typeInfo)
{
if (typeInfo.Kind is not InfoKind.CustomType)
{
throw new ArgumentException("Invalid type for ReadType: " + typeInfo.Kind);
}

return this;
}

string IDeserializer.ReadString() => throw new NotImplementedException();
T? IDeserializer.ReadNullableRef<T>(IDeserialize<T> deserialize) where T : class => throw new NotImplementedException();
bool IDeserializer.ReadBool() => throw new NotImplementedException();
char IDeserializer.ReadChar() => throw new NotImplementedException();
byte IDeserializer.ReadU8() => throw new NotImplementedException();
ushort IDeserializer.ReadU16() => throw new NotImplementedException();
uint IDeserializer.ReadU32() => throw new NotImplementedException();
ulong IDeserializer.ReadU64() => throw new NotImplementedException();
sbyte IDeserializer.ReadI8() => throw new NotImplementedException();
short IDeserializer.ReadI16() => throw new NotImplementedException();
int IDeserializer.ReadI32() => throw new NotImplementedException();
long IDeserializer.ReadI64() => throw new NotImplementedException();
float IDeserializer.ReadF32() => throw new NotImplementedException();
double IDeserializer.ReadF64() => throw new NotImplementedException();
decimal IDeserializer.ReadDecimal() => throw new NotImplementedException();
DateTime IDeserializer.ReadDateTime() => throw new NotImplementedException();
void IDeserializer.ReadBytes(IBufferWriter<byte> writer) => throw new NotImplementedException();
void IDisposable.Dispose() => throw new NotImplementedException();
}
}
56 changes: 56 additions & 0 deletions src/serde/FixedWidth/FixedWidthDeserializer.Type.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System;
using System.Buffers;
using System.Globalization;

namespace Serde.FixedWidth
{
internal sealed partial class FixedWidthDeserializer : ITypeDeserializer
{
private const NumberStyles Numeric = NumberStyles.Integer | NumberStyles.AllowThousands;

int? ITypeDeserializer.SizeOpt => null;

private int _fieldIndex = -1;

T ITypeDeserializer.ReadValue<T>(ISerdeInfo info, int index, IDeserialize<T> deserialize)
=> deserialize.Deserialize(this);

string ITypeDeserializer.ReadString(ISerdeInfo info, int index) => _reader.ReadString(info, index);
bool ITypeDeserializer.ReadBool(ISerdeInfo info, int index) => _reader.ReadBool(info, index);
char ITypeDeserializer.ReadChar(ISerdeInfo info, int index) => _reader.ReadChar(info, index);
DateTime ITypeDeserializer.ReadDateTime(ISerdeInfo info, int index) => _reader.ReadDateTime(info, index);
decimal ITypeDeserializer.ReadDecimal(ISerdeInfo info, int index) => _reader.ReadNumber<decimal>(info, index, NumberStyles.Currency | NumberStyles.AllowLeadingSign);
float ITypeDeserializer.ReadF32(ISerdeInfo info, int index) => _reader.ReadNumber<float>(info, index, NumberStyles.Float);
double ITypeDeserializer.ReadF64(ISerdeInfo info, int index) => _reader.ReadNumber<double>(info, index, NumberStyles.Float);
short ITypeDeserializer.ReadI16(ISerdeInfo info, int index) => _reader.ReadNumber<short>(info, index, Numeric);
int ITypeDeserializer.ReadI32(ISerdeInfo info, int index) => _reader.ReadNumber<int>(info, index, Numeric);
long ITypeDeserializer.ReadI64(ISerdeInfo info, int index) => _reader.ReadNumber<long>(info, index, Numeric);
sbyte ITypeDeserializer.ReadI8(ISerdeInfo info, int index) => _reader.ReadNumber<sbyte>(info, index, Numeric);
ushort ITypeDeserializer.ReadU16(ISerdeInfo info, int index) => _reader.ReadNumber<ushort>(info, index, Numeric);
uint ITypeDeserializer.ReadU32(ISerdeInfo info, int index) => _reader.ReadNumber<uint>(info, index, Numeric);
ulong ITypeDeserializer.ReadU64(ISerdeInfo info, int index) => _reader.ReadNumber<ulong>(info, index, Numeric);
byte ITypeDeserializer.ReadU8(ISerdeInfo info, int index) => _reader.ReadNumber<byte>(info, index, Numeric);

void ITypeDeserializer.SkipValue(ISerdeInfo info, int index) { }

int ITypeDeserializer.TryReadIndex(ISerdeInfo info) => TryReadIndexWithName(info).Item1;

(int, string? errorName) ITypeDeserializer.TryReadIndexWithName(ISerdeInfo info) => TryReadIndexWithName(info);

private (int, string? errorName) TryReadIndexWithName(ISerdeInfo serdeInfo)
{
_fieldIndex++;
if (_fieldIndex == serdeInfo.FieldCount)
{
return (ITypeDeserializer.EndOfType, null);
}

return (_fieldIndex, null);
}

void ITypeDeserializer.ReadBytes(ISerdeInfo info, int index, IBufferWriter<byte> writer)
{
throw new NotImplementedException();
}
}
}
16 changes: 16 additions & 0 deletions src/serde/FixedWidth/FixedWidthDeserializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Contains implementations of data interfaces for core types

using Serde.FixedWidth.Reader;
using Serde.IO;
using System.Text;

namespace Serde.FixedWidth
{
/// <summary>
/// Defines a type which handles deserializing a fixed-width file.
/// </summary>
internal sealed partial class FixedWidthDeserializer(string document)
{
private readonly FixedWidthReader _reader = new(document);
}
}
40 changes: 40 additions & 0 deletions src/serde/FixedWidth/FixedWidthSerializer.Serialize.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Serde.FixedWidth
{
public sealed partial class FixedWidthSerializer : ISerializer
{
/// <inheritdoc/>
ITypeSerializer ISerializer.WriteType(ISerdeInfo info)
{
if (info.Kind is not InfoKind.CustomType)
{
throw new ArgumentException("Invalid type for WriteType: " + info.Kind);
}

return this;
}

void ISerializer.WriteString(string s) => writer.WriteRaw(s);
void ISerializer.WriteBool(bool b) => writer.WriteRaw(b);
void ISerializer.WriteBytes(ReadOnlyMemory<byte> bytes) => throw new NotImplementedException();
void ISerializer.WriteChar(char c) => writer.WriteRaw(c);
ITypeSerializer ISerializer.WriteCollection(ISerdeInfo info, int? count) => throw new NotImplementedException();
void ISerializer.WriteDateTime(DateTime dt) => writer.WriteRaw(dt);
void ISerializer.WriteDateTimeOffset(DateTimeOffset dto) => writer.WriteRaw(dto);
void ISerializer.WriteDecimal(decimal d) => writer.WriteRaw(d);
void ISerializer.WriteF32(float f) => writer.WriteRaw(f);
void ISerializer.WriteF64(double d) => writer.WriteRaw(d);
void ISerializer.WriteI16(short i16) => writer.WriteRaw(i16);
void ISerializer.WriteI32(int i32) => writer.WriteRaw(i32);
void ISerializer.WriteI64(long i64) => writer.WriteRaw(i64);
void ISerializer.WriteI8(sbyte b) => writer.WriteRaw(b);
void ISerializer.WriteNull() { }
void ISerializer.WriteU16(ushort u16) => writer.WriteRaw(u16);
void ISerializer.WriteU32(uint u32) => writer.WriteRaw(u32);
void ISerializer.WriteU64(ulong u64) => writer.WriteRaw(u64);
void ISerializer.WriteU8(byte b) => writer.WriteRaw(b);
}
}
33 changes: 33 additions & 0 deletions src/serde/FixedWidth/FixedWidthSerializer.Type.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;

namespace Serde.FixedWidth
{
public sealed partial class FixedWidthSerializer : ITypeSerializer
{
void ITypeSerializer.WriteValue<T>(ISerdeInfo typeInfo, int index, T value, ISerialize<T> serialize)
=> serialize.Serialize(value, this);

void ITypeSerializer.End(ISerdeInfo info) => writer.WriteRaw();
void ITypeSerializer.WriteBool(ISerdeInfo typeInfo, int index, bool b)
{
writer.WriteObject(typeInfo, index, b);
}
void ITypeSerializer.WriteChar(ISerdeInfo typeInfo, int index, char c) => writer.WriteObject(typeInfo, index, c);
void ITypeSerializer.WriteU8(ISerdeInfo typeInfo, int index, byte b) => writer.WriteObject(typeInfo, index, b);
void ITypeSerializer.WriteU16(ISerdeInfo typeInfo, int index, ushort u16) => writer.WriteObject(typeInfo, index, u16);
void ITypeSerializer.WriteU32(ISerdeInfo typeInfo, int index, uint u32) => writer.WriteObject(typeInfo, index, u32);
void ITypeSerializer.WriteU64(ISerdeInfo typeInfo, int index, ulong u64) => writer.WriteObject(typeInfo, index, u64);
void ITypeSerializer.WriteI8(ISerdeInfo typeInfo, int index, sbyte b) => writer.WriteObject(typeInfo, index, b);
void ITypeSerializer.WriteI16(ISerdeInfo typeInfo, int index, short i16) => writer.WriteObject(typeInfo, index, i16);
void ITypeSerializer.WriteI32(ISerdeInfo typeInfo, int index, int i32) => writer.WriteObject(typeInfo, index, i32);
void ITypeSerializer.WriteI64(ISerdeInfo typeInfo, int index, long i64) => writer.WriteObject(typeInfo, index, i64);
void ITypeSerializer.WriteF32(ISerdeInfo typeInfo, int index, float f) => writer.WriteObject(typeInfo, index, f);
void ITypeSerializer.WriteF64(ISerdeInfo typeInfo, int index, double d) => writer.WriteObject(typeInfo, index, d);
void ITypeSerializer.WriteDecimal(ISerdeInfo typeInfo, int index, decimal d) => writer.WriteObject(typeInfo, index, d);
void ITypeSerializer.WriteString(ISerdeInfo typeInfo, int index, string s) => writer.WriteObject(typeInfo, index, s);
void ITypeSerializer.WriteNull(ISerdeInfo typeInfo, int index) => writer.WriteObject(typeInfo, index, string.Empty);
void ITypeSerializer.WriteDateTime(ISerdeInfo typeInfo, int index, DateTime dt) => writer.WriteObject(typeInfo, index, dt);
void ITypeSerializer.WriteDateTimeOffset(ISerdeInfo typeInfo, int index, DateTimeOffset dt) => writer.WriteObject(typeInfo, index, dt);
void ITypeSerializer.WriteBytes(ISerdeInfo typeInfo, int index, ReadOnlyMemory<byte> bytes) => writer.WriteObject(typeInfo, index, bytes);
}
}
Loading