Skip to content

Commit

Permalink
feat: improve handling of custom types (#100)
Browse files Browse the repository at this point in the history
  • Loading branch information
Seddryck authored Jan 19, 2025
1 parent f38bb45 commit 23268a1
Show file tree
Hide file tree
Showing 21 changed files with 1,073 additions and 618 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
Expand Down Expand Up @@ -59,18 +60,18 @@ public void WithFieldsNamed_ShouldSetFields()
var descriptor = new SchemaDescriptorBuilder()
.Indexed()
.WithField<int>((f) => f.WithName("foo"))
.WithField<bool>((f) => f.WithName("bar").WithFormat("%Y-%M-%d"))
.WithField<bool>((f) => f.WithName("bar"))
.Build();
Assert.That(descriptor, Is.Not.Null);
Assert.That(descriptor!.Fields, Has.Length.EqualTo(2));
Assert.That(descriptor.Fields[0].RuntimeType, Is.EqualTo(typeof(int)));
Assert.That(descriptor.Fields[0].Name, Is.EqualTo("foo"));
Assert.That(descriptor.Fields[0].Format, Is.Null);
Assert.That(descriptor.Fields[0].Format, Is.Not.Null);
Assert.That(descriptor.Fields.TryGetValue("foo", out var _), Is.True);
Assert.That(descriptor.Fields.TryGetValue("qrk", out var _), Is.False);
Assert.That(descriptor.Fields[1].Name, Is.EqualTo("bar"));
Assert.That(descriptor.Fields[1].RuntimeType, Is.EqualTo(typeof(bool)));
Assert.That(descriptor.Fields[1].Format, Is.EqualTo("%Y-%M-%d"));
Assert.That(descriptor.Fields[1].Format, Is.EqualTo(IFormatDescriptor.None));
}

[Test]
Expand All @@ -79,18 +80,23 @@ public void NamedWithFields_ShouldSetFields()
var descriptor = new SchemaDescriptorBuilder()
.Named()
.WithField<int>("foo")
.WithField<DateTime>("bar", (f) => f.WithFormat("%Y-%M-%d"))
.WithTemporalField<DateTime>("bar", (f) => f.WithFormat("yyyy-MM-dd"))
.Build();
Assert.That(descriptor, Is.Not.Null);
Assert.That(descriptor!.IsMatchingByName, Is.True);
Assert.That(descriptor!.IsMatchingByIndex, Is.False);
Assert.That(descriptor!.Fields, Has.Length.EqualTo(2));
Assert.That(descriptor.Fields["foo"].RuntimeType, Is.EqualTo(typeof(int)));
Assert.That(descriptor.Fields["foo"].Name, Is.EqualTo("foo"));
Assert.That(descriptor.Fields["foo"].Format, Is.Null);
Assert.That(descriptor.Fields["foo"].Format, Is.TypeOf<NumericFormatDescriptor>());
var formatInfo = (descriptor.Fields["foo"].Format as NumericFormatDescriptor)!.Culture as NumberFormatInfo;
Assert.That(formatInfo!.NumberGroupSeparator, Is.EqualTo(","));

Assert.That(descriptor.Fields["bar"].RuntimeType, Is.EqualTo(typeof(DateTime)));
Assert.That(descriptor.Fields["bar"].Name, Is.EqualTo("bar"));
Assert.That(descriptor.Fields["bar"].Format, Is.EqualTo("%Y-%M-%d"));
Assert.That(descriptor.Fields["bar"].Format, Is.TypeOf<TemporalFormatDescriptor>());
var format = descriptor.Fields["bar"].Format as TemporalFormatDescriptor;
Assert.That(format!.Pattern, Is.EqualTo("yyyy-MM-dd"));

