Skip to main content

A Self-Validating Config

A subscription plan with a bad reset_inc — zero, or negative — is the kind of bug that looks completely fine as JSON and then quietly breaks billing in production: a reset loop that never advances, or fires constantly. The fix isn't a check somewhere downstream that someone has to remember to write. It's making the config incapable of holding that value in the first place.

See Prototypes & Schemas and the Object Library for more information on Schemafy.

Attaching a Rule to a Field

#[schema(...)] on a field is a validator — schemafy(target) checks a target object against it:

step1.stof
#[type]
Plan: {
  #[schema((target_value: str): bool => target_value.len() > 1)]
  str name: 'default'

  #[schema((target_value: hr): bool => target_value > 0hr)]
  hr reset_inc: 1hr

  /// Plan output as YAML to pass to the host or send over the wire, etc.
  fn out() -> str { stringify('yaml', self) }
}

#[main]
fn main() {
  const good = new Plan { reset_inc: 2hr };
  pln(<Plan>.schemafy(good));

  const bad = new Plan { reset_inc: -1hr };
  pln(<Plan>.schemafy(bad));

  pln(good.out());
}
Output

The Plan

Every field that matters gets its own rule — a real plan, checked all at once:

plan.stof
#[type]
Plan: {
  #[schema((target_value: str): bool => target_value.len() > 0)]
  str label: 'Growth Plan'

  #[schema((target_value: float): bool => target_value > 0)]
  float credit_limit: 100_000

  #[schema((target_value: hr): bool => target_value > 0hr)]
  hr reset_inc: 1hr
}

#[main]
fn main() {
  const incoming = new { label: 'Team Plan', credit_limit: 50_000, reset_inc: 1hr };
  pln(<Plan>.schemafy(incoming));

  const broken = new { label: 'Broken Plan', credit_limit: 50_000, reset_inc: -1hr };
  pln(<Plan>.schemafy(broken));
}
Output

incoming passes and broken doesn't — and nothing about that required a separate validation layer, a schema file, or a test to catch it. The rule lives on the same field it protects, so it can't drift out of sync with what the field actually needs.