# `HL7v2.Validation.ProfileRules`
[🔗](https://github.com/Balneario-de-Cofrentes/hl7v2/blob/v3.10.1/lib/hl7v2/validation/profile_rules.ex#L1)

Evaluates a `HL7v2.Profile` against a typed message and returns validation
errors. Called from `HL7v2.Validation.validate/2` when a `:profile` option
is provided.

Profile errors include `:rule` (which rule type fired) and `:profile`
(profile name) fields in addition to the standard
`:level/:location/:field/:message` shape used by the rest of the validation
stack.

## Rule Evaluation Order

For each profile, the following checks run in order and all errors from all
checks are collected (short-circuiting is intentionally avoided so integrators
see a complete diagnostic in one pass):

1. `require_segment` — every segment in `profile.required_segments` must appear
2. `forbid_segment` — no segment in `profile.forbidden_segments` may appear
3. `require_field`  — every `{seg_id, field_seq}` must be populated when its
   segment is present; missing segment is also an error
4. `forbid_field` — every `{seg_id, field_seq}` in `profile.forbidden_fields`
   must be blank or absent
5. `require_value` — a declarative equality or membership pin on a field
   (from `Profile.require_value/5` or `Profile.require_value_in/5`)
6. `require_component` — a declarative component/subcomponent pin on a
   composite field (from `Profile.require_component/5`)
7. `bind_table` — a field's value must be in a specific HL7 table
   (from `Profile.bind_table/4`)
8. `require_cardinality` — segment occurrence counts must fall within `{min, max}`
9. `value_constraint` — custom predicate on a field value (only when field present)
10. `custom_rule` — arbitrary function returning error maps

Custom rule errors are tagged with `:rule` (the rule name) and `:profile`
(the profile name) if the rule did not set them. If a custom rule raises,
a synthetic `:custom_rule_exception` error is emitted — exceptions are
never silently swallowed.

## Applicability

A profile is applied only when its `:message_type` tuple matches the
typed message's code/event AND (when declared) its `:version` matches
the version in MSH-12. A profile with nil `:message_type` matches any
type; a profile with nil `:version` matches any version.

## "Blank" semantics

Several rules (`require_field`, `forbid_field`, `require_value`,
`require_component`) share a single `blank?/1` helper that treats
any of the following as blank:

- `nil`
- `""`
- `[]`
- a list whose elements are all themselves blank
- a struct whose every field is blank (checked recursively)

The last clause means a composite field like `%HL7v2.Type.CE{}` with
every subcomponent set to `nil` is considered blank — which is the
right answer for "is PID-3 populated?" but may surprise callers who
consider a CE with only an `alternate_identifier` to be "empty" or
"populated" depending on their use case. If your rule needs
stricter semantics, prefer `require_value/5` with an accessor or
`require_component/5` targeting a specific subcomponent.

# `error`

```elixir
@type error() :: %{
  level: :error | :warning,
  location: String.t(),
  field: atom() | nil,
  message: String.t(),
  rule: atom(),
  profile: String.t()
}
```

# `check`

```elixir
@spec check(HL7v2.TypedMessage.t(), HL7v2.Profile.t()) :: [error()]
```

Evaluates a profile against a typed message.

Returns `[]` if the message satisfies all profile rules, or a list of
error maps describing each violation.

If the profile declares a `:message_type` and it doesn't match the typed
message's type, the profile is considered not applicable and `[]` is
returned — it is the caller's responsibility to decide whether a
non-matching profile is itself an error.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