foreach (var field in descriptor.Fields)
Assert.That(field.Name, Is.Not.Null.Or.Empty);
Expand All @@ -107,16 +113,17 @@ private static IEnumerable<ISchemaDescriptorBuilder> CreateBuilder()
public void InterfaceWithFields_ShouldSetFields(ISchemaDescriptorBuilder builder)
{
builder.WithField(typeof(int), "foo", (x) => x);
builder.WithField(typeof(DateTime), "bar", (f) => f.WithFormat("%Y-%M-%d"));
builder.WithTemporalField(typeof(DateTime), "bar", (f) => f.WithFormat("yyyy-MM-dd"));
var descriptor = builder.Build();
Assert.That(descriptor, Is.Not.Null);
Assert.That(descriptor!.Fields, Has.Length.EqualTo(2));
Assert.That(descriptor.Fields["foo"].RuntimeType, Is.EqualTo(typeof(int)));
Assert.That(descriptor.Fields["foo"].Name, Is.EqualTo("foo"));
Assert.That(descriptor.Fields["foo"].Format, Is.Null);
Assert.That(descriptor.Fields["foo"].Format, Is.TypeOf<NumericFormatDescriptor>());
Assert.That(descriptor.Fields["bar"].RuntimeType, Is.EqualTo(typeof(DateTime)));
Assert.That(descriptor.Fields["bar"].Name, Is.EqualTo("bar"));
Assert.That(descriptor.Fields["bar"].Format, Is.EqualTo("%Y-%M-%d"));
var format = descriptor.Fields["bar"].Format as TemporalFormatDescriptor;
Assert.That(format!.Pattern, Is.EqualTo("yyyy-MM-dd"));

foreach (var field in descriptor.Fields)
Assert.That(field.Name, Is.Not.Null.Or.Empty);
Expand Down Expand Up @@ -151,7 +158,7 @@ public void IndexedWithSequences_ShouldSetSequences()
{
var descriptor = new SchemaDescriptorBuilder()
.Indexed()
.WithNumericField<int>((f) => f.WithSequence("NaN", "0"))
.WithNumberField<int>((f) => f.WithSequence("NaN", "0"))
.WithField<string>((f) => f.WithSequence("", "Unknown"))
.Build();
Assert.That(descriptor, Is.Not.Null);
Expand Down
66 changes: 49 additions & 17 deletions PocketCsvReader.Testing/CsvDataReaderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Buffers;
using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces;
using PocketCsvReader.Configuration;
using System.Globalization;

namespace PocketCsvReader.Testing;

Expand Down Expand Up @@ -204,8 +205,10 @@ public void Read_WithSchemaAndFormatDate_CorrectParsing(string record)
.WithSchema(
(schema) => schema
.Named()
.WithField<DateTime>("bar", (f) => f.WithFormat("dd-MM-yy"))
.WithField<DateTime>("foo", (f) => f.WithFormat("yyyy/MM/dd"))
.WithTemporalField<DateTime>("bar", (f) => f.WithFormat("dd-MM-yy"))
.WithTemporalField<DateTime>("foo", (f) => f.WithFormat(
"yyyy/MM/dd"
, (fmt) => fmt.WithDateSeparator("/")))
);
using var dataReader = builder.Build().ToDataReader(buffer);
dataReader.Read();
Expand Down Expand Up @@ -234,8 +237,8 @@ public void Read_WithSchemaAndFormatTime_CorrectParsing(string record)
.WithSchema(
(schema) => schema
.Named()
.WithField<TimeOnly>("foo", (f) => f.WithFormat("HH:mm:ss"))
.WithField<TimeOnly>("bar", (f) => f.WithFormat("hh:mm:ss tt"))
.WithTemporalField<TimeOnly>("foo", (f) => f.WithFormat("HH:mm:ss"))
.WithTemporalField<TimeOnly>("bar", (f) => f.WithFormat("hh:mm:ss tt"))
);
using var dataReader = builder.Build().ToDataReader(buffer);
dataReader.Read();
Expand All @@ -262,8 +265,8 @@ public void Read_WithSchemaAndFormatDateTime_CorrectParsing(string record)
.WithSchema(
(schema) => schema
.Named()
.WithField<DateTime>("foo", (f) => f.WithFormat("yyyy-MM-ddTHH:mm:ss"))
.WithField<DateTime>("bar", (f) => f.WithFormat("MM/dd/yyyy hh:mm:ss tt"))
.WithTemporalField<DateTime>("foo", (f) => f.WithFormat("yyyy-MM-ddTHH:mm:ss"))
.WithTemporalField<DateTime>("bar", (f) => f.WithFormat("MM/dd/yyyy hh:mm:ss tt"))
);
using var dataReader = builder.Build().ToDataReader(buffer);
dataReader.Read();
Expand Down Expand Up @@ -291,8 +294,8 @@ public void Read_WithSchemaAndFormatDateTimeOffset_CorrectParsing(string record)
.WithSchema(
(schema) => schema
.Named()
.WithField<DateTimeOffset>("foo", (f) => f.WithFormat("yyyy-MM-ddTHH:mm:ssZ"))
.WithField<DateTimeOffset>("bar", (f) => f.WithFormat("MM/dd/yyyy hh:mm:ss tt zzz"))
.WithTemporalField<DateTimeOffset>("foo", (f) => f.WithFormat("yyyy-MM-ddTHH:mm:ssZ"))
.WithTemporalField<DateTimeOffset>("bar", (f) => f.WithFormat("MM/dd/yyyy hh:mm:ss tt zzz"))
);
using var dataReader = builder.Build().ToDataReader(buffer);
dataReader.Read();
Expand All @@ -319,8 +322,8 @@ public void GetValue_WithSchemaAndFormatDateTimeAndShort_CorrectParsing(string r
.WithSchema(
(schema) => schema
.Named()
.WithField<DateTime>("foo", (f) => f.WithFormat("yyyy-MM-ddTHH:mm:ss"))
.WithField<short>("bar")
.WithTemporalField<DateTime>("foo", (f) => f.WithFormat("yyyy-MM-ddTHH:mm:ss"))
.WithIntegerField<short>("bar")
);
using var dataReader = builder.Build().ToDataReader(buffer);
dataReader.Read();
Expand Down Expand Up @@ -356,7 +359,7 @@ public void GetFieldValue_WithSchemaAndFormatDateTimeAndShort_CorrectParsing(str
.WithSchema(
(schema) => schema
.Named()
.WithField<DateTime>("foo", (f) => f.WithFormat("yyyy-MM-ddTHH:mm:ss"))
.WithField<DateTime>("foo")
.WithField<short>("bar")
);
using var dataReader = builder.Build().ToDataReader(buffer);
Expand Down Expand Up @@ -390,8 +393,8 @@ public void GetDecimal_WithDecimalCharAndGroupChar_CorrectParsing(string record)
.WithSchema(
(schema) => schema
.Named()
.WithNumericField<decimal>("foo", (f) => f.WithDecimalChar('.').WithGroupChar(','))
.WithNumericField<decimal>("bar", (f) => f.WithDecimalChar(',').WithGroupChar('.'))
.WithNumberField<decimal>("foo", (f) => f.WithFormat((fmt) => fmt.WithDecimalChar('.').WithGroupChar(',')))
.WithNumberField<decimal>("bar", (f) => f.WithFormat((fmt) => fmt.WithDecimalChar(',').WithGroupChar('.')))
);
using var dataReader = builder.Build().ToDataReader(buffer);
dataReader.Read();
Expand Down Expand Up @@ -424,8 +427,8 @@ public void GetInt32_WithGroupChar_CorrectParsing(string record)
.WithSchema(
(schema) => schema
.Named()
.WithNumericField<int>("foo", (f) => f.WithGroupChar(','))
.WithNumericField<int>("bar", (f) => f.WithGroupChar(' '))
.WithNumberField<int>("foo", (f) => f.WithFormat((fmt) => fmt.WithGroupChar(",")))
.WithNumberField<int>("bar", (f) => f.WithFormat((fmt) => fmt.WithGroupChar(" ")))
);
using var dataReader = builder.Build().ToDataReader(buffer);
dataReader.Read();
Expand Down Expand Up @@ -467,7 +470,7 @@ public void GetInt32_WithoutGroupChar_ThrowsFormat(string record)
.WithSchema(
(schema) => schema
.Named()
.WithNumericField<int>("foo", (f) => f.WithoutGroupChar())
.WithNumberField<int>("foo", (f) => f.WithFormat((fmt) => fmt.WithoutGroupChar()))
);
using var dataReader = builder.Build().ToDataReader(buffer);
dataReader.Read();
Expand All @@ -493,7 +496,7 @@ public void GetInt32_WithoutGroupChar_CorrectParsing(string record)
.WithSchema(
(schema) => schema
.Named()
.WithNumericField<int>("foo", (f) => f.WithoutGroupChar())
.WithNumberField<int>("foo", (f) => f.WithFormat((fmt) => fmt.WithoutGroupChar()))
);
using var dataReader = builder.Build().ToDataReader(buffer);
dataReader.Read();
Expand Down Expand Up @@ -636,6 +639,35 @@ public void Read_WithSchemaNotStringAndSequences_CorrectParsing(string record)
Assert.That(dataReader.GetInt32(1), Is.EqualTo(15));
}

[Test]
[TestCase("foo;bar\r\n2025-02;Q1.25")]
public void Read_WithSchemaParsableTypes_CorrectParsing(string record)
{
var buffer = new MemoryStream(Encoding.UTF8.GetBytes(record));

var builder = new CsvReaderBuilder()
.WithDialect(
(dialect) => dialect
.WithDelimiter(';')
.WithHeader(true))
.WithSchema(
(schema) => schema
.Named()
.WithCustomField<YearMonth>("foo", (f) => f.WithFormat(new DateTimeFormatInfo() { YearMonthPattern = "yyyy-MM" }))
.WithCustomField<YearQuarter>("bar", (f) => f.WithFormat(new YearQuarterFormatProvider() { YearQuarterPattern="Qq.yy" }))
);
using var dataReader = builder.Build().ToDataReader(buffer);
dataReader.Read();
Assert.That(dataReader.FieldCount, Is.EqualTo(2));
Assert.That(dataReader.GetName(0), Is.EqualTo("foo"));
Assert.That(dataReader.GetName(1), Is.EqualTo("bar"));
Assert.That(dataReader.GetFieldType(0), Is.EqualTo(typeof(YearMonth)));
Assert.That(dataReader.GetFieldType(1), Is.EqualTo(typeof(YearQuarter)));
Assert.That(dataReader.GetValue(0), Is.EqualTo(new YearMonth(2025,2)));
Assert.That(dataReader.GetValue(1), Is.EqualTo(new YearQuarter(2025, 1)));
}


[Test]
[TestCase("Ansi")]
[TestCase("Utf16-BE")]
Expand Down
Loading

0 comments on commit 23268a1

Please sign in to comment.