---
title: Raise a validation failure
summary: Build and throw a validation exception carrying structured failure detail.
status: draft
owner: saif-docgen
last_reviewed: 2026-09-15
library: SAIF.Framework.Common
reference_type: package
operation_id: OP-019
trigger: Caller detects invalid input or state and needs to surface property-level errors
---

# Raise a validation failure

Build and throw a validation exception carrying structured failure detail, triggered when caller detects invalid input or state and needs to surface property-level errors.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-019 |
| **Trigger** | Caller detects invalid input or state and needs to surface property-level errors |
| **Inputs** | none / `IValidationResult` / `errorMessage` / `propertyName + errorMessage` |
| **Outputs** | A throwable exception carrying an `IValidationResult` |
| **Primary components** | ``SaifValidationException` constructors; `ValidationResult`; `ValidationFailure`` |
| **Entry point** | `Common/SAIF.Common/Classes/SaifValidationException.cs` |

## Steps

1. Parameterless ctor: attach a fresh empty `ValidationResult` so the `ValidationResult` property is never null. ([BR-044](../business-rules.md#br-044-a-saifvalidationexception-always-exposes-a-non-null-ivalidationresult))
2. `(IValidationResult result)` ctor: attach the caller's result as-is. ([BR-044](../business-rules.md#br-044-a-saifvalidationexception-always-exposes-a-non-null-ivalidationresult))
3. `(string errorMessage)` ctor: build a `ValidationResult` holding one `ValidationFailure` with an empty property name. ([BR-045](../business-rules.md#br-045-a-validation-exception-built-from-a-bare-message-carries-a-single-failure-whose-propertyname-is-the-empty-string%2C-and-the-two-argument-overload-binds-the-failure-to-a-named-property))
4. `(string propertyName, string errorMessage)` ctor: build a `ValidationResult` holding one `ValidationFailure` bound to that property. ([BR-045](../business-rules.md#br-045-a-validation-exception-built-from-a-bare-message-carries-a-single-failure-whose-propertyname-is-the-empty-string%2C-and-the-two-argument-overload-binds-the-failure-to-a-named-property))
5. `ValidationResult()` initialises `Errors` and `Messages` to empty lists; `ValidationResult(IEnumerable<ValidationFailure>)` initialises both and then replaces `Errors` with the supplied failures only when the argument is non-null. ([BR-046](../business-rules.md#br-046-a-validationresult-always-exposes-non-null-errors-and-messages-collections%2C-even-when-constructed-from-a-null-failure-sequence))
6. `ValidationResult.IsValid` is computed as `!Errors.Any()` — validity is always derived, never assigned. ([BR-047](../business-rules.md#br-047-a-validation-result-is-valid-if-and-only-if-it-holds-no-errors))

## Process flow

The following diagram visualizes this operation's control flow.

```mermaid
flowchart TD
	Start(["Caller constructs a<br>SaifValidationException"]) --> S1["Parameterless ctor: attach<br>fresh empty ValidationResult"]
	S1 --> S2["(IValidationResult) ctor:<br>attach caller's result as-is"]
	S2 --> S3["(errorMessage) ctor: build failure<br>with empty property name"]
	S3 --> S4["(propertyName, errorMessage) ctor:<br>build failure bound to property"]
	S4 --> S5["ValidationResult ctors init<br>Errors and Messages collections"]
	S5 --> S6["IsValid computed as<br>!Errors.Any()"]
	S6 --> End(["Exception ready to throw"])
```

## Related documentation
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)

## Sources
- `Common/SAIF.Common/Classes/SaifValidationException.cs`
- `Common/SAIF.Common/Classes/ValidationResult.cs`
- `Common/SAIF.Common/Classes/ValidationFailure.cs`
