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
69 changes: 69 additions & 0 deletions Source/aweXpect.Core/Core/SpanWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#if NET8_0_OR_GREATER
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace aweXpect.Core;

/// <summary>
/// Wraps a span by providing access to the underlying data.
/// </summary>
public class SpanWrapper<T> : ICollection<T>
{
private readonly T[] _value;

/// <inheritdoc cref="SpanWrapper{T}" />
public SpanWrapper(ReadOnlySpan<T> span)
{
_value = span.ToArray();
}

/// <inheritdoc cref="SpanWrapper{T}" />
public SpanWrapper(Span<T> span)
{
_value = span.ToArray();
}

/// <summary>
/// Creates a new span over the wrapped array.
/// </summary>
public Span<T> AsSpan() => _value.AsSpan();

/// <inheritdoc cref="IEnumerable{T}.GetEnumerator()" />
public IEnumerator<T> GetEnumerator()
=> (_value as IEnumerable<T>).GetEnumerator();

/// <inheritdoc cref="IEnumerable.GetEnumerator()" />
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();

/// <inheritdoc cref="ICollection{T}.Add(T)" />
public void Add(T item)
=> throw new NotSupportedException("You may not change a SpanWrapper!");

/// <inheritdoc cref="ICollection{T}.Clear()" />
public void Clear()
=> throw new NotSupportedException("You may not change a SpanWrapper!");

/// <inheritdoc cref="ICollection{T}.Contains(T)" />
public bool Contains(T item)
=> _value.Contains(item);

/// <inheritdoc cref="ICollection{T}.CopyTo(T[], int)" />
public void CopyTo(T[] array, int arrayIndex)
=> _value.CopyTo(array, arrayIndex);

/// <inheritdoc cref="ICollection{T}.Remove(T)" />
public bool Remove(T item)
=> throw new NotSupportedException("You may not change a SpanWrapper!");

/// <inheritdoc cref="ICollection{T}.Count" />
public int Count
=> _value.Length;

/// <inheritdoc cref="ICollection{T}.IsReadOnly" />
public bool IsReadOnly
=> true;
}
#endif
20 changes: 20 additions & 0 deletions Source/aweXpect.Core/Expect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,26 @@ public static IThat<T> That<T>(Task<T> subject,
=> new ThatSubject<T>(new ExpectationBuilder<T>(
new AsyncValueSource<T>(subject), doNotPopulateThisValue));

#if NET8_0_OR_GREATER
/// <summary>
/// Specify expectations for the current <paramref name="subject" />.
/// </summary>
public static IThat<SpanWrapper<T>> That<T>(ReadOnlySpan<T> subject,
[CallerArgumentExpression("subject")] string doNotPopulateThisValue = "")
=> new ThatSubject<SpanWrapper<T>>(new ExpectationBuilder<SpanWrapper<T>>(
new ValueSource<SpanWrapper<T>>(new SpanWrapper<T>(subject)), doNotPopulateThisValue));
#endif

#if NET8_0_OR_GREATER
/// <summary>
/// Specify expectations for the current <paramref name="subject" />.
/// </summary>
public static IThat<SpanWrapper<T>> That<T>(Span<T> subject,
[CallerArgumentExpression("subject")] string doNotPopulateThisValue = "")
=> new ThatSubject<SpanWrapper<T>>(new ExpectationBuilder<SpanWrapper<T>>(
new ValueSource<SpanWrapper<T>>(new SpanWrapper<T>(subject)), doNotPopulateThisValue));
#endif

#if NET8_0_OR_GREATER
/// <summary>
/// Specify expectations for the current asynchronous <paramref name="subject" />.
Expand Down
3 changes: 2 additions & 1 deletion Source/aweXpect/Results/IsParsableResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
namespace aweXpect.Results;

/// <summary>
/// The result for verifying that the subject is parsable into <typeparamref name="TType" />.
/// The result for verifying that the <see cref="IParsable{TType}" /> subject is parsable
/// into <typeparamref name="TType" />.
/// </summary>
public class IsParsableResult<TType>(
ExpectationBuilder expectationBuilder,
Expand Down
35 changes: 35 additions & 0 deletions Source/aweXpect/Results/IsSpanParsableResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#if NET8_0_OR_GREATER
using System;
using aweXpect.Core;

namespace aweXpect.Results;

/// <summary>
/// The result for verifying that the <see cref="ISpanParsable{TType}" /> subject is parsable
/// into <typeparamref name="TType" />.
/// </summary>
public class IsSpanParsableResult<TType>(
ExpectationBuilder expectationBuilder,
IThat<SpanWrapper<char>> subject,
IFormatProvider? formatProvider)
: AndOrResult<SpanWrapper<char>, IThat<SpanWrapper<char>>>(expectationBuilder, subject)
where TType : ISpanParsable<TType>
{
private readonly ExpectationBuilder _expectationBuilder = expectationBuilder;

/// <summary>
/// Gives access to the parsed value.
/// </summary>
public IThat<TType> Which
=> new ThatSubject<TType>(_expectationBuilder
.ForWhich<SpanWrapper<char>, TType?>(d =>
{
if (TType.TryParse(d.AsSpan(), formatProvider, out TType? result))
{
return result;
}

return default;
}, " which "));
}
#endif
35 changes: 35 additions & 0 deletions Source/aweXpect/Results/IsUtf8SpanParsableResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#if NET8_0_OR_GREATER
using System;
using aweXpect.Core;

namespace aweXpect.Results;

/// <summary>
/// The result for verifying that the <see cref="IUtf8SpanParsable{TType}" /> subject is parsable
/// into <typeparamref name="TType" />.
/// </summary>
public class IsUtf8SpanParsableResult<TType>(
ExpectationBuilder expectationBuilder,
IThat<SpanWrapper<byte>> subject,
IFormatProvider? formatProvider)
: AndOrResult<SpanWrapper<byte>, IThat<SpanWrapper<byte>>>(expectationBuilder, subject)
where TType : IUtf8SpanParsable<TType>
{
private readonly ExpectationBuilder _expectationBuilder = expectationBuilder;

/// <summary>
/// Gives access to the parsed value.
/// </summary>
public IThat<TType> Which
=> new ThatSubject<TType>(_expectationBuilder
.ForWhich<SpanWrapper<byte>, TType?>(d =>
{
if (TType.TryParse(d.AsSpan(), formatProvider, out TType? result))
{
return result;
}

return default;
}, " which "));
}
#endif
218 changes: 218 additions & 0 deletions Source/aweXpect/That/Spans/ThatSpan.IsParsableInto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
#if NET8_0_OR_GREATER
using System;
using aweXpect.Core;
using aweXpect.Core.Constraints;
using aweXpect.Helpers;
using aweXpect.Results;

namespace aweXpect;

public static partial class ThatSpan
{
/// <summary>
/// Verifies that the subject is parsable into type <typeparamref name="TType" />.
/// </summary>
/// <remarks>
/// The optional parameter <paramref name="formatProvider" /> provides culture-specific formatting information
/// in the call to <see cref="IParsable{TType}.Parse(string, IFormatProvider)" />.
/// </remarks>
public static IsSpanParsableResult<TType> IsParsableInto<TType>(
this IThat<SpanWrapper<char>> source,
IFormatProvider? formatProvider = null)
where TType : ISpanParsable<TType>
=> new(source.Get().ExpectationBuilder.AddConstraint((it, grammars)
=> new IsParsableIntoConstraint<TType>(it, grammars, formatProvider)),
source,
formatProvider);

/// <summary>
/// Verifies that the subject is parsable into type <typeparamref name="TType" />.
/// </summary>
/// <remarks>
/// The optional parameter <paramref name="formatProvider" /> provides culture-specific formatting information
/// in the call to <see cref="IParsable{TType}.Parse(string, IFormatProvider)" />.
/// </remarks>
public static IsUtf8SpanParsableResult<TType> IsParsableInto<TType>(
this IThat<SpanWrapper<byte>> source,
IFormatProvider? formatProvider = null)
where TType : IUtf8SpanParsable<TType>
=> new(source.Get().ExpectationBuilder.AddConstraint((it, grammars)
=> new IsUtf8ParsableIntoConstraint<TType>(it, grammars, formatProvider)),
source,
formatProvider);

/// <summary>
/// Verifies that the subject is not parsable into type <typeparamref name="TType" />.
/// </summary>
/// <remarks>
/// The optional parameter <paramref name="formatProvider" /> provides culture-specific formatting information
/// in the call to <see cref="IParsable{TType}.Parse(string, IFormatProvider)" />.
/// </remarks>
public static AndOrResult<SpanWrapper<char>, IThat<SpanWrapper<char>>> IsNotParsableInto<TType>(
this IThat<SpanWrapper<char>> source,
IFormatProvider? formatProvider = null)
where TType : ISpanParsable<TType>
=> new(source.Get().ExpectationBuilder.AddConstraint((it, grammars)
=> new IsParsableIntoConstraint<TType>(it, grammars, formatProvider).Invert()),
source);

/// <summary>
/// Verifies that the subject is not parsable into type <typeparamref name="TType" />.
/// </summary>
/// <remarks>
/// The optional parameter <paramref name="formatProvider" /> provides culture-specific formatting information
/// in the call to <see cref="IParsable{TType}.Parse(string, IFormatProvider)" />.
/// </remarks>
public static AndOrResult<SpanWrapper<byte>, IThat<SpanWrapper<byte>>> IsNotParsableInto<TType>(
this IThat<SpanWrapper<byte>> source,
IFormatProvider? formatProvider = null)
where TType : IUtf8SpanParsable<TType>
=> new(source.Get().ExpectationBuilder.AddConstraint((it, grammars)
=> new IsUtf8ParsableIntoConstraint<TType>(it, grammars, formatProvider).Invert()),
source);

private sealed class IsParsableIntoConstraint<TType> : ConstraintResult.WithValue<SpanWrapper<char>>,
IValueConstraint<SpanWrapper<char>>
where TType : ISpanParsable<TType>
{
private readonly IFormatProvider? _formatProvider;
private readonly string _it;
private string? _exceptionMessage;

public IsParsableIntoConstraint(string it,
ExpectationGrammars grammars,
IFormatProvider? formatProvider) : base(grammars)
{
_it = it;
_formatProvider = formatProvider;
FurtherProcessingStrategy = FurtherProcessingStrategy.IgnoreResult;
}

public ConstraintResult IsMetBy(SpanWrapper<char> actual)
{
Actual = actual;

try
{
_ = TType.Parse(actual.AsSpan(), _formatProvider);
Outcome = Outcome.Success;
}
catch (Exception ex)
{
if (string.IsNullOrEmpty(ex.Message) || ex.Message.Length < 2)
{
_exceptionMessage = "an unknown error occurred";
}
else
{
_exceptionMessage = char.ToLowerInvariant(ex.Message[0]) + ex.Message[1..^1];
Comment thread
vbreuss marked this conversation as resolved.
}

Outcome = Outcome.Failure;
}

return this;
}

protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null)
{
stringBuilder.Append("is parsable into ");
Formatter.Format(stringBuilder, typeof(TType));
if (_formatProvider is not null)
{
stringBuilder.Append(" using ");
Formatter.Format(stringBuilder, _formatProvider);
}
}

protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null)
=> stringBuilder.Append(_it).Append(" was not because ").Append(_exceptionMessage);

protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null)
{
stringBuilder.Append("is not parsable into ");
Formatter.Format(stringBuilder, typeof(TType));
if (_formatProvider is not null)
{
stringBuilder.Append(" using ");
Formatter.Format(stringBuilder, _formatProvider);
}
}

protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null)
=> stringBuilder.Append(_it).Append(" was");
}

