libnyanpasu/clash-nyanpasu · error

clean-schema output rejected by domain model

Error message

clean-schema output rejected by domain model: {e}

What it means

After transforming the legacy document into the clean schema, run_clean_schema round-trips the migrated mapping through the typed domain model nyanpasu_config::profile::Profiles. If the deserializer rejects the migrated value (unknown/invalid fields, malformed items, duplicate uids enforced by R13), this error is thrown. It signals the migration transform itself produced an invalid document, so nothing is written.

Solutions

  1. Inspect {e} to find the offending field/item, then fix the corresponding entry in the source profiles.yaml (e.g. deduplicate or assign unique uids, fill missing required fields) and re-run migration.
  2. Check whether the file was already partially migrated; restore the .yaml.bak and retry on the pristine legacy data.
  3. Verify the nyanpasu-config domain model version matches the migration code — version skew can make valid legacy output invalid.
  4. If it reproduces on legitimate data, report it as a migration-transform bug: the transform should never emit a document the domain model rejects.

Example fix

// before
let profiles: Profiles = serde_yaml::from_value(Value::Mapping(migrated))
    .map_err(|e| anyhow::anyhow!("clean-schema output rejected by domain model: {e}"))?;
// after
let profiles: Profiles = serde_yaml::from_value(Value::Mapping(migrated))
    .with_context(|| format!("clean-schema output rejected by domain model for {}: duplicate uid or invalid item", path.display()))?;
Defensive patterns

Strategy: validation

Validate before calling

fn migrated_output_loads(migrated: &serde_yaml::Mapping) -> Result<(), String> {
    let v = serde_yaml::Value::Mapping(migrated.clone());
    match serde_yaml::from_value::<nyanpasu_config::profile::Profiles>(v) {
        Ok(_) => Ok(()),
        Err(e) => Err(format!("domain model round-trip failed: {e}")),
    }
}

Type guard

fn loads_into_domain_model(migrated: serde_yaml::Mapping) -> bool {
    serde_yaml::from_value::<nyanpasu_config::profile::Profiles>(Value::Mapping(migrated)).is_ok()
}

Try / catch

let profiles: Profiles = match serde_yaml::from_value(Value::Mapping(migrated)) {
    Ok(p) => p,
    Err(e) => {
        eprintln!("migration transform bug: output rejected by domain model: {e}");
        eprintln!("restore profiles.yaml.bak; report duplicate-uid/invalid-item data");
        return Err(e.into());
    }
};

Prevention

When it happens

Trigger: The migrated Mapping contains a profiles document the domain model cannot deserialize: duplicate item uids, items with missing/invalid type or url fields, wrong data types after transform (e.g. string where u32 expected), or legacy fields that map to none of the domain variants.

Common situations: Legacy profiles with unusual or malformed entries (empty uid, duplicate uids from copy-pasted profile blocks, exotic item types) surviving the transform; a bug in the migration transform leaving required fields null; version skew where the domain model gained new constraints the legacy data violates.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08). Data as JSON: /api/errors/38a6cbd4f2df5eda. Report an issue: GitHub.

Appendix: source

Thrown at backend/tauri/src/core/migration/modules/profiles.rs:256

    let raw = std::fs::read_to_string(&path)?;
    let doc: Mapping =
        serde_yaml::from_str(&raw).map_err(|e| anyhow::anyhow!("failed to parse profiles: {e}"))?;
    if is_clean_schema(&doc) {
        return Ok(());
    }

    // R15: backup first, then transform (D3: mandatory .bak)
    let bak = path.with_extension("yaml.bak");
    crate::core::migration::fs::atomic_write(&bak, raw.as_bytes())?;

    let migrated = migrate_clean_schema(doc)?;

    // Typed round-trip: the only accepted output is a document the new domain
    // model can load AND validate (design §14.4). Duplicate uids are rejected
    // here by the items deserializer (R13).
    let profiles: nyanpasu_config::profile::Profiles =
        serde_yaml::from_value(Value::Mapping(migrated))
            .map_err(|e| anyhow::anyhow!("clean-schema output rejected by domain model: {e}"))?;
    profiles
        .validate()
        .map_err(|errors| anyhow::anyhow!("clean-schema output failed validation: {errors:?}"))?;

    let body = serde_yaml::to_string(&profiles)
        .map_err(|e| anyhow::anyhow!("failed to serialize migrated profiles: {e}"))?;
    let content = format!("# Profiles Config for Clash Nyanpasu\n\n{body}");
    crate::core::migration::fs::atomic_write(&path, content.as_bytes())?;
    Ok(())
}

fn rollback_clean_schema(ctx: &mut Ctx) -> anyhow::Result<()> {
    let path = ctx.profiles_path();
    let bak = path.with_extension("yaml.bak");
    if !bak.exists() {
        eprintln!("profiles.yaml.bak not found, nothing to roll back");
        return Ok(());
    }

View on GitHub (pinned to f7dbce2997)