# Element Validation

SGN provides validators that enforce pad count and naming constraints on
elements. Validators catch misconfiguration early — at construction time rather
than at runtime.

## Available Validators

| Validator         | Applies to       | Constraint                                      |
|-------------------|------------------|-------------------------------------------------|
| `one_to_one`      | TransformElement | Exactly one sink pad and one source pad          |
| `one_to_many`     | TransformElement | Exactly one sink pad, any number of source pads  |
| `many_to_one`     | TransformElement | Any number of sink pads, exactly one source pad  |
| `pad_names_match` | TransformElement | Source and sink pad names are the same set        |
| `num_pads`        | TransformElement | Exact counts for sink and/or source pads         |
| `single_pad`      | SinkElement      | Exactly one sink pad                             |

All validators are in the `sgn.validator` module:

```{.python notest}
from sgn import validator
```

## Using Validators as Decorators

Decorate a `validate()` method on your element class. The validator runs its
check before your method body executes:

```{.python notest}
from dataclasses import dataclass
from sgn import TransformElement, validator

@dataclass
class MyTransform(TransformElement):
    @validator.one_to_one
    def validate(self) -> None:
        # Additional custom validation can go here
        pass
```

The `validate()` method is called automatically during element construction.
If the element is instantiated with the wrong number of pads, an
`AssertionError` is raised immediately.

## Using Validators Directly

Call the validator in `__post_init__()` instead of decorating a method. Pass
`self` as the argument:

```{.python notest}
from dataclasses import dataclass
from sgn import SinkElement, validator

@dataclass
class MySink(SinkElement):
    def __post_init__(self):
        super().__post_init__()
        validator.single_pad(self)
```

Both approaches produce the same result — choose whichever fits your style.

## Specifying Exact Pad Counts

`num_pads` takes keyword arguments for `sink_pads` and `source_pads`. Either
can be omitted to leave that side unconstrained:

```{.python notest}
@dataclass
class MergeTransform(TransformElement):
    @validator.num_pads(sink_pads=2, source_pads=1)
    def validate(self) -> None:
        pass
```

When used directly, `num_pads` returns a validator that you then call with
the instance:

```{.python notest}
def __post_init__(self):
    super().__post_init__()
    validator.num_pads(sink_pads=2, source_pads=1)(self)
```

## Matching Pad Names

`pad_names_match` asserts that the set of source pad names equals the set of
sink pad names. This is useful for pass-through transforms that process each
channel independently:

```{.python notest}
@dataclass
class PerChannelTransform(TransformElement):
    @validator.pad_names_match
    def validate(self) -> None:
        pass

# OK — pad names match
t = PerChannelTransform(name="t", sink_pad_names=["H1", "L1"], source_pad_names=["H1", "L1"])

# AssertionError — pad names don't match
t = PerChannelTransform(name="t", sink_pad_names=["H1"], source_pad_names=["H1", "L1"])
```

## Combining Validators

Stack multiple decorators or chain direct calls to apply more than one
constraint:

```{.python notest}
@dataclass
class StrictTransform(TransformElement):
    @validator.pad_names_match
    @validator.num_pads(sink_pads=2, source_pads=2)
    def validate(self) -> None:
        pass
```
