Validation

How input validation works (and does not work) in HFortix.

Overview

HFortix does not validate payloads client-side before sending them. Requests go to the FortiGate as-is, and the device itself enforces enum values, length limits, integer ranges, and referential integrity. Invalid input surfaces as an exception from hfortix_core.exceptions — most commonly InvalidValueError (FortiOS error -651) or BadRequestError (HTTP 400).

There is no ValidationError class in this library. Handle server-side validation failures like any other API error:

from hfortix_fortios import FortiOS
from hfortix_core.exceptions import (
    APIError,
    BadRequestError,
    InvalidValueError,
)

fgt = FortiOS(host='192.168.1.99', token='your-token')

try:
    fgt.api.cmdb.firewall.policy.post(
        name='test',
        action='invalid-action',  # FortiGate rejects: must be accept/deny/...
        srcintf=[{"name": "internal"}],
        dstintf=[{"name": "wan1"}],
        srcaddr=[{"name": "all"}],
        dstaddr=[{"name": "all"}],
        service=[{"name": "ALL"}],
        schedule='always',
    )
except InvalidValueError as e:
    print(f"FortiGate rejected a field value: {e}")
except BadRequestError as e:
    print(f"Bad request: {e}")
except APIError as e:
    print(f"Other API error: {e}")

What the type stubs give you

Every endpoint ships .pyi stubs with Literal[...] enums and typed parameters, so most invalid enum values and wrong types are caught before runtime by your IDE or type checker (pyright/mypy). This is the primary “validation” layer in HFortix — it costs nothing at runtime.

Optional manual checks

Endpoints expose schema metadata you can use for explicit pre-flight checks when you want to fail fast without a round trip:

addr = fgt.api.cmdb.firewall.address

# Validate a single field value against its schema constraints
is_valid, error = addr.validate_field('type', 'not-a-real-type')
if not is_valid:
    print(f"Invalid: {error}")

# Introspect the schema
print(addr.required_fields())   # Fields required on create
print(addr.field_info('type'))  # Constraints for one field

These helpers are opt-in — nothing calls them automatically.

See Also