Skip to content

feat: validate collection and parsable query/header parameters#80

Open
graham-a3 wants to merge 3 commits into
mainfrom
feat/collection-query-header-validation
Open

feat: validate collection and parsable query/header parameters#80
graham-a3 wants to merge 3 commits into
mainfrom
feat/collection-query-header-validation

Conversation

@graham-a3

Copy link
Copy Markdown

Summary

Header/query validation previously returned 400 for any collection-typed parameter (string[], int[], …) and couldn't validate types outside a fixed scalar set. This adds both.

What changed

  • Collections[FromQuery]/[FromHeader] parameters like string[]/int[] now bind from repeated keys (?tag=a&tag=b) and are validated per element. Validation attributes apply to the collection itself (matching System.ComponentModel.DataAnnotations), so [MinLength]/[MaxLength]/[Length] constrain item count. Per-element rules remain the job of FromQuery<T> + RuleForEach.
  • Parsable typesUtils.TryCastValue now falls back to a parse convention (enumIParsable<T> → static TryParse(string, out T)) for types not in the scalar set, so any type the framework can bind is also validated. This covers regular enums and string-backed "smart enum" types.

Behaviour notes

  • Purely additive — these inputs returned 400 before, so no passing request regresses. Scalar and un-attributed parameters are unchanged (regression-tested).
  • Only arrays / StringValues bind for a bare [FromQuery]; List<T>/IEnumerable<T> collection properties go through the FromQuery<T> model binder (unchanged).

Testing

New integration tests across the full middleware pipeline for collections (query + header), closed/open smart enums (scalar + array), a real enum, and an un-attributed-parameter guard.

Full suite green: 1592 passed, 0 failed.

Header/query validation previously rejected any collection-typed parameter
(string[], int[], ...) with a 400, and could not validate types outside a
fixed scalar set. Two changes close both gaps:

- HeaderOrQuery reads the full StringValues for collection parameters and
  validates each element, applying any ValidationAttribute to the
  materialised collection (matching DataAnnotations semantics, so
  [MinLength]/[MaxLength]/[Length] constrain item count).
- Utils.TryCastValue falls back to a parse convention (enum, IParsable<T>,
  or a static TryParse(string, out T)) for types not in the scalar set,
  so any type the framework can bind is also validated. Includes support
  for string-backed "smart enum" types.

Adds integration tests covering collections (query + header), closed/open
smart enums, and a regression guard for un-attributed parameters.
Add a README section and an example endpoint covering repeated-key
collection binding, collection-level validation attributes, and enum /
parsable-type validation.
Scaffold a concise agent guide covering build/test commands, project layout,
the two validation paths (middleware vs custom binders), and repo conventions.
@graham-a3
graham-a3 requested review from AMCN41R and JonnyBooker July 17, 2026 11:23
@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

Summary - Unit Tests

Result Passed Failed Skipped
success 💚 1592 💔 0 💤 0

Summary - Test Coverage

Summary
Generated on: 07/17/2026 - 11:24:40
Coverage date: 07/17/2026 - 11:24:37
Parser: MultiReport (2x Cobertura)
Assemblies: 1
Classes: 23
Files: 22
Line coverage: 87% (746 of 857)
Covered lines: 746
Uncovered lines: 111
Coverable lines: 857
Total lines: 2168
Branch coverage: 75.7% (579 of 764)
Covered branches: 579
Total branches: 764
Method coverage: Feature is only available for sponsors

Coverage

A3.MinimalApiValidation - 87%
Name Line Branch
A3.MinimalApiValidation 87% 75.7%
A3.MinimalApiValidation.Binders.FromQuery`1 100% 71.4%
A3.MinimalApiValidation.Binders.QueryTypeInfo 93.4% 85%
A3.MinimalApiValidation.EndpointValidationExtensions 81.4% 50%
A3.MinimalApiValidation.EndpointValidatorOptions 100%
A3.MinimalApiValidation.Exceptions.BinderValidationFailedException 41.6%
A3.MinimalApiValidation.Internal.Filter.RequestModelValidationFilter`1 97% 72.7%
A3.MinimalApiValidation.Internal.Filter.ValidateOnlyFilter 93.3% 50%
A3.MinimalApiValidation.Internal.Filter.ValidateOnlyResponse 100%
A3.MinimalApiValidation.Internal.LogMessages 81.4% 46.4%
A3.MinimalApiValidation.Internal.Middleware.Body 90.3% 81.2%
A3.MinimalApiValidation.Internal.Middleware.BodyParameterInfo 93.7% 71.4%
A3.MinimalApiValidation.Internal.Middleware.BodyValidationResult 100%
A3.MinimalApiValidation.Internal.Middleware.HeaderOrQuery 100% 100%
A3.MinimalApiValidation.Internal.Middleware.ParameterAttributeInfo 93.1% 83.3%
A3.MinimalApiValidation.Internal.Middleware.ValidationMiddleware 94.6% 83.3%
A3.MinimalApiValidation.Internal.Utils 78.2% 73.5%
A3.MinimalApiValidation.ValidationAttributes.GreaterThanAttribute 82.2% 70%
A3.MinimalApiValidation.ValidationAttributes.GreaterThanOrEqualToAttribute 82.2% 70%
A3.MinimalApiValidation.ValidationAttributes.LessThanAttribute 84.4% 73.3%
A3.MinimalApiValidation.ValidationAttributes.LessThanOrEqualToAttribute 84.4% 73.3%
A3.MinimalApiValidation.ValidationAttributes.MaxAttribute 86.3% 96.6%
A3.MinimalApiValidation.ValidationAttributes.MinAttribute 86.3% 96.6%
A3.MinimalApiValidation.ValidationAttributes.ValidationContextExtensions 100% 75%

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant