zpl_toolchain_profile 0.1.4

Printer profile loading and validation for the ZPL toolchain
Documentation
# zpl_toolchain_profile

Printer profile crate for the ZPL toolchain. Defines the `Profile` struct and loading utilities that drive data-driven validation.

Part of the [zpl-toolchain](https://github.com/trevordcampbell/zpl-toolchain) project.

> **Full guide:** See [`docs/PROFILE_GUIDE.md`]../../docs/PROFILE_GUIDE.md for the complete profile system reference — schema, printerGates semantics, DPI-dependent defaults, and custom profile creation.

## Profile Schema

Profiles describe a printer's capabilities: resolution, page dimensions, speed/darkness ranges, hardware features, media capabilities, and memory/firmware info. The canonical schema lives in `spec/schema/profile.schema.jsonc` and is cross-validated by the spec-compiler.

```json
{
  "id": "zebra-generic-203",
  "schema_version": "1.1.0",
  "dpi": 203,
  "page": { "width_dots": 812, "height_dots": 1218 },
  "speed_range": { "min": 2, "max": 8 },
  "darkness_range": { "min": 0, "max": 30 },
  "features": {
    "cutter": false, "peel": false, "rewinder": false,
    "applicator": false, "rfid": false, "rtc": false,
    "battery": false, "zbi": false, "lcd": false, "kiosk": false
  },
  "media": {
    "print_method": "direct_thermal",
    "supported_modes": ["T"],
    "supported_tracking": ["N", "Y", "W", "M"]
  },
  "memory": {
    "ram_kb": 32768,
    "flash_kb": 65536
  }
}
```

## Structs
- **`Profile`** — top-level printer profile with required `id`, `schema_version`, `dpi` and optional `page`, `speed_range`, `darkness_range`, `features`, `media`, `memory`
- **`Page`** — page/label dimension constraints (`width_dots`, `height_dots` as `Option<u32>`)
- **`Range`** — min/max range for numeric capabilities (`min: u32`, `max: u32`); validated that `min <= max` on load. Constructors: `Range::new(min, max)` (panics if `min > max`) and `Range::try_new(min, max) -> Option<Range>` (returns `None` if invalid)
- **`Features`** — hardware feature flags for `printerGates` enforcement (`cutter`, `peel`, `rewinder`, `applicator`, `rfid`, `rtc`, `battery`, `zbi`, `lcd`, `kiosk` as `Option<bool>`); three-state semantics: `true` = has feature, `false` = lacks feature (triggers ZPL1402), `None` = unknown (gate skipped)
- **`Media`** — media capability descriptors (`print_method`, `supported_modes`, `supported_tracking` as `Option`)
- **`Memory`** — memory and firmware info (`ram_kb`, `flash_kb` as `Option<u32>`, `firmware_version` as `Option<String>`)

Derives: `Debug`, `Clone`, `Serialize`, `Deserialize`, `Default`, `PartialEq`, `Eq` (Profile, Page, Features, Media, Memory); `Range` derives all except `Default`.

## Gate Resolution
The validator resolves `printerGates` against `Profile.features`:
- Feature `true` → gate passes
- Feature `false` → gate fails → ZPL1402
- Feature `None` / `features` absent → gate check skipped (no false positives)

Command-level gates emit errors; enum value-level gates emit warnings.

## Usage
- CLI `--profile profiles/zebra-generic-203.json` loads a profile and enables `profileConstraint` checks (e.g., `^PW` width ≤ `page.width_dots`, `~SD` darkness ≤ `darkness_range.max`) and `printerGates` enforcement.
- `load_profile_from_str()` deserializes and validates structural invariants, returning `ProfileError` on failure. `ProfileError` has two variants: `InvalidJson` (serde parse failure) and `InvalidField` (structural invariant violation such as `min > max`, empty `id`, DPI out of 100–600, non-positive page dimensions, speed outside 1–14, darkness outside 0–30, or non-positive memory).
- `resolve_profile_field()` in the validator maps dotted paths (e.g., `"page.width_dots"`) to profile values.
- The `all_profile_constraint_fields_are_resolvable` test ensures the resolver covers every field referenced in command specs.

## Shipped Profiles
- `profiles/zebra-generic-203.json` — 203 dpi generic (4" printhead, 812×1218 dots, direct thermal, no cutter/RFID)
- `profiles/zebra-generic-300.json` — 300 dpi generic (4" printhead, 1218×1800 dots, direct thermal, no cutter/RFID)