sinelaw/fresh · error · io::Error (InvalidData)

Copy action without source

Error message

Copy action without source

What it means

write_recipe_to_file fails when the recipe contains Copy actions but the recipe's src_path is None. Copy actions read bytes from the source file, so without a source path the recipe cannot be executed. This is an internally inconsistent recipe (InvalidData).

Solutions

  1. Set recipe.src_path to the file the buffer was loaded from before writing.
  2. For buffers with no source file, build an insert-only recipe (no Copy actions).
  3. Validate recipes: if actions contain Copy, assert src_path.is_some() at build time.

Example fix

// before
let recipe = WriteRecipe { actions: vec![RecipeAction::Copy{offset:0,len:10}], src_path: None, .. };
// after
let recipe = WriteRecipe { actions: vec![RecipeAction::Copy{offset:0,len:10}], src_path: Some(original_path.into()), .. };
Defensive patterns

Strategy: validation

Validate before calling

if recipe.actions.iter().any(|a| matches!(a, RecipeAction::Copy{..})) && recipe.src_path.is_none() {
    return Err(io::Error::new(io::ErrorKind::InvalidData, "recipe has Copy actions but no src_path"));
}

Try / catch

match write_recipe_to_file(&recipe, ...) {
    Ok(()) => (),
    Err(e) if e.to_string().contains("Copy action without source") => {
        // rebuild as insert-only recipe for unsaved buffers
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling write_recipe_to_file with a recipe built with Copy actions but never assigned src_path — e.g. an in-memory-only document saved through the copy-based path instead of the insert-only path.

Common situations: Saving a new (never-loaded-from-disk) buffer whose recipe generator wrongly emitted Copy actions; a builder that forgets set_src_path.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of sinelaw/fresh@67894ca546 (2026-09-13). Data as JSON: /api/errors/070e81222100b8ac. Report an issue: GitHub.

Appendix: source

Thrown at crates/fresh-editor-core/src/model/buffer/save.rs:532

        out_file.write_all(&chunk)?;
        offset += chunk_len as u64;
    }

    Ok(())
}

/// Write the recipe content to a file writer.
pub(super) fn write_recipe_to_file(
    fs: &Arc<dyn FileSystem + Send + Sync>,
    out_file: &mut Box<dyn FileWriter>,
    recipe: &WriteRecipe,
) -> io::Result<()> {
    for action in &recipe.actions {
        match action {
            RecipeAction::Copy { offset, len } => {
                // Read from source and write to output
                let src_path = recipe.src_path.as_ref().ok_or_else(|| {
                    io::Error::new(io::ErrorKind::InvalidData, "Copy action without source")
                })?;
                let data = fs.read_range(src_path, *offset, *len as usize)?;
                out_file.write_all(&data)?;
            }
            RecipeAction::Insert { index } => {
                out_file.write_all(&recipe.insert_data[*index])?;
            }
        }
    }
    Ok(())
}

/// Internal helper to create a SudoSaveRequired error.
pub(super) fn make_sudo_error(
    temp_path: PathBuf,
    dest_path: &Path,
    original_metadata: Option<FileMetadata>,
) -> anyhow::Error {

View on GitHub (pinned to 67894ca546)