Skip to content

Commit aee99ec

Browse files
committed
refactored exceptions and stuff around diagnostics.
1 parent 1b843d8 commit aee99ec

16 files changed

Lines changed: 461 additions & 444 deletions

src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs

Lines changed: 40 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,21 @@
77
using PolylineAlgorithm;
88
using PolylineAlgorithm.Diagnostics;
99
using PolylineAlgorithm.Internal;
10+
using PolylineAlgorithm.Internal.Diagnostics;
1011
using PolylineAlgorithm.Internal.Logging;
1112
using System;
1213
using System.Runtime.CompilerServices;
1314
using System.Threading;
1415

15-
namespace PolylineAlgorithm.Abstraction
16-
{
16+
namespace PolylineAlgorithm.Abstraction {
1717
/// <summary>
1818
/// Decodes encoded polyline strings into sequences of geographic coordinates.
1919
/// Implements the <see cref="IPolylineDecoder{TPolyline, TCoordinate}"/> interface.
2020
/// </summary>
2121
/// <remarks>
2222
/// This abstract class provides a base implementation for decoding polylines, allowing subclasses to define how to handle specific polyline formats.
2323
/// </remarks>
24-
public abstract class AbstractPolylineDecoder<TPolyline, TCoordinate> : IPolylineDecoder<TPolyline, TCoordinate>
25-
{
24+
public abstract class AbstractPolylineDecoder<TPolyline, TCoordinate> : IPolylineDecoder<TPolyline, TCoordinate> {
2625
private readonly ILogger<AbstractPolylineDecoder<TPolyline, TCoordinate>> _logger;
2726

2827
/// <summary>
@@ -40,10 +39,15 @@ protected AbstractPolylineDecoder()
4039
/// <exception cref="ArgumentNullException">
4140
/// Thrown when <paramref name="encodingOptions"/> is <see langword="null" />
4241
/// </exception>
43-
protected AbstractPolylineDecoder(PolylineEncodingOptions encodingOptions)
44-
{
45-
Options = encodingOptions ?? throw new ArgumentNullException(nameof(encodingOptions));
46-
_logger = Options.LoggerFactory.CreateLogger<AbstractPolylineDecoder<TPolyline, TCoordinate>>();
42+
protected AbstractPolylineDecoder(PolylineEncodingOptions options) {
43+
if(options is null) {
44+
ExceptionGuard.ThrowArgumentNull(nameof(options));
45+
}
46+
47+
Options = options;
48+
_logger = Options
49+
.LoggerFactory
50+
.CreateLogger<AbstractPolylineDecoder<TPolyline, TCoordinate>>();
4751
}
4852

4953
/// <summary>
@@ -81,99 +85,83 @@ public IEnumerable<TCoordinate> Decode(TPolyline polyline)
8185
/// <exception cref="ArgumentNullException"/>
8286
/// <exception cref="ArgumentException"/>
8387
/// <exception cref="InvalidPolylineException"/>
84-
public IEnumerable<TCoordinate> Decode(TPolyline polyline, CancellationToken cancellationToken)
85-
{
88+
public IEnumerable<TCoordinate> Decode(TPolyline polyline, CancellationToken cancellationToken) {
8689
const string OperationName = nameof(Decode);
8790

8891
_logger?.LogOperationStartedDebug(OperationName);
8992

9093
ValidateNullPolyline(polyline, _logger);
9194

92-
ReadOnlyMemory<char> polylineSequence = GetReadOnlyMemory(in polyline);
95+
ReadOnlyMemory<char> sequence = GetReadOnlyMemory(in polyline);
9396

94-
ValidateEmptySequence(polylineSequence, _logger);
95-
ValidateFormat(polylineSequence, _logger);
97+
ValidateSequence(sequence, _logger);
98+
ValidateFormat(sequence, _logger);
9699

97-
int currentPosition = 0;
100+
int position = 0;
98101
int encodedLatitude = 0;
99102
int encodedLongitude = 0;
100103

101-
try
102-
{
103-
while (currentPosition < polylineSequence.Length)
104-
{
104+
try {
105+
while (position < sequence.Length) {
105106
cancellationToken.ThrowIfCancellationRequested();
106107

107-
if (!PolylineEncoding.TryReadValue(ref encodedLatitude, polylineSequence, ref currentPosition)
108-
|| !PolylineEncoding.TryReadValue(ref encodedLongitude, polylineSequence, ref currentPosition))
109-
{
108+
if (!PolylineEncoding.TryReadValue(ref encodedLatitude, sequence, ref position)
109+
|| !PolylineEncoding.TryReadValue(ref encodedLongitude, sequence, ref position)) {
110110
_logger?.LogOperationFailedDebug(OperationName);
111-
_logger?.LogInvalidPolylineWarning(currentPosition);
111+
_logger?.LogInvalidPolylineWarning(position);
112112

113-
OnInvalidPolyline(currentPosition, polylineSequence);
114-
InvalidPolylineException.Throw(currentPosition);
113+
OnInvalidPolyline(position, sequence);
114+
ExceptionGuard.ThrowInvalidPolylineFormat(position);
115115
}
116116

117117
double decodedLatitude = PolylineEncoding.Denormalize(encodedLatitude, Options.Precision);
118118
double decodedLongitude = PolylineEncoding.Denormalize(encodedLongitude, Options.Precision);
119119

120-
_logger?.LogDecodedCoordinateDebug(decodedLatitude, decodedLongitude, currentPosition);
120+
_logger?.LogDecodedCoordinateDebug(decodedLatitude, decodedLongitude, position);
121121

122122
yield return CreateCoordinate(decodedLatitude, decodedLongitude);
123123
}
124-
}
125-
finally
126-
{
124+
} finally {
127125
_logger?.LogOperationFinishedDebug(OperationName);
128126
}
129127
}
130128

131129
[MethodImpl(MethodImplOptions.AggressiveInlining)]
132-
private static void ValidateNullPolyline(TPolyline polyline, ILogger logger)
133-
{
134-
if (polyline is null)
135-
{
130+
private static void ValidateNullPolyline(TPolyline polyline, ILogger? logger) {
131+
if (polyline is null) {
136132
logger?.LogNullArgumentWarning(nameof(polyline));
137-
throw new ArgumentNullException(nameof(polyline), "Polyline argument cannot be null.");
133+
ExceptionGuard.ThrowArgumentNull(nameof(polyline));
138134
}
139135
}
140136

141137
[MethodImpl(MethodImplOptions.AggressiveInlining)]
142-
private static void ValidateEmptySequence(ReadOnlyMemory<char> polylineSequence, ILogger logger)
143-
{
144-
if (polylineSequence.Length < Defaults.Polyline.Block.Length.Min)
145-
{
138+
private static void ValidateSequence(ReadOnlyMemory<char> polylineSequence, ILogger? logger) {
139+
if (polylineSequence.Length < Defaults.Polyline.Block.Length.Min) {
146140
logger?.LogOperationFailedDebug(nameof(Decode));
147-
logger?.LogPolylineCannotBeShorterThanWarning(nameof(polylineSequence), polylineSequence.Length, Defaults.Polyline.Block.Length.Min);
141+
logger?.LogPolylineCannotBeShorterThanWarning(polylineSequence.Length, Defaults.Polyline.Block.Length.Min);
148142

149-
throw new ArgumentException(
150-
$"Polyline must be at least {Defaults.Polyline.Block.Length.Min} characters long, but was {polylineSequence.Length}.",
151-
nameof(polylineSequence));
143+
ExceptionGuard.ThrowInvalidPolylineLength(polylineSequence.Length, Defaults.Polyline.Block.Length.Min);
152144
}
153145
}
154146

155147
/// <summary>
156148
/// Validates the polyline format for allowed characters.
157149
/// </summary>
158150
[MethodImpl(MethodImplOptions.AggressiveInlining)]
159-
protected virtual void ValidateFormat(ReadOnlyMemory<char> polylineSequence, ILogger logger)
160-
{
161-
try
162-
{
163-
PolylineEncoding.Validation.ValidateFormat(polylineSequence.Span);
164-
}
165-
catch (ArgumentException ex)
166-
{
167-
logger?.LogWarning(ex.Message);
151+
protected virtual void ValidateFormat(ReadOnlyMemory<char> sequence, ILogger? logger) {
152+
try {
153+
PolylineEncoding.Validation.ValidateFormat(sequence.Span);
154+
} catch (ArgumentException ex) {
155+
logger?.LogInvalidPolylineFormatWarning(ex);
156+
168157
throw;
169158
}
170159
}
171160

172161
/// <summary>
173162
/// Hook for subclasses to handle invalid polyline cases.
174163
/// </summary>
175-
protected virtual void OnInvalidPolyline(int position, ReadOnlyMemory<char> polylineSequence)
176-
{
164+
protected virtual void OnInvalidPolyline(int position, ReadOnlyMemory<char> polylineSequence) {
177165
// Subclasses can override for custom error handling/logging.
178166
}
179167

src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ protected AbstractPolylineEncoder()
3939
/// </param>
4040
/// <exception cref="ArgumentNullException">Thrown when <paramref name="options"/> is <see langword="null" /></exception>
4141
protected AbstractPolylineEncoder(PolylineEncodingOptions options) {
42-
Options = options ?? throw new ArgumentNullException(nameof(options));
42+
if (options is null) {
43+
ExceptionGuard.ThrowArgumentNull(nameof(options));
44+
}
45+
46+
Options = options;
4347
_logger = Options
4448
.LoggerFactory
4549
.CreateLogger<AbstractPolylineEncoder<TCoordinate, TPolyline>>();
@@ -108,7 +112,8 @@ public TPolyline Encode(ReadOnlySpan<TCoordinate> coordinates) {
108112
_logger
109113
.LogCannotWriteValueToBufferWarning(position, consumed);
110114

111-
throw new InvalidOperationException(ExceptionMessages.GetCouldNotWriteEncodedValueToTheBuffer());
115+
ExceptionGuard.ThrowCouldNotWriteEncodedValueToBuffer();
116+
112117
}
113118

114119
consumed++;
@@ -143,7 +148,7 @@ static void ValidateEmptyCoordinates(ref ReadOnlySpan<TCoordinate> coordinates,
143148
logger
144149
.LogEmptyArgumentWarning(nameof(coordinates));
145150

146-
throw new ArgumentException(ExceptionMessages.GetArgumentCannotBeEmptyEnumerationMessage(), nameof(coordinates));
151+
ExceptionGuard.ThrwoArgumentCannotBeEmptyEnumerationMessage(nameof(coordinates));
147152
}
148153
}
149154
}

src/PolylineAlgorithm/Coordinate.cs

Lines changed: 67 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ namespace PolylineAlgorithm;
99
using System;
1010
using System.Diagnostics;
1111
using System.Globalization;
12+
using System.Runtime.CompilerServices;
1213
using System.Runtime.InteropServices;
1314

1415
/// <summary>
@@ -23,16 +24,14 @@ namespace PolylineAlgorithm;
2324
/// </remarks>
2425
[DebuggerDisplay("{ToString()}")]
2526
[StructLayout(LayoutKind.Auto)]
26-
public readonly struct Coordinate : IEquatable<Coordinate>
27-
{
27+
public readonly struct Coordinate : IEquatable<Coordinate> {
2828
/// <summary>
2929
/// Initializes a new instance of the <see cref="Coordinate"/> struct with default values (0) for <see cref="Latitude"/> and <see cref="Longitude"/>.
3030
/// </summary>
3131
/// <remarks>
3232
/// The default value (0, 0) is a valid coordinate (Gulf of Guinea), but is also treated as the "default" value by <see cref="IsDefault"/>.
3333
/// </remarks>
34-
public Coordinate()
35-
{
34+
public Coordinate() {
3635
Latitude = default;
3736
Longitude = default;
3837
}
@@ -50,10 +49,8 @@ public Coordinate()
5049
/// Thrown when <paramref name="latitude"/> is less than -90 or greater than 90,
5150
/// or when <paramref name="longitude"/> is less than -180 or greater than 180.
5251
/// </exception>
53-
public Coordinate(double latitude, double longitude)
54-
{
55-
Validation.ValidateLatitude(latitude);
56-
Validation.ValidateLongitude(longitude);
52+
public Coordinate(double latitude, double longitude) {
53+
Validator.Validate(latitude, longitude);
5754

5855
Latitude = latitude;
5956
Longitude = longitude;
@@ -86,8 +83,7 @@ public bool IsDefault()
8683
public override bool Equals(object? obj) => obj is Coordinate coordinate && Equals(coordinate);
8784

8885
/// <inheritdoc />
89-
public bool Equals(Coordinate other)
90-
{
86+
public bool Equals(Coordinate other) {
9187
return Latitude.Equals(other.Latitude) &&
9288
Longitude.Equals(other.Longitude);
9389
}
@@ -113,8 +109,7 @@ public bool Equals(Coordinate other, double tolerance)
113109
/// <returns>
114110
/// A string representation of the coordinate.
115111
/// </returns>
116-
public override string ToString()
117-
{
112+
public override string ToString() {
118113
return $"{{ {nameof(Latitude)}: {Latitude.ToString("G", CultureInfo.InvariantCulture)}, {nameof(Longitude)}: {Longitude.ToString("G", CultureInfo.InvariantCulture)} }}";
119114
}
120115

@@ -139,38 +134,80 @@ public override string ToString()
139134
public static bool operator !=(Coordinate left, Coordinate right) => !(left == right);
140135

141136
/// <summary>
142-
/// Provides validation methods for latitude and longitude values used in <see cref="Coordinate"/>.
137+
/// Provides static methods for validating latitude and longitude values used in <see cref="Coordinate"/>.
143138
/// </summary>
144-
internal static class Validation
145-
{
139+
/// <remarks>
140+
/// The <c>Validator</c> class ensures that latitude and longitude values are within their valid ranges:
141+
/// <list type="bullet">
142+
/// <item>
143+
/// <description>Latitude must be between -90 and 90 degrees.</description>
144+
/// </item>
145+
/// <item>
146+
/// <description>Longitude must be between -180 and 180 degrees.</description>
147+
/// </item>
148+
/// </list>
149+
/// If a value is out of range or not finite, an exception is thrown via <see cref="ExceptionGuard.ThrowCoordinateValueOutOfRange"/>.
150+
/// </remarks>
151+
public static class Validator {
146152
/// <summary>
147-
/// Validates that the specified latitude is within the valid range of -90 to 90 degrees and is a finite value.
153+
/// Validates that the specified latitude is within the valid range of -90 to 90 degrees.
148154
/// </summary>
149155
/// <param name="latitude">The latitude value to validate.</param>
150156
/// <exception cref="ArgumentOutOfRangeException">
151-
/// Thrown when <paramref name="latitude"/> is less than -90, greater than 90, or not a finite value.
157+
/// Thrown if <paramref name="latitude"/> is less than -90, greater than 90, or not a finite number.
152158
/// </exception>
153-
internal static void ValidateLatitude(double latitude)
154-
{
155-
if (latitude < -90 || latitude > 90 || !double.IsFinite(latitude))
156-
{
157-
throw new ArgumentOutOfRangeException(nameof(latitude), ExceptionMessages.FormatCoordinateValueMustBeBetween("latitude", -90, 90));
158-
}
159+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
160+
public static void ValidateLatitude(double latitude) {
161+
const double min = -90;
162+
const double max = 90;
163+
164+
ValidateValue(latitude, min, max, nameof(latitude));
159165
}
160166

161167
/// <summary>
162-
/// Validates that the specified longitude is within the valid range of -180 to 180 degrees and is a finite value.
168+
/// Validates that the specified longitude is within the valid range of -180 to 180 degrees.
163169
/// </summary>
164170
/// <param name="longitude">The longitude value to validate.</param>
165171
/// <exception cref="ArgumentOutOfRangeException">
166-
/// Thrown when <paramref name="longitude"/> is less than -180, greater than 180, or not a finite value.
172+
/// Thrown if <paramref name="longitude"/> is less than -180, greater than 180, or not a finite number.
167173
/// </exception>
168-
internal static void ValidateLongitude(double longitude)
169-
{
170-
if (longitude < -180 || longitude > 180 || !double.IsFinite(longitude))
171-
{
172-
throw new ArgumentOutOfRangeException(nameof(longitude), ExceptionMessages.FormatCoordinateValueMustBeBetween("Longitude", -180, 180));
174+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
175+
public static void ValidateLongitude(double longitude) {
176+
const double min = -180;
177+
const double max = 180;
178+
179+
ValidateValue(longitude, min, max, nameof(longitude));
180+
}
181+
182+
/// <summary>
183+
/// Validates that the specified value is finite and within the specified range.
184+
/// </summary>
185+
/// <param name="value">The value to validate.</param>
186+
/// <param name="min">The minimum allowed value (inclusive).</param>
187+
/// <param name="max">The maximum allowed value (inclusive).</param>
188+
/// <param name="paramName">The name of the parameter being validated.</param>
189+
/// <exception cref="ArgumentOutOfRangeException">
190+
/// Thrown if <paramref name="value"/> is not finite, less than <paramref name="min"/>, or greater than <paramref name="max"/>.
191+
/// </exception>
192+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
193+
public static void ValidateValue(double value, double min, double max, string paramName) {
194+
if (!double.IsFinite(value) || value < min || value > max) {
195+
ExceptionGuard.ThrowCoordinateValueOutOfRange(value, min, max, paramName);
173196
}
174197
}
198+
199+
/// <summary>
200+
/// Validates both latitude and longitude values for a coordinate.
201+
/// </summary>
202+
/// <param name="latitude">The latitude value to validate.</param>
203+
/// <param name="longitude">The longitude value to validate.</param>
204+
/// <exception cref="ArgumentOutOfRangeException">
205+
/// Thrown if either <paramref name="latitude"/> or <paramref name="longitude"/> is out of range or not finite.
206+
/// </exception>
207+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
208+
internal static void Validate(double latitude, double longitude) {
209+
ValidateLatitude(latitude);
210+
ValidateLongitude(longitude);
211+
}
175212
}
176213
}

src/PolylineAlgorithm/Diagnostics/InvalidPolylineException.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,16 @@ namespace PolylineAlgorithm.Diagnostics;
1717
/// This exception is used internally to indicate that a polyline string does not conform to the expected format or contains errors.
1818
/// </remarks>
1919
[DebuggerDisplay($"{nameof(InvalidPolylineException)}: {{ToString()}}")]
20-
[SuppressMessage("Roslynator", "RCS1194:Implement exception constructors", Justification = "Internal use only.")]
21-
[SuppressMessage("Design", "CA1032:Implement standard exception constructors", Justification = "Internal use only.")]
20+
[SuppressMessage("Roslynator", "RCS1194:Implement exception constructors", Justification = "This exception is intended for use by consumers of the library, but additional constructors are not required for its intended usage.")]
21+
[SuppressMessage("Design", "CA1032:Implement standard exception constructors", Justification = "Standard exception constructors are not necessary for the intended usage of this exception type.")]
2222
public sealed class InvalidPolylineException : Exception {
2323
/// <summary>
2424
/// Initializes a new instance of the <see cref="InvalidPolylineException"/> class with a specified error message.
2525
/// </summary>
2626
/// <param name="message">
2727
/// The error message that describes the reason for the exception.
2828
/// </param>
29-
private InvalidPolylineException(string message)
29+
internal InvalidPolylineException(string message)
3030
: base(message) { }
3131

3232
/// <summary>
@@ -41,6 +41,6 @@ private InvalidPolylineException(string message)
4141
internal static void Throw(long position) {
4242
Debug.Assert(position >= 0, "Position must be a non-negative value.");
4343

44-
throw new InvalidPolylineException(ExceptionMessages.FormatPolylineStringIsMalformed(position));
44+
ExceptionGuard.ThrowInvalidPolyline(position);
4545
}
4646
}

0 commit comments

Comments
 (0)