Skip to content

Commit 9db960a

Browse files
Copilotcaptainsafia
andcommitted
Fix member name formatting in validation errors
Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com>
1 parent e94aff2 commit 9db960a

File tree

4 files changed

+73
-35
lines changed

4 files changed

+73
-35
lines changed

src/Http/Http.Abstractions/src/Validation/ValidatableParameterInfo.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,14 @@ public virtual async Task ValidateAsync(object? value, ValidateContext context,
6666
}
6767

6868
context.ValidationContext.DisplayName = DisplayName;
69-
context.ValidationContext.MemberName = Name;
69+
70+
// Format member name according to naming policy if available
71+
var memberName = Name;
72+
if (context.SerializerOptions?.PropertyNamingPolicy is not null)
73+
{
74+
memberName = context.SerializerOptions.PropertyNamingPolicy.ConvertName(Name);
75+
}
76+
context.ValidationContext.MemberName = memberName;
7077

7178
var validationAttributes = GetValidationAttributes();
7279

src/Http/Http.Abstractions/src/Validation/ValidatablePropertyInfo.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,14 @@ public virtual async Task ValidateAsync(object? value, ValidateContext context,
7676
}
7777

7878
context.ValidationContext.DisplayName = DisplayName;
79-
context.ValidationContext.MemberName = Name;
79+
80+
// Format member name according to naming policy if available
81+
var memberName = Name;
82+
if (context.SerializerOptions?.PropertyNamingPolicy is not null)
83+
{
84+
memberName = context.SerializerOptions.PropertyNamingPolicy.ConvertName(Name);
85+
}
86+
context.ValidationContext.MemberName = memberName;
8087

8188
// Check required attribute first
8289
if (_requiredAttribute is not null || validationAttributes.TryGetRequiredAttribute(out _requiredAttribute))

src/Http/Http.Abstractions/src/Validation/ValidateContext.cs

Lines changed: 53 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -215,41 +215,65 @@ private string FormatErrorMessage(string errorMessage)
215215
return errorMessage;
216216
}
217217

218-
// Common pattern: "The {PropertyName} field is required."
219-
const string pattern = "The ";
220-
const string fieldPattern = " field ";
218+
// Pattern 1: "The {PropertyName} field is required."
219+
const string pattern1Start = "The ";
220+
const string pattern1Middle = " field ";
221221

222-
int startIndex = errorMessage.IndexOf(pattern, StringComparison.Ordinal);
223-
if (startIndex != 0)
224-
{
225-
return errorMessage; // Does not start with "The "
226-
}
222+
// Pattern 2: "The field {PropertyName} must be between X and Y."
223+
const string pattern2 = "The field ";
227224

228-
int endIndex = errorMessage.IndexOf(fieldPattern, pattern.Length, StringComparison.Ordinal);
229-
if (endIndex <= pattern.Length)
225+
// Try Pattern 1 first
226+
if (errorMessage.StartsWith(pattern1Start, StringComparison.Ordinal))
230227
{
231-
return errorMessage; // Does not contain " field " or it's too early
228+
int endIndex = errorMessage.IndexOf(pattern1Middle, pattern1Start.Length, StringComparison.Ordinal);
229+
if (endIndex > pattern1Start.Length)
230+
{
231+
// Extract the property name between "The " and " field "
232+
ReadOnlySpan<char> messageSpan = errorMessage.AsSpan();
233+
ReadOnlySpan<char> propertyNameSpan = messageSpan.Slice(pattern1Start.Length, endIndex - pattern1Start.Length);
234+
string propertyName = propertyNameSpan.ToString();
235+
236+
if (!string.IsNullOrWhiteSpace(propertyName))
237+
{
238+
// Format the property name with the naming policy
239+
string formattedPropertyName = SerializerOptions.PropertyNamingPolicy.ConvertName(propertyName);
240+
241+
// Construct the new error message by combining parts
242+
return string.Concat(
243+
pattern1Start,
244+
formattedPropertyName,
245+
messageSpan.Slice(endIndex).ToString()
246+
);
247+
}
248+
}
232249
}
233-
234-
// Extract the property name between "The " and " field "
235-
// Use ReadOnlySpan<char> for better performance
236-
ReadOnlySpan<char> messageSpan = errorMessage.AsSpan();
237-
ReadOnlySpan<char> propertyNameSpan = messageSpan.Slice(pattern.Length, endIndex - pattern.Length);
238-
string propertyName = propertyNameSpan.ToString();
239-
240-
if (string.IsNullOrWhiteSpace(propertyName))
250+
// Try Pattern 2
251+
else if (errorMessage.StartsWith(pattern2, StringComparison.Ordinal))
241252
{
242-
return errorMessage;
253+
// Find the word after "The field " and before " must"
254+
const string pattern2End = " must";
255+
int startPos = pattern2.Length;
256+
int endPos = errorMessage.IndexOf(pattern2End, startPos, StringComparison.Ordinal);
257+
258+
if (endPos > startPos)
259+
{
260+
string propertyName = errorMessage.Substring(startPos, endPos - startPos);
261+
if (!string.IsNullOrWhiteSpace(propertyName))
262+
{
263+
// Format the property name with the naming policy
264+
string formattedPropertyName = SerializerOptions.PropertyNamingPolicy.ConvertName(propertyName);
265+
266+
// Reconstruct the message
267+
ReadOnlySpan<char> errorSpan = errorMessage.AsSpan();
268+
return string.Concat(
269+
pattern2,
270+
formattedPropertyName,
271+
errorSpan.Slice(endPos));
272+
}
273+
}
243274
}
244275

245-
// Format the property name with the naming policy
246-
string formattedPropertyName = SerializerOptions.PropertyNamingPolicy.ConvertName(propertyName);
247-
248-
// Construct the new error message by combining parts
249-
return string.Concat(
250-
pattern,
251-
formattedPropertyName,
252-
messageSpan.Slice(endIndex).ToString()
253-
);
276+
// Return the original message if no patterns matched or formatting failed
277+
return errorMessage;
254278
}
255279
}

src/Http/Http.Abstractions/test/Validation/ValidatableTypeInfoTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ [new RequiredAttribute()])
139139
kvp =>
140140
{
141141
Assert.Equal("name", kvp.Key);
142-
Assert.Equal("The Name field is required.", kvp.Value.First());
142+
Assert.Equal("The name field is required.", kvp.Value.First());
143143
},
144144
kvp =>
145145
{
@@ -149,12 +149,12 @@ [new RequiredAttribute()])
149149
kvp =>
150150
{
151151
Assert.Equal("address.street", kvp.Key);
152-
Assert.Equal("The Street field is required.", kvp.Value.First());
152+
Assert.Equal("The street field is required.", kvp.Value.First());
153153
},
154154
kvp =>
155155
{
156156
Assert.Equal("address.city", kvp.Key);
157-
Assert.Equal("The City field is required.", kvp.Value.First());
157+
Assert.Equal("The city field is required.", kvp.Value.First());
158158
});
159159
}
160160

@@ -281,7 +281,7 @@ [new RangeAttribute(1, 100)])
281281
kvp =>
282282
{
283283
Assert.Equal("items[0].quantity", kvp.Key);
284-
Assert.Equal("The field quantity must be between 1 and 100.", kvp.Value.First());
284+
Assert.Equal("The field Quantity must be between 1 and 100.", kvp.Value.First());
285285
});
286286
}
287287

0 commit comments

Comments
 (0)