feature-flag 0.1.0

Server-side feature flag evaluation for async Rust: targeting rules, sticky percentage rollouts, hot reload, zero RNG.
Documentation
use feature_flag::{FeatureFlagError, FlagSet};

const VALID: &str = r#"{
  "version": "demo-1",
  "flags": [
    {
      "id": "new-checkout",
      "variants": ["on", "off"],
      "default_variant": "off",
      "rules": [
        {
          "id": "always",
          "when": {"kind": "always"},
          "outcome": {"kind": "variant", "variant": "on"}
        }
      ]
    }
  ]
}"#;

#[test]
fn parses_valid_set() {
    let set = FlagSet::from_json(VALID).expect("valid");
    assert_eq!(set.flags.len(), 1);
    assert_eq!(set.flags[0].id, "new-checkout");
}

#[test]
fn rejects_default_outside_variants() {
    let bad = r#"{
      "flags": [
        {"id": "x", "variants": ["on"], "default_variant": "off", "rules": []}
      ]
    }"#;
    let err = FlagSet::from_json(bad).unwrap_err();
    assert!(matches!(err, FeatureFlagError::Invalid(_)), "got {err:?}");
}

#[test]
fn rejects_rule_referencing_unknown_variant() {
    let bad = r#"{
      "flags": [{
        "id": "x", "variants": ["on", "off"], "default_variant": "off",
        "rules": [{
          "id": "r",
          "when": {"kind": "always"},
          "outcome": {"kind": "variant", "variant": "ghost"}
        }]
      }]
    }"#;
    let err = FlagSet::from_json(bad).unwrap_err();
    assert!(matches!(err, FeatureFlagError::Invalid(_)));
}

#[test]
fn rejects_rollout_weights_not_summing_to_100() {
    let bad = r#"{
      "flags": [{
        "id": "x", "variants": ["a", "b"], "default_variant": "a",
        "rules": [{
          "id": "r",
          "when": {"kind": "always"},
          "outcome": {
            "kind": "rollout",
            "variants": [
              {"variant": "a", "weight": 60},
              {"variant": "b", "weight": 30}
            ]
          }
        }]
      }]
    }"#;
    let err = FlagSet::from_json(bad).unwrap_err();
    assert!(matches!(err, FeatureFlagError::Invalid(_)));
}

#[test]
fn rejects_duplicate_flag_ids() {
    let bad = r#"{
      "flags": [
        {"id": "x", "variants": ["on"], "default_variant": "on", "rules": []},
        {"id": "x", "variants": ["on"], "default_variant": "on", "rules": []}
      ]
    }"#;
    let err = FlagSet::from_json(bad).unwrap_err();
    assert!(matches!(err, FeatureFlagError::Invalid(_)));
}