Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Build: The Codebook Types

Maps to: Task 2. Kind: Build.

Objective

Create codes.rs in panoptes-core: the ActionType and StrategicLogic enums and the CodedRow struct. Write the test that is the whole reason we chose Rust — an invalid codebook value must fail to deserialize.

Scaffold

Create: crates/panoptes-core/src/codes.rs. Modify: crates/panoptes-core/src/lib.rs (declare and re-export the module).

Dependencies: no manifest changes — strum (for SCREAMING_SNAKE_CASE serialization) and serde_with (for NoneAsEmptyString on coder_notes) are already in core's Cargo.toml from the previous chapter.

Expected result: cargo test -p panoptes-core codes3 tests pass (valid_logic_parses, invalid_logic_is_rejected, coded_row_json_roundtrip).

The spec (givens)

  • ActionType variants: SensorRetask, Maneuver, Monitor, EscalateToCommand, RequestData, NoAction. StrategicLogic variants: Control, Maritime, Political, Procedural, None, Mixed.
  • Both carry two attributes: #[strum(serialize_all = "SCREAMING_SNAKE_CASE")] and #[serde(rename_all = "SCREAMING_SNAKE_CASE")] — wire form "SENSOR_RETASK", "CONTROL" in both codecs — plus the same derive stack as the params enums. Unlike params.rs (where the strum and serde codecs never cross paths), the coded files are hand-filled with codebook strings and parsed by serde — so here the two codecs must agree, which means saying it to each codec separately. Omit the serde attribute and serde_json::from_str::<StrategicLogic>("\"CONTROL\"") fails with unknown variant CONTROL, expected one of Control, ….
  • CodedRow fields, in order: response_id: String, c1_action: ActionType, c2_logic: StrategicLogic, c2_anchor_quote: String, c3_escalation: u8, codebook_version: String, coder_notes: Option<String> — the last annotated #[serde_as(as = "NoneAsEmptyString")] under a #[serde_as] struct attribute.

Full code: Answer Key, Task 2.

Concepts exercised

  • #[strum(serialize_all = "SCREAMING_SNAKE_CASE")] to match codebook string conventions.
  • serde_with::NoneAsEmptyString for optional free-text fields.
  • The negative test: asserting that a parse fails.

The build loop (you drive)

  1. Write three failing tests: a valid value parses; an invalid value ("AGGRESSIVE") is rejected; a full CodedRow round-trips through JSON.
  2. Predict: the invalid_logic_is_rejected test asserts result.is_err(). Before implementing, what makes it currently fail — the assertion, or the fact that the type does not compile yet?
  3. Run, check.
  4. Implement the enums and struct.
  5. Run green, commit.
The test that matters most invalid_logic_is_rejected is the load-bearing test of the entire harness. Everything about the instrument's integrity traces back to this one assertion passing. Write it deliberately.
Best practice — why an enum, not a String Effective Rust's Item 1 is "use the type system to express your data structures," and its guidance on exactly this choice is worth internalizing: prefer an enum over a wrapped primitive when there's a chance that a new alternative could arise in the future. Codebook criteria are precisely that case — you may add a strategic-logic category during piloting — so an enum, not a string or a newtype-over-string, is the idiomatic and correct representation. The compiler then, in the book's phrase, "takes care of policing those semantics" for you.

Done when

cargo test -p panoptes-core codes is green, and the invalid-value test proves the codebook constraint lives in the type rather than in a separate check.