Files
codex/codex-rs/artifact-spreadsheet/src/manager.rs
2026-03-03 12:38:27 +00:00

1906 lines
67 KiB
Rust

use std::collections::BTreeMap;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use base64::Engine;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value;
use crate::CellAddress;
use crate::CellRange;
use crate::SpreadsheetArtifact;
use crate::SpreadsheetArtifactError;
use crate::SpreadsheetCellRangeRef;
use crate::SpreadsheetCellRef;
use crate::SpreadsheetCellValue;
use crate::SpreadsheetCitation;
use crate::SpreadsheetRangeView;
use crate::SpreadsheetSheetSummary;
use crate::SpreadsheetSummary;
#[derive(Debug, Clone, Deserialize)]
pub struct SpreadsheetArtifactRequest {
pub artifact_id: Option<String>,
pub action: String,
#[serde(default)]
pub args: Value,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PathAccessKind {
Read,
Write,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PathAccessRequirement {
pub action: String,
pub kind: PathAccessKind,
pub path: PathBuf,
}
impl SpreadsheetArtifactRequest {
pub fn required_path_accesses(
&self,
cwd: &Path,
) -> Result<Vec<PathAccessRequirement>, SpreadsheetArtifactError> {
let access = match self.action.as_str() {
"import_xlsx" | "load" | "read" => {
let args: PathArgs = parse_args(&self.action, &self.args)?;
vec![PathAccessRequirement {
action: self.action.clone(),
kind: PathAccessKind::Read,
path: resolve_path(cwd, &args.path),
}]
}
"export_xlsx" => {
let args: PathArgs = parse_args(&self.action, &self.args)?;
vec![PathAccessRequirement {
action: self.action.clone(),
kind: PathAccessKind::Write,
path: resolve_path(cwd, &args.path),
}]
}
"save" => {
let args: SaveArgs = parse_args(&self.action, &self.args)?;
vec![PathAccessRequirement {
action: self.action.clone(),
kind: PathAccessKind::Write,
path: resolve_path(cwd, &args.path),
}]
}
_ => Vec::new(),
};
Ok(access)
}
}
#[derive(Debug, Default)]
pub struct SpreadsheetArtifactManager {
documents: HashMap<String, SpreadsheetArtifact>,
}
impl SpreadsheetArtifactManager {
pub fn execute(
&mut self,
request: SpreadsheetArtifactRequest,
cwd: &Path,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
match request.action.as_str() {
"create" => self.create(request),
"import_xlsx" | "load" | "read" => self.import_xlsx(request, cwd),
"export_xlsx" => self.export_xlsx(request, cwd),
"save" => self.save(request, cwd),
"get_summary" => self.get_summary(request),
"list_sheets" => self.list_sheets(request),
"get_sheet" => self.get_sheet(request),
"inspect" => self.inspect(request),
"create_sheet" => self.create_sheet(request),
"rename_sheet" => self.rename_sheet(request),
"delete_sheet" => self.delete_sheet(request),
"set_sheet_properties" => self.set_sheet_properties(request),
"set_column_widths" => self.set_column_widths(request),
"set_column_widths_bulk" => self.set_column_widths_bulk(request),
"set_row_height" => self.set_row_height(request),
"set_row_heights" => self.set_row_heights(request),
"set_row_heights_bulk" => self.set_row_heights_bulk(request),
"get_row_height" => self.get_row_height(request),
"cleanup_and_validate_sheet" => self.cleanup_and_validate_sheet(request),
"get_cell" => self.get_cell(request),
"get_cell_by_indices" => self.get_cell_by_indices(request),
"get_cell_field" => self.get_cell_field(request),
"get_cell_field_by_indices" => self.get_cell_field_by_indices(request),
"get_range" => self.get_range(request),
"set_cell_value" => self.set_cell_value(request),
"set_range_value" => self.set_range_value(request),
"set_range_values" => self.set_range_values(request),
"set_cell_formula" => self.set_cell_formula(request),
"set_range_formula" => self.set_range_formula(request),
"set_range_formulas" => self.set_range_formulas(request),
"set_cell_style" => self.set_cell_style(request),
"set_range_style" => self.set_range_style(request),
"clear_range" => self.clear_range(request),
"merge_range" => self.merge_range(request),
"unmerge_range" => self.unmerge_range(request),
"cite_cell" => self.cite_cell(request),
"cite_range" => self.cite_range(request),
"calculate" | "recalculate" => self.calculate(request),
"serialize_dict" => self.serialize_dict(request),
"serialize_json" => self.serialize_json(request),
"serialize_bytes" => self.serialize_bytes(request),
"deserialize_dict" => self.deserialize_dict(request),
"deserialize_json" => self.deserialize_json(request),
"deserialize_bytes" => self.deserialize_bytes(request),
"delete_artifact" => self.delete_artifact(request),
other => Err(SpreadsheetArtifactError::UnknownAction(other.to_string())),
}
}
fn create(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: CreateArgs = parse_args(&request.action, &request.args)?;
let mut artifact = SpreadsheetArtifact::new(args.name);
if let Some(auto_recalculate) = args.auto_recalculate {
artifact.auto_recalculate = auto_recalculate;
}
let artifact_id = artifact.artifact_id.clone();
let summary = format!("Created spreadsheet artifact `{artifact_id}`");
let snapshot = snapshot_for_artifact(&artifact);
self.documents.insert(artifact_id.clone(), artifact);
Ok(SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
summary,
snapshot,
))
}
fn import_xlsx(
&mut self,
request: SpreadsheetArtifactRequest,
cwd: &Path,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: PathArgs = parse_args(&request.action, &request.args)?;
let path = resolve_path(cwd, &args.path);
let artifact = SpreadsheetArtifact::from_source_file(&path, None)?;
let artifact_id = artifact.artifact_id.clone();
let snapshot = snapshot_for_artifact(&artifact);
let summary = format!(
"Imported `{}` as spreadsheet artifact `{artifact_id}` with {} sheets",
path.display(),
artifact.sheets.len()
);
self.documents.insert(artifact_id.clone(), artifact);
Ok(SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
summary,
snapshot,
))
}
fn export_xlsx(
&mut self,
request: SpreadsheetArtifactRequest,
cwd: &Path,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: PathArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
let path = resolve_path(cwd, &args.path);
let exported = artifact.export(&path)?;
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
format!("Exported spreadsheet to `{}`", exported.display()),
snapshot_for_artifact(artifact),
);
response.exported_paths.push(exported);
response.workbook_summary = Some(artifact.summary());
response.sheet_list = Some(
artifact
.sheets
.iter()
.map(super::model::SpreadsheetSheet::summary)
.collect(),
);
Ok(response)
}
fn save(
&mut self,
request: SpreadsheetArtifactRequest,
cwd: &Path,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: SaveArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
let path = resolve_path(cwd, &args.path);
let exported = artifact.save(&path, args.file_type.as_deref())?;
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
format!("Saved spreadsheet to `{}`", exported.display()),
snapshot_for_artifact(artifact),
);
response.exported_paths.push(exported);
response.workbook_summary = Some(artifact.summary());
Ok(response)
}
fn get_summary(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact(&artifact_id, &request.action)?;
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
format!(
"Spreadsheet has {} sheets and {} bytes of serialized state",
artifact.sheets.len(),
artifact.summary().size_bytes
),
snapshot_for_artifact(artifact),
);
response.workbook_summary = Some(artifact.summary());
response.sheet_list = Some(
artifact
.sheets
.iter()
.map(super::model::SpreadsheetSheet::summary)
.collect(),
);
Ok(response)
}
fn list_sheets(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact(&artifact_id, &request.action)?;
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
format!("Listed {} sheets", artifact.sheets.len()),
snapshot_for_artifact(artifact),
);
response.sheet_list = Some(
artifact
.sheets
.iter()
.map(super::model::SpreadsheetSheet::summary)
.collect(),
);
Ok(response)
}
fn get_sheet(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: SheetLookupArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact(&artifact_id, &request.action)?;
let sheet = artifact.sheet_lookup(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
let range = if let Some(range) = args.range.as_deref() {
Some(CellRange::parse(range)?)
} else {
sheet.minimum_range()
};
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
format!("Retrieved sheet `{}`", sheet.name),
snapshot_for_artifact(artifact),
);
response.sheet_list = Some(vec![sheet.summary()]);
response.sheet_ref = Some(sheet_reference(sheet));
response.range_ref = range
.as_ref()
.map(|entry| SpreadsheetCellRangeRef::new(sheet.name.clone(), entry));
response.rendered_text = Some(sheet.to_rendered_text(range.as_ref()));
response.range = range.as_ref().map(|entry| sheet.get_range_view(entry));
response.serialized_dict = Some(sheet.to_dict()?);
Ok(response)
}
fn inspect(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: SheetLookupArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact(&artifact_id, &request.action)?;
let inspected = if args.sheet_name.is_none() && args.sheet_index.is_none() {
artifact.to_dict()?
} else {
let sheet = artifact.sheet_lookup(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
serde_json::to_value(sheet).map_err(|error| {
SpreadsheetArtifactError::Serialization {
message: error.to_string(),
}
})?
};
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
"Generated inspection snapshot".to_string(),
snapshot_for_artifact(artifact),
);
response.serialized_dict = Some(inspected);
Ok(response)
}
fn create_sheet(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: CreateSheetArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
let name = args.name.clone();
artifact.create_sheet(args.name)?;
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
format!("Created sheet `{name}`"),
snapshot_for_artifact(artifact),
);
response.sheet_list = Some(
artifact
.sheets
.iter()
.map(super::model::SpreadsheetSheet::summary)
.collect(),
);
Ok(response)
}
fn rename_sheet(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: RenameSheetArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
let new_name = args.new_name.clone();
artifact.rename_sheet(
args.new_name,
args.old_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
format!("Renamed sheet to `{new_name}`"),
snapshot_for_artifact(artifact),
);
response.sheet_list = Some(
artifact
.sheets
.iter()
.map(super::model::SpreadsheetSheet::summary)
.collect(),
);
Ok(response)
}
fn delete_sheet(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: DeleteSheetArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
artifact.delete_sheet(
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
"Deleted sheet".to_string(),
snapshot_for_artifact(artifact),
);
response.sheet_list = Some(
artifact
.sheets
.iter()
.map(super::model::SpreadsheetSheet::summary)
.collect(),
);
Ok(response)
}
fn set_sheet_properties(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: SetSheetPropertiesArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
let sheet_summary = {
let sheet = artifact.sheet_lookup_mut(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
if let Some(default_row_height) = args.default_row_height {
sheet.default_row_height = Some(default_row_height);
}
if let Some(default_column_width) = args.default_column_width {
sheet.default_column_width = Some(default_column_width);
}
if let Some(show_grid_lines) = args.show_grid_lines {
sheet.show_grid_lines = show_grid_lines;
}
sheet.summary()
};
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
format!("Updated sheet `{}` properties", sheet_summary.name),
snapshot_for_artifact(artifact),
);
response.sheet_list = Some(vec![sheet_summary]);
Ok(response)
}
fn set_column_widths(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: SetColumnWidthsArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
let sheet_summary = {
let sheet = artifact.sheet_lookup_mut(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
sheet.set_column_widths(&args.reference, args.width)?;
sheet.summary()
};
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
format!(
"Updated column widths `{}` on `{}`",
args.reference, sheet_summary.name
),
snapshot_for_artifact(artifact),
);
response.sheet_list = Some(vec![sheet_summary]);
Ok(response)
}
fn set_column_widths_bulk(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: SetColumnWidthsBulkArgs = parse_args(&request.action, &request.args)?;
let width_count = args.widths.len();
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
let sheet_summary = {
let sheet = artifact.sheet_lookup_mut(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
sheet.set_column_widths_bulk(&args.widths)?;
sheet.summary()
};
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
format!(
"Updated {width_count} column width references on `{}`",
sheet_summary.name
),
snapshot_for_artifact(artifact),
);
response.sheet_list = Some(vec![sheet_summary]);
Ok(response)
}
fn set_row_height(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: SetRowHeightArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
let (sheet_summary, row_height) = {
let sheet = artifact.sheet_lookup_mut(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
sheet.set_row_height(args.row_index, args.height)?;
(sheet.summary(), sheet.get_row_height(args.row_index))
};
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
format!(
"Updated row height for row {} on `{}`",
args.row_index, sheet_summary.name
),
snapshot_for_artifact(artifact),
);
response.sheet_list = Some(vec![sheet_summary]);
response.row_height = row_height;
Ok(response)
}
fn set_row_heights(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: SetRowHeightsArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
let start = args.start_row_index.min(args.end_row_index);
let end = args.start_row_index.max(args.end_row_index);
let sheet_summary = {
let sheet = artifact.sheet_lookup_mut(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
sheet.set_row_heights(args.start_row_index, args.end_row_index, args.height)?;
sheet.summary()
};
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
format!(
"Updated row heights {start}:{end} on `{}`",
sheet_summary.name
),
snapshot_for_artifact(artifact),
);
response.sheet_list = Some(vec![sheet_summary]);
Ok(response)
}
fn set_row_heights_bulk(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: SetRowHeightsBulkArgs = parse_args(&request.action, &request.args)?;
let height_count = args.heights.len();
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
let sheet_summary = {
let sheet = artifact.sheet_lookup_mut(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
sheet.set_row_heights_bulk(&args.heights)?;
sheet.summary()
};
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
format!(
"Updated {height_count} row height entries on `{}`",
sheet_summary.name
),
snapshot_for_artifact(artifact),
);
response.sheet_list = Some(vec![sheet_summary]);
Ok(response)
}
fn get_row_height(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: GetRowHeightArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact(&artifact_id, &request.action)?;
let sheet = artifact.sheet_lookup(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
format!(
"Retrieved row height for row {} from `{}`",
args.row_index, sheet.name
),
snapshot_for_artifact(artifact),
);
response.sheet_ref = Some(sheet_reference(sheet));
response.sheet_list = Some(vec![sheet.summary()]);
response.row_height = sheet.get_row_height(args.row_index);
Ok(response)
}
fn cleanup_and_validate_sheet(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: SheetLookupArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
let sheet_summary = {
let sheet = artifact.sheet_lookup_mut(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
sheet.cleanup_and_validate_sheet()?;
sheet.summary()
};
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
format!("Cleaned and validated `{}`", sheet_summary.name),
snapshot_for_artifact(artifact),
);
response.sheet_list = Some(vec![sheet_summary]);
Ok(response)
}
fn get_cell(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: CellAddressArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact(&artifact_id, &request.action)?;
let sheet = artifact.sheet_lookup(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
let cell = sheet.get_cell_view(CellAddress::parse(&args.address)?);
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
format!("Retrieved cell `{}` from `{}`", args.address, sheet.name),
snapshot_for_artifact(artifact),
);
response.cell = Some(cell);
response.cell_ref = Some(sheet.cell_ref(&args.address)?);
Ok(response)
}
fn get_cell_by_indices(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: CellIndicesArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact(&artifact_id, &request.action)?;
let sheet = artifact.sheet_lookup(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
let address = CellAddress {
column: args.column_index,
row: args.row_index,
};
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
format!(
"Retrieved cell by indices ({}, {}) from `{}`",
args.column_index, args.row_index, sheet.name
),
snapshot_for_artifact(artifact),
);
response.cell = Some(sheet.get_cell_view_by_indices(args.column_index, args.row_index));
response.cell_ref = Some(sheet.cell_ref(address.to_a1())?);
Ok(response)
}
fn get_cell_field(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: CellFieldArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact(&artifact_id, &request.action)?;
let sheet = artifact.sheet_lookup(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
let value = sheet.get_cell_field(CellAddress::parse(&args.address)?, &args.field)?;
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
format!(
"Retrieved field `{}` from `{}` on `{}`",
args.field, args.address, sheet.name
),
snapshot_for_artifact(artifact),
);
response.cell_field = value;
response.cell_ref = Some(sheet.cell_ref(&args.address)?);
Ok(response)
}
fn get_cell_field_by_indices(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: CellFieldByIndicesArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact(&artifact_id, &request.action)?;
let sheet = artifact.sheet_lookup(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
let value =
sheet.get_cell_field_by_indices(args.column_index, args.row_index, &args.field)?;
let address = CellAddress {
column: args.column_index,
row: args.row_index,
};
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
format!(
"Retrieved field `{}` from indices ({}, {}) on `{}`",
args.field, args.column_index, args.row_index, sheet.name
),
snapshot_for_artifact(artifact),
);
response.cell_field = value;
response.cell_ref = Some(sheet.cell_ref(address.to_a1())?);
Ok(response)
}
fn get_range(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: RangeArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact(&artifact_id, &request.action)?;
let sheet = artifact.sheet_lookup(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
let range = CellRange::parse(&args.range)?;
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
format!("Retrieved range `{}` from `{}`", args.range, sheet.name),
snapshot_for_artifact(artifact),
);
response.range = Some(sheet.get_range_view(&range));
response.rendered_text = Some(sheet.to_rendered_text(Some(&range)));
Ok(response)
}
fn set_cell_value(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let action = request.action.clone();
let args: SetCellValueArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
let address = CellAddress::parse(&args.address)?;
let recalculate = args.recalculate.unwrap_or(artifact.auto_recalculate);
{
let sheet = artifact.sheet_lookup_mut(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
let value = normalize_optional_cell_value(args.value)?;
sheet.set_value(address, value)?;
}
if recalculate {
artifact.recalculate();
}
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
action.clone(),
format!("Updated cell `{}`", args.address),
snapshot_for_artifact(artifact),
);
let sheet = artifact.sheet_lookup(
&action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
response.cell = Some(sheet.get_cell_view(address));
response.cell_ref = Some(sheet.cell_ref(&args.address)?);
Ok(response)
}
fn set_range_value(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let action = request.action.clone();
let args: SetRangeValueArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
let range = CellRange::parse(&args.range)?;
let recalculate = args.recalculate.unwrap_or(artifact.auto_recalculate);
{
let sheet = artifact.sheet_lookup_mut(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
sheet.set_range_to_value(&range, normalize_optional_cell_value(args.value)?)?;
}
if recalculate {
artifact.recalculate();
}
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
action.clone(),
format!("Updated range `{}` to a single value", args.range),
snapshot_for_artifact(artifact),
);
let sheet = artifact.sheet_lookup(
&action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
response.range = Some(sheet.get_range_view(&range));
response.range_ref = Some(SpreadsheetCellRangeRef::new(sheet.name.clone(), &range));
Ok(response)
}
fn set_range_values(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let action = request.action.clone();
let args: SetRangeValuesArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
let range = CellRange::parse(&args.range)?;
let recalculate = args.recalculate.unwrap_or(artifact.auto_recalculate);
let values = normalize_value_matrix(args.values, &request.action)?;
{
let sheet = artifact.sheet_lookup_mut(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
sheet.set_values_matrix(&range, &values)?;
}
if recalculate {
artifact.recalculate();
}
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
action.clone(),
format!("Updated range `{}`", args.range),
snapshot_for_artifact(artifact),
);
let sheet = artifact.sheet_lookup(
&action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
response.range = Some(sheet.get_range_view(&range));
response.range_ref = Some(SpreadsheetCellRangeRef::new(sheet.name.clone(), &range));
Ok(response)
}
fn set_cell_formula(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let action = request.action.clone();
let args: SetCellFormulaArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
let address = CellAddress::parse(&args.address)?;
let recalculate = args.recalculate.unwrap_or(artifact.auto_recalculate);
{
let sheet = artifact.sheet_lookup_mut(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
sheet.set_formula(address, Some(normalize_formula(args.formula)))?;
}
if recalculate {
artifact.recalculate();
}
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
action.clone(),
format!("Updated formula in `{}`", args.address),
snapshot_for_artifact(artifact),
);
let sheet = artifact.sheet_lookup(
&action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
response.cell = Some(sheet.get_cell_view(address));
response.cell_ref = Some(sheet.cell_ref(&args.address)?);
Ok(response)
}
fn set_range_formula(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let action = request.action.clone();
let args: SetRangeFormulaArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
let range = CellRange::parse(&args.range)?;
let recalculate = args.recalculate.unwrap_or(artifact.auto_recalculate);
{
let sheet = artifact.sheet_lookup_mut(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
sheet.set_range_to_formula(&range, Some(normalize_formula(args.formula)))?;
}
if recalculate {
artifact.recalculate();
}
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
action.clone(),
format!("Updated range `{}` to a single formula", args.range),
snapshot_for_artifact(artifact),
);
let sheet = artifact.sheet_lookup(
&action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
response.range = Some(sheet.get_range_view(&range));
response.range_ref = Some(SpreadsheetCellRangeRef::new(sheet.name.clone(), &range));
Ok(response)
}
fn set_range_formulas(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let action = request.action.clone();
let args: SetRangeFormulasArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
let range = CellRange::parse(&args.range)?;
let recalculate = args.recalculate.unwrap_or(artifact.auto_recalculate);
let formulas = args
.formulas
.into_iter()
.map(|row| {
row.into_iter()
.map(|value| value.map(normalize_formula))
.collect()
})
.collect::<Vec<Vec<Option<String>>>>();
{
let sheet = artifact.sheet_lookup_mut(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
sheet.set_formulas_matrix(&range, &formulas)?;
}
if recalculate {
artifact.recalculate();
}
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
action.clone(),
format!("Updated formulas in `{}`", args.range),
snapshot_for_artifact(artifact),
);
let sheet = artifact.sheet_lookup(
&action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
response.range = Some(sheet.get_range_view(&range));
response.range_ref = Some(SpreadsheetCellRangeRef::new(sheet.name.clone(), &range));
Ok(response)
}
fn set_cell_style(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: SetCellStyleArgs = parse_args(&request.action, &request.args)?;
let range = CellRange::parse(&args.address)?;
self.set_style_impl(
request,
args.sheet_name,
args.sheet_index,
range,
args.style_index,
)
}
fn set_range_style(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: SetRangeStyleArgs = parse_args(&request.action, &request.args)?;
let range = CellRange::parse(&args.range)?;
self.set_style_impl(
request,
args.sheet_name,
args.sheet_index,
range,
args.style_index,
)
}
fn set_style_impl(
&mut self,
request: SpreadsheetArtifactRequest,
sheet_name: Option<String>,
sheet_index: Option<u32>,
range: CellRange,
style_index: u32,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let action = request.action.clone();
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
{
let sheet = artifact.sheet_lookup_mut(
&request.action,
sheet_name.as_deref(),
sheet_index.map(|value| value as usize),
)?;
sheet.set_style_index(&range, style_index)?;
}
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
action.clone(),
format!("Updated style index {} on `{}`", style_index, range.to_a1()),
snapshot_for_artifact(artifact),
);
let sheet = artifact.sheet_lookup(
&action,
sheet_name.as_deref(),
sheet_index.map(|value| value as usize),
)?;
response.range = Some(sheet.get_range_view(&range));
response.range_ref = Some(SpreadsheetCellRangeRef::new(sheet.name.clone(), &range));
Ok(response)
}
fn clear_range(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let action = request.action.clone();
let args: ClearRangeArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
let range = CellRange::parse(&args.range)?;
let recalculate = args.recalculate.unwrap_or(artifact.auto_recalculate);
{
let sheet = artifact.sheet_lookup_mut(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
sheet.clear_range(&range, args.fields.as_deref())?;
}
if recalculate {
artifact.recalculate();
}
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
action.clone(),
format!("Cleared range `{}`", args.range),
snapshot_for_artifact(artifact),
);
let sheet = artifact.sheet_lookup(
&action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
response.range = Some(sheet.get_range_view(&range));
response.range_ref = Some(SpreadsheetCellRangeRef::new(sheet.name.clone(), &range));
Ok(response)
}
fn merge_range(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let action = request.action.clone();
let args: MergeRangeArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
let range = CellRange::parse(&args.range)?;
{
let sheet = artifact.sheet_lookup_mut(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
sheet.merge_cells(&range, args.raise_on_conflict.unwrap_or(false))?;
}
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
action.clone(),
format!("Merged `{}`", args.range),
snapshot_for_artifact(artifact),
);
let sheet = artifact.sheet_lookup(
&action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
response.range = Some(sheet.get_range_view(&range));
response.range_ref = Some(SpreadsheetCellRangeRef::new(sheet.name.clone(), &range));
Ok(response)
}
fn cite_cell(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let action = request.action.clone();
let args: CiteCellArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
let address = CellAddress::parse(&args.address)?;
let citation = SpreadsheetCitation {
tether_id: args.tether_id,
start_line: args.start_line,
end_line: args.end_line,
content_reference_type: args.content_reference_type,
source_type: args.source_type,
};
{
let sheet = artifact.sheet_lookup_mut(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
let cell_ref = sheet.cell_ref(&args.address)?;
cell_ref.cite(sheet, citation)?;
}
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
action.clone(),
format!("Attached citation to cell `{}`", args.address),
snapshot_for_artifact(artifact),
);
let sheet = artifact.sheet_lookup(
&action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
response.cell = Some(sheet.get_cell_view(address));
response.cell_ref = Some(sheet.cell_ref(&args.address)?);
Ok(response)
}
fn unmerge_range(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let action = request.action.clone();
let args: RangeArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
let range = CellRange::parse(&args.range)?;
{
let sheet = artifact.sheet_lookup_mut(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
sheet.unmerge_cells(&range);
}
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
action.clone(),
format!("Unmerged `{}`", args.range),
snapshot_for_artifact(artifact),
);
let sheet = artifact.sheet_lookup(
&action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
response.range = Some(sheet.get_range_view(&range));
Ok(response)
}
fn cite_range(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let action = request.action.clone();
let args: CiteRangeArgs = parse_args(&request.action, &request.args)?;
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
let range = CellRange::parse(&args.range)?;
let citation = SpreadsheetCitation {
tether_id: args.tether_id,
start_line: args.start_line,
end_line: args.end_line,
content_reference_type: args.content_reference_type,
source_type: args.source_type,
};
{
let sheet = artifact.sheet_lookup_mut(
&request.action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
sheet.cite_range(&range, citation)?;
}
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
action.clone(),
format!("Attached citation to `{}`", args.range),
snapshot_for_artifact(artifact),
);
let sheet = artifact.sheet_lookup(
&action,
args.sheet_name.as_deref(),
args.sheet_index.map(|value| value as usize),
)?;
response.range = Some(sheet.get_range_view(&range));
Ok(response)
}
fn calculate(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
artifact.recalculate();
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
"Recalculated workbook".to_string(),
snapshot_for_artifact(artifact),
);
response.workbook_summary = Some(artifact.summary());
Ok(response)
}
fn serialize_dict(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact(&artifact_id, &request.action)?;
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
"Serialized workbook to dict".to_string(),
snapshot_for_artifact(artifact),
);
response.serialized_dict = Some(artifact.to_dict()?);
Ok(response)
}
fn serialize_json(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact(&artifact_id, &request.action)?;
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
"Serialized workbook to JSON".to_string(),
snapshot_for_artifact(artifact),
);
response.serialized_json = Some(artifact.to_json()?);
Ok(response)
}
fn serialize_bytes(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let artifact_id = required_artifact_id(&request)?;
let artifact = self.get_artifact(&artifact_id, &request.action)?;
let mut response = SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
"Serialized workbook to bytes".to_string(),
snapshot_for_artifact(artifact),
);
response.serialized_bytes_base64 = Some(artifact.to_bytes_base64());
Ok(response)
}
fn deserialize_dict(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: DeserializeDictArgs = parse_args(&request.action, &request.args)?;
let artifact = SpreadsheetArtifact::from_dict(args.data, args.artifact_id)?;
let artifact_id = artifact.artifact_id.clone();
let snapshot = snapshot_for_artifact(&artifact);
self.documents.insert(artifact_id.clone(), artifact);
Ok(SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
"Deserialized workbook from dict".to_string(),
snapshot,
))
}
fn deserialize_json(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: DeserializeJsonArgs = parse_args(&request.action, &request.args)?;
let artifact = SpreadsheetArtifact::from_json(args.json, args.artifact_id)?;
let artifact_id = artifact.artifact_id.clone();
let snapshot = snapshot_for_artifact(&artifact);
self.documents.insert(artifact_id.clone(), artifact);
Ok(SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
"Deserialized workbook from JSON".to_string(),
snapshot,
))
}
fn deserialize_bytes(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let args: DeserializeBytesArgs = parse_args(&request.action, &request.args)?;
let bytes = BASE64_STANDARD.decode(args.bytes_base64).map_err(|error| {
SpreadsheetArtifactError::Serialization {
message: error.to_string(),
}
})?;
let artifact = SpreadsheetArtifact::from_bytes(&bytes, args.artifact_id)?;
let artifact_id = artifact.artifact_id.clone();
let snapshot = snapshot_for_artifact(&artifact);
self.documents.insert(artifact_id.clone(), artifact);
Ok(SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
"Deserialized workbook from bytes".to_string(),
snapshot,
))
}
fn delete_artifact(
&mut self,
request: SpreadsheetArtifactRequest,
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
let artifact_id = required_artifact_id(&request)?;
let artifact = self.documents.remove(&artifact_id).ok_or_else(|| {
SpreadsheetArtifactError::UnknownArtifactId {
action: request.action.clone(),
artifact_id: artifact_id.clone(),
}
})?;
let snapshot = snapshot_for_artifact(&artifact);
Ok(SpreadsheetArtifactResponse::new(
artifact_id,
request.action,
"Deleted spreadsheet artifact".to_string(),
snapshot,
))
}
fn get_artifact(
&self,
artifact_id: &str,
action: &str,
) -> Result<&SpreadsheetArtifact, SpreadsheetArtifactError> {
self.documents
.get(artifact_id)
.ok_or_else(|| SpreadsheetArtifactError::UnknownArtifactId {
action: action.to_string(),
artifact_id: artifact_id.to_string(),
})
}
fn get_artifact_mut(
&mut self,
artifact_id: &str,
action: &str,
) -> Result<&mut SpreadsheetArtifact, SpreadsheetArtifactError> {
self.documents.get_mut(artifact_id).ok_or_else(|| {
SpreadsheetArtifactError::UnknownArtifactId {
action: action.to_string(),
artifact_id: artifact_id.to_string(),
}
})
}
}
#[derive(Debug, Clone, Serialize)]
pub struct SpreadsheetArtifactResponse {
pub artifact_id: String,
pub action: String,
pub summary: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub exported_paths: Vec<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")]
pub artifact_snapshot: Option<SpreadsheetArtifactSnapshot>,
#[serde(skip_serializing_if = "Option::is_none")]
pub workbook_summary: Option<SpreadsheetSummary>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sheet_list: Option<Vec<SpreadsheetSheetSummary>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sheet_ref: Option<SpreadsheetSheetRef>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cell_ref: Option<SpreadsheetCellRef>,
#[serde(skip_serializing_if = "Option::is_none")]
pub range_ref: Option<SpreadsheetCellRangeRef>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cell: Option<crate::SpreadsheetCellView>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cell_field: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub range: Option<SpreadsheetRangeView>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rendered_text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub row_height: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub serialized_dict: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub serialized_json: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub serialized_bytes_base64: Option<String>,
}
impl SpreadsheetArtifactResponse {
fn new(
artifact_id: String,
action: String,
summary: String,
artifact_snapshot: SpreadsheetArtifactSnapshot,
) -> Self {
Self {
artifact_id,
action,
summary,
exported_paths: Vec::new(),
artifact_snapshot: Some(artifact_snapshot),
workbook_summary: None,
sheet_list: None,
sheet_ref: None,
cell_ref: None,
range_ref: None,
cell: None,
cell_field: None,
range: None,
rendered_text: None,
row_height: None,
serialized_dict: None,
serialized_json: None,
serialized_bytes_base64: None,
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct SpreadsheetSheetRef {
pub sheet_name: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct SpreadsheetArtifactSnapshot {
pub sheet_count: usize,
pub sheet_names: Vec<String>,
pub sheets: Vec<SpreadsheetSheetSnapshot>,
}
#[derive(Debug, Clone, Serialize)]
pub struct SpreadsheetSheetSnapshot {
pub sheet_id: String,
pub name: String,
pub filled_rows: usize,
pub filled_columns: usize,
pub minimum_range_filled: String,
pub merged_range_count: usize,
}
#[derive(Debug, Deserialize)]
struct CreateArgs {
name: Option<String>,
auto_recalculate: Option<bool>,
}
#[derive(Debug, Deserialize)]
struct PathArgs {
path: PathBuf,
}
#[derive(Debug, Deserialize)]
struct SaveArgs {
path: PathBuf,
file_type: Option<String>,
}
#[derive(Debug, Deserialize)]
struct SheetLookupArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
range: Option<String>,
}
#[derive(Debug, Deserialize)]
struct CreateSheetArgs {
name: String,
}
#[derive(Debug, Deserialize)]
struct RenameSheetArgs {
old_name: Option<String>,
sheet_index: Option<u32>,
new_name: String,
}
#[derive(Debug, Deserialize)]
struct DeleteSheetArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
}
#[derive(Debug, Deserialize)]
struct SetSheetPropertiesArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
default_row_height: Option<f64>,
default_column_width: Option<f64>,
show_grid_lines: Option<bool>,
}
#[derive(Debug, Deserialize)]
struct SetColumnWidthsArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
reference: String,
width: f64,
}
#[derive(Debug, Deserialize)]
struct SetColumnWidthsBulkArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
widths: BTreeMap<String, f64>,
}
#[derive(Debug, Deserialize)]
struct SetRowHeightArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
row_index: u32,
height: Option<f64>,
}
#[derive(Debug, Deserialize)]
struct SetRowHeightsArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
start_row_index: u32,
end_row_index: u32,
height: Option<f64>,
}
#[derive(Debug, Deserialize)]
struct SetRowHeightsBulkArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
heights: BTreeMap<u32, Option<f64>>,
}
#[derive(Debug, Deserialize)]
struct GetRowHeightArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
row_index: u32,
}
#[derive(Debug, Deserialize)]
struct CellAddressArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
address: String,
}
#[derive(Debug, Deserialize)]
struct CellIndicesArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
column_index: u32,
row_index: u32,
}
#[derive(Debug, Deserialize)]
struct CellFieldArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
address: String,
field: String,
}
#[derive(Debug, Deserialize)]
struct CellFieldByIndicesArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
column_index: u32,
row_index: u32,
field: String,
}
#[derive(Debug, Deserialize)]
struct RangeArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
range: String,
}
#[derive(Debug, Deserialize)]
struct SetCellValueArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
address: String,
value: Value,
recalculate: Option<bool>,
}
#[derive(Debug, Deserialize)]
struct SetRangeValuesArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
range: String,
values: Vec<Vec<Value>>,
recalculate: Option<bool>,
}
#[derive(Debug, Deserialize)]
struct SetRangeValueArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
range: String,
value: Value,
recalculate: Option<bool>,
}
#[derive(Debug, Deserialize)]
struct SetCellFormulaArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
address: String,
formula: String,
recalculate: Option<bool>,
}
#[derive(Debug, Deserialize)]
struct SetRangeFormulasArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
range: String,
formulas: Vec<Vec<Option<String>>>,
recalculate: Option<bool>,
}
#[derive(Debug, Deserialize)]
struct SetRangeFormulaArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
range: String,
formula: String,
recalculate: Option<bool>,
}
#[derive(Debug, Deserialize)]
struct SetCellStyleArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
address: String,
style_index: u32,
}
#[derive(Debug, Deserialize)]
struct SetRangeStyleArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
range: String,
style_index: u32,
}
#[derive(Debug, Deserialize)]
struct ClearRangeArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
range: String,
fields: Option<Vec<String>>,
recalculate: Option<bool>,
}
#[derive(Debug, Deserialize)]
struct MergeRangeArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
range: String,
raise_on_conflict: Option<bool>,
}
#[derive(Debug, Deserialize)]
struct CiteRangeArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
range: String,
tether_id: String,
start_line: Option<u32>,
end_line: Option<u32>,
content_reference_type: Option<String>,
source_type: Option<String>,
}
#[derive(Debug, Deserialize)]
struct CiteCellArgs {
sheet_name: Option<String>,
sheet_index: Option<u32>,
address: String,
tether_id: String,
start_line: Option<u32>,
end_line: Option<u32>,
content_reference_type: Option<String>,
source_type: Option<String>,
}
#[derive(Debug, Deserialize)]
struct DeserializeDictArgs {
data: Value,
artifact_id: Option<String>,
}
#[derive(Debug, Deserialize)]
struct DeserializeJsonArgs {
json: String,
artifact_id: Option<String>,
}
#[derive(Debug, Deserialize)]
struct DeserializeBytesArgs {
bytes_base64: String,
artifact_id: Option<String>,
}
fn snapshot_for_artifact(artifact: &SpreadsheetArtifact) -> SpreadsheetArtifactSnapshot {
SpreadsheetArtifactSnapshot {
sheet_count: artifact.sheets.len(),
sheet_names: artifact.list_sheet_names(),
sheets: artifact
.sheets
.iter()
.map(|sheet| SpreadsheetSheetSnapshot {
sheet_id: sheet.sheet_id.clone(),
name: sheet.name.clone(),
filled_rows: sheet.filled_rows(),
filled_columns: sheet.filled_columns(),
minimum_range_filled: sheet.minimum_range_filled(),
merged_range_count: sheet.merged_ranges.len(),
})
.collect(),
}
}
fn sheet_reference(sheet: &crate::SpreadsheetSheet) -> SpreadsheetSheetRef {
SpreadsheetSheetRef {
sheet_name: sheet.name.clone(),
}
}
fn normalize_optional_cell_value(
value: Value,
) -> Result<Option<SpreadsheetCellValue>, SpreadsheetArtifactError> {
if value.is_null() {
Ok(None)
} else {
Ok(Some(SpreadsheetCellValue::try_from(value)?))
}
}
fn normalize_value_matrix(
values: Vec<Vec<Value>>,
action: &str,
) -> Result<Vec<Vec<Option<SpreadsheetCellValue>>>, SpreadsheetArtifactError> {
if values.is_empty() {
return Err(SpreadsheetArtifactError::InvalidArgs {
action: action.to_string(),
message: "values matrix cannot be empty".to_string(),
});
}
let width = values.first().map(Vec::len).unwrap_or(0);
if width == 0 || values.iter().any(|row| row.len() != width) {
return Err(SpreadsheetArtifactError::InvalidArgs {
action: action.to_string(),
message: "values matrix must be rectangular".to_string(),
});
}
values
.into_iter()
.map(|row| {
row.into_iter()
.map(normalize_optional_cell_value)
.collect::<Result<Vec<_>, _>>()
})
.collect::<Result<Vec<_>, _>>()
}
fn normalize_formula(formula: String) -> String {
let trimmed = formula.trim();
if trimmed.starts_with('=') {
trimmed.to_string()
} else {
format!("={trimmed}")
}
}
fn required_artifact_id(
request: &SpreadsheetArtifactRequest,
) -> Result<String, SpreadsheetArtifactError> {
request
.artifact_id
.clone()
.ok_or_else(|| SpreadsheetArtifactError::MissingArtifactId {
action: request.action.clone(),
})
}
fn parse_args<T: for<'de> Deserialize<'de>>(
action: &str,
value: &Value,
) -> Result<T, SpreadsheetArtifactError> {
serde_json::from_value(value.clone()).map_err(|error| SpreadsheetArtifactError::InvalidArgs {
action: action.to_string(),
message: error.to_string(),
})
}
fn resolve_path(cwd: &Path, path: &Path) -> PathBuf {
if path.is_absolute() {
path.to_path_buf()
} else {
cwd.join(path)
}
}