Table of Contents

Class ValidationArgs

Namespace
Trellis
Assembly
Trellis.Core.dll

Builds the Args dictionary carried by a FieldViolation or RuleViolation.

public static class ValidationArgs
Inheritance
ValidationArgs
Inherited Members

Remarks

Args are the machine-readable operands of a violation — the 50 in "must be at most 50", the 0 and 255 in "must be between 0 and 255". A client that has them can render its own localized message; a client that has only the English detail string cannot.

The values are ValidationArgValue — a closed union of text, number, boolean, and list — rather than object. A number therefore reaches the wire as a number, so a client can compare it without parsing, while the union still denies a producer the chance to hand over an arbitrary object and let culture-sensitive formatting leak into the payload. The numeric conversions are implicit, so ValidationArgs.Of("max", 255) needs no ceremony.

There is deliberately no IFormattable overload. Adding one would make ValidationArgs.Of("max", 255) ambiguous — an int converts to IFormattable by boxing and to ValidationArgValue by a user-defined conversion, and neither target is better than the other — so every numeric call site would stop compiling. Its absence is what lets the implicit conversions bind. A value with no numeric or textual meaning of its own, such as a timestamp, must therefore be formatted explicitly and invariantly by the caller.

Args are for operands, not for echoing what the caller sent. Never put the rejected value itself in here — a rejected password or token would then be reflected back in an error response and into whatever logs that response.

Fields

MaxAllowedMembers

The largest member list Allowed(IEnumerable<string>) will publish.

public const int MaxAllowedMembers = 64

Field Value

int

Remarks

A list a client cannot act on is not worth what it costs to send. The 248 ISO country names serialize to roughly 3 KB, attached to every rejection; a request carrying several invalid enum fields multiplies that, so a small request provokes a large response, which is an amplification vector rather than mere waste. Past a few dozen options a client is not rendering "choose one of…" from an error payload anyway — it wants a schema or an enumeration endpoint, and the error is the wrong channel.

The bound is a member count rather than a serialized length because member names are identifiers in every producer — CLR enum names, or the static field names behind a RequiredEnum — so their length is already bounded in practice, and a count is something a client can be told and can predict.

Methods

Allowed(IEnumerable<string>)

Builds the allowed entry naming the members a symbolic value may take.

public static ImmutableDictionary<string, ValidationArgValue> Allowed(IEnumerable<string> names)

Parameters

names IEnumerable<string>

The permitted member names. May be empty.

Returns

ImmutableDictionary<string, ValidationArgValue>

Remarks

Every producer that rejects a value for not being one of a fixed set routes through here, so none of them can disagree about the entry's name, the order of its members, or the point at which the list becomes too long to send. The producers derive their members from unrelated places — GetNames(Type) for query binding, a registry of declared statics for RequiredEnum — and nothing else would force those to line up. A client that compares the list across producers, or caches it, must not see a difference that is only ordering.

Ordinal sorting is what makes the order total and culture-independent; sorting by the current culture would let the same enum serialize differently on two machines.

Beyond MaxAllowedMembers the list is dropped whole and replaced by an allowedCount entry. Truncating instead would publish a false statement: a client cannot tell a shortened list from a complete one, so it would render "choose one of…" over a wrong set and reject valid input when validating against it. Omission is already a case every client handles, since a blank value carries no list either; the count is what distinguishes "too many to send" from "not applicable here".

Of(string, ValidationArgValue)

Builds a single-entry args dictionary.

public static ImmutableDictionary<string, ValidationArgValue> Of(string name, ValidationArgValue value)

Parameters

name string

The operand name, in camelCase.

value ValidationArgValue

The operand value.

Returns

ImmutableDictionary<string, ValidationArgValue>

Of(string, ValidationArgValue, string, ValidationArgValue)

Builds a two-entry args dictionary.

public static ImmutableDictionary<string, ValidationArgValue> Of(string name1, ValidationArgValue value1, string name2, ValidationArgValue value2)

Parameters

name1 string

The first operand name, in camelCase.

value1 ValidationArgValue

The first operand value.

name2 string

The second operand name, in camelCase.

value2 ValidationArgValue

The second operand value.

Returns

ImmutableDictionary<string, ValidationArgValue>

Of(params (string Name, ValidationArgValue Value)[])

Builds an args dictionary of any size.

public static ImmutableDictionary<string, ValidationArgValue> Of(params (string Name, ValidationArgValue Value)[] pairs)

Parameters

pairs (string Name, ValidationArgValue Value)[]

The operand names, in camelCase, paired with their values.

Returns

ImmutableDictionary<string, ValidationArgValue>

Remarks

A rule with three or more operands is ordinary — a scale-and-precision failure carries four — and without this overload a producer that needed one had to abandon ValidationArgs and assemble the dictionary by hand.