private sealed class IsUtf8ParsableIntoConstraint<TType> : ConstraintResult.WithValue<SpanWrapper<byte>>,
IValueConstraint<SpanWrapper<byte>>
where TType : IUtf8SpanParsable<TType>
{
private readonly IFormatProvider? _formatProvider;
private readonly string _it;
private string? _exceptionMessage;

public IsUtf8ParsableIntoConstraint(string it,
ExpectationGrammars grammars,
IFormatProvider? formatProvider) : base(grammars)
{
_it = it;
_formatProvider = formatProvider;
FurtherProcessingStrategy = FurtherProcessingStrategy.IgnoreResult;
}

public ConstraintResult IsMetBy(SpanWrapper<byte> actual)
{
Actual = actual;

try
{
_ = TType.Parse(actual.AsSpan(), _formatProvider);
Outcome = Outcome.Success;
}
catch (Exception ex)
{
if (string.IsNullOrEmpty(ex.Message) || ex.Message.Length < 2)
{
_exceptionMessage = "an unknown error occurred";
}
else
{
_exceptionMessage = char.ToLowerInvariant(ex.Message[0]) + ex.Message[1..^1];
Comment thread
vbreuss marked this conversation as resolved.
}

Outcome = Outcome.Failure;
}

return this;
}

protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null)
{
stringBuilder.Append("is parsable into ");
Formatter.Format(stringBuilder, typeof(TType));
if (_formatProvider is not null)
{
stringBuilder.Append(" using ");
Formatter.Format(stringBuilder, _formatProvider);
}
}

protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null)
=> stringBuilder.Append(_it).Append(" was not because ").Append(_exceptionMessage);

protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null)
{
stringBuilder.Append("is not parsable into ");
Formatter.Format(stringBuilder, typeof(TType));
if (_formatProvider is not null)
{
stringBuilder.Append(" using ");
Formatter.Format(stringBuilder, _formatProvider);
}
}

protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null)
=> stringBuilder.Append(_it).Append(" was");
}
}
#endif
Loading
Loading