-
-
Notifications
You must be signed in to change notification settings - Fork 0
feat: support ISpanParsable and IUtf8SpanParsable
#738
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
vbreuss
merged 2 commits into
main
from
topic/support-spanparsable-and-utf8spanparsable
Aug 26, 2025
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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]; | ||
| } | ||
|
|
||
| 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]; | ||
|
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 | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.