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, 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, 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), }] } "render_workbook" | "render_sheet" | "render_range" => { let args: RenderArgs = parse_args(&self.action, &self.args)?; args.output_path .map(|path| { vec![PathAccessRequirement { action: self.action.clone(), kind: PathAccessKind::Write, path: resolve_path(cwd, &path), }] }) .unwrap_or_default() } _ => Vec::new(), }; Ok(access) } } #[derive(Debug, Default)] pub struct SpreadsheetArtifactManager { documents: HashMap, } impl SpreadsheetArtifactManager { pub fn execute( &mut self, request: SpreadsheetArtifactRequest, cwd: &Path, ) -> Result { 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), "render_workbook" => self.render_workbook(request, cwd), "render_sheet" => self.render_sheet(request, cwd), "render_range" => self.render_range(request, cwd), "get_summary" => self.get_summary(request), "list_sheets" => self.list_sheets(request), "get_sheet" => self.get_sheet(request), "inspect" => self.inspect(request), "list_charts" => self.list_charts(request), "get_chart" => self.get_chart(request), "create_chart" => self.create_chart(request), "add_chart_series" => self.add_chart_series(request), "set_chart_properties" => self.set_chart_properties(request), "delete_chart" => self.delete_chart(request), "list_tables" => self.list_tables(request), "get_table" => self.get_table(request), "create_table" => self.create_table(request), "set_table_style" => self.set_table_style(request), "clear_table_filters" => self.clear_table_filters(request), "reapply_table_filters" => self.reapply_table_filters(request), "rename_table_column" => self.rename_table_column(request), "set_table_column_totals" => self.set_table_column_totals(request), "delete_table" => self.delete_table(request), "list_conditional_formats" => self.list_conditional_formats(request), "add_conditional_format" => self.add_conditional_format(request), "delete_conditional_format" => self.delete_conditional_format(request), "list_pivot_tables" => self.list_pivot_tables(request), "get_pivot_table" => self.get_pivot_table(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), "create_text_style" => self.create_text_style(request), "get_text_style" => self.get_text_style(request), "create_fill" => self.create_fill(request), "get_fill" => self.get_fill(request), "create_border" => self.create_border(request), "get_border" => self.get_border(request), "create_number_format" => self.create_number_format(request), "get_number_format" => self.get_number_format(request), "create_cell_format" => self.create_cell_format(request), "get_cell_format" => self.get_cell_format(request), "create_differential_format" => self.create_differential_format(request), "get_differential_format" => self.get_differential_format(request), "get_cell_format_summary" => self.get_cell_format_summary(request), "get_range_format_summary" => self.get_range_format_summary(request), "get_reference" => self.get_reference(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 { 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 { 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 { 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 { 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 { 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 render_workbook( &mut self, request: SpreadsheetArtifactRequest, cwd: &Path, ) -> Result { let args: RenderArgs = parse_args(&request.action, &request.args)?; let artifact_id = required_artifact_id(&request)?; let artifact = self.get_artifact(&artifact_id, &request.action)?; let rendered = artifact.render_workbook_previews(cwd, &render_options_from_args(args)?)?; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Rendered workbook to {} preview files", rendered.len()), snapshot_for_artifact(artifact), ); response.exported_paths = rendered.into_iter().map(|output| output.path).collect(); Ok(response) } fn render_sheet( &mut self, request: SpreadsheetArtifactRequest, cwd: &Path, ) -> Result { let args: RenderArgs = 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 rendered = artifact.render_sheet_preview(cwd, sheet, &render_options_from_args(args)?)?; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Rendered sheet `{}`", sheet.name), snapshot_for_artifact(artifact), ); response.sheet_ref = Some(sheet_reference(sheet)); response.exported_paths.push(rendered.path); response.rendered_html = Some(rendered.html); response.rendered_text = Some(sheet.to_rendered_text(None)); Ok(response) } fn render_range( &mut self, request: SpreadsheetArtifactRequest, cwd: &Path, ) -> Result { let args: RenderArgs = 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_text = args.range .clone() .ok_or_else(|| SpreadsheetArtifactError::InvalidArgs { action: request.action.clone(), message: "range is required".to_string(), })?; let range = CellRange::parse(&range_text)?; let rendered = artifact.render_range_preview(cwd, sheet, &range, &render_options_from_args(args)?)?; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Rendered range `{range_text}` from `{}`", sheet.name), snapshot_for_artifact(artifact), ); response.sheet_ref = Some(sheet_reference(sheet)); response.range_ref = Some(SpreadsheetCellRangeRef::new(sheet.name.clone(), &range)); response.exported_paths.push(rendered.path); response.rendered_html = Some(rendered.html); response.rendered_text = Some(sheet.to_rendered_text(Some(&range))); Ok(response) } fn list_sheets( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { 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 { 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.top_left_style_index = range .as_ref() .map(|entry| sheet.top_left_style_index(entry)); response.range_format = range.as_ref().map(|entry| sheet.range_format(entry)); response.cell_format_summary = response .top_left_style_index .and_then(|style_index| artifact.cell_format_summary(style_index)); response.serialized_dict = Some(sheet.to_dict()?); Ok(response) } fn inspect( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { 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 { 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 list_charts( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { 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 = args.range.as_deref().map(CellRange::parse).transpose()?; let charts = sheet.list_charts(range.as_ref())?; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Listed {} charts on `{}`", charts.len(), sheet.name), snapshot_for_artifact(artifact), ); response.sheet_ref = Some(sheet_reference(sheet)); response.range_ref = range .as_ref() .map(|entry| SpreadsheetCellRangeRef::new(sheet.name.clone(), entry)); response.chart_list = Some(charts); Ok(response) } fn get_chart( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: ChartLookupArgs = 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 chart = sheet .get_chart(&request.action, chart_lookup_from_args(&args))? .clone(); let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Retrieved chart `{}` from `{}`", chart.id, sheet.name), snapshot_for_artifact(artifact), ); response.sheet_ref = Some(sheet_reference(sheet)); response.chart_list = Some(vec![chart]); Ok(response) } fn create_chart( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: CreateChartArgs = parse_args(&request.action, &request.args)?; let artifact_id = required_artifact_id(&request)?; let artifact = self.get_artifact_mut(&artifact_id, &request.action)?; if let Some(source_sheet_name) = args.source_sheet_name.as_deref() { artifact.sheet_lookup(&request.action, Some(source_sheet_name), None)?; } let source_range = CellRange::parse(&args.source_range)?; let chart = { let sheet = artifact.sheet_lookup_mut( &request.action, args.sheet_name.as_deref(), args.sheet_index.map(|value| value as usize), )?; let chart_id = sheet.create_chart( &request.action, args.chart_type, args.source_sheet_name.or_else(|| Some(sheet.name.clone())), &source_range, args.options.unwrap_or_default(), )?; sheet .get_chart( &request.action, crate::SpreadsheetChartLookup { id: Some(chart_id), index: None, }, )? .clone() }; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Created chart `{}`", chart.id), snapshot_for_artifact(artifact), ); response.chart_list = Some(vec![chart]); Ok(response) } fn add_chart_series( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: AddChartSeriesArgs = parse_args(&request.action, &request.args)?; let artifact_id = required_artifact_id(&request)?; let artifact = self.get_artifact_mut(&artifact_id, &request.action)?; let chart = { let sheet = artifact.sheet_lookup_mut( &request.action, args.sheet_name.as_deref(), args.sheet_index.map(|value| value as usize), )?; let series_id = sheet.add_chart_series( &request.action, crate::SpreadsheetChartLookup { id: args.chart_id, index: args.chart_index.map(|value| value as usize), }, args.series, )?; let chart = sheet.get_chart( &request.action, crate::SpreadsheetChartLookup { id: args.chart_id.or(Some(series_id).and(None)), index: args.chart_index.map(|value| value as usize), }, ); match chart { Ok(chart) => chart.clone(), Err(_) => sheet .get_chart( &request.action, crate::SpreadsheetChartLookup { id: None, index: args.chart_index.map(|value| value as usize), }, )? .clone(), } }; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Added chart series to chart `{}`", chart.id), snapshot_for_artifact(artifact), ); response.chart_list = Some(vec![chart]); Ok(response) } fn set_chart_properties( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: SetChartPropertiesArgs = parse_args(&request.action, &request.args)?; let artifact_id = required_artifact_id(&request)?; let artifact = self.get_artifact_mut(&artifact_id, &request.action)?; let chart = { let sheet = artifact.sheet_lookup_mut( &request.action, args.lookup.sheet_name.as_deref(), args.lookup.sheet_index.map(|value| value as usize), )?; let lookup = chart_lookup_from_args(&args.lookup); sheet.set_chart_properties(&request.action, lookup.clone(), args.properties)?; sheet.get_chart(&request.action, lookup)?.clone() }; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Updated chart `{}`", chart.id), snapshot_for_artifact(artifact), ); response.chart_list = Some(vec![chart]); Ok(response) } fn delete_chart( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: ChartLookupArgs = 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_name = { let sheet = artifact.sheet_lookup_mut( &request.action, args.sheet_name.as_deref(), args.sheet_index.map(|value| value as usize), )?; let name = sheet.name.clone(); sheet.delete_chart(&request.action, chart_lookup_from_args(&args))?; name }; let action = request.action.clone(); let mut response = SpreadsheetArtifactResponse::new( artifact_id, action.clone(), format!("Deleted chart from `{sheet_name}`"), snapshot_for_artifact(artifact), ); response.sheet_list = Some(vec![ artifact .sheet_lookup(&action, Some(&sheet_name), None)? .summary(), ]); Ok(response) } fn list_tables( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { 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 = args.range.as_deref().map(CellRange::parse).transpose()?; let tables = sheet.list_tables(range.as_ref())?; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Listed {} tables on `{}`", tables.len(), sheet.name), snapshot_for_artifact(artifact), ); response.sheet_ref = Some(sheet_reference(sheet)); response.range_ref = range .as_ref() .map(|entry| SpreadsheetCellRangeRef::new(sheet.name.clone(), entry)); response.table_list = Some(tables); Ok(response) } fn get_table( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: TableLookupArgs = 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 table = sheet.get_table_view(&request.action, table_lookup_from_args(&args))?; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Retrieved table `{}` from `{}`", table.name, sheet.name), snapshot_for_artifact(artifact), ); response.sheet_ref = Some(sheet_reference(sheet)); response.table_list = Some(vec![table]); Ok(response) } fn create_table( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: CreateTableArgs = 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 table = { let sheet = artifact.sheet_lookup_mut( &request.action, args.sheet_name.as_deref(), args.sheet_index.map(|value| value as usize), )?; let table_id = sheet.create_table(&request.action, &range, args.options)?; sheet.get_table_view( &request.action, crate::SpreadsheetTableLookup { name: None, display_name: None, id: Some(table_id), }, )? }; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Created table `{}`", table.name), snapshot_for_artifact(artifact), ); response.table_list = Some(vec![table]); Ok(response) } fn set_table_style( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: SetTableStyleArgs = parse_args(&request.action, &request.args)?; let artifact_id = required_artifact_id(&request)?; let artifact = self.get_artifact_mut(&artifact_id, &request.action)?; let table = { let sheet = artifact.sheet_lookup_mut( &request.action, args.lookup.sheet_name.as_deref(), args.lookup.sheet_index.map(|value| value as usize), )?; let lookup = table_lookup_from_args(&args.lookup); sheet.set_table_style(&request.action, lookup.clone(), args.options)?; sheet.get_table_view(&request.action, lookup)? }; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Updated table style for `{}`", table.name), snapshot_for_artifact(artifact), ); response.table_list = Some(vec![table]); Ok(response) } fn clear_table_filters( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: TableLookupArgs = parse_args(&request.action, &request.args)?; let artifact_id = required_artifact_id(&request)?; let artifact = self.get_artifact_mut(&artifact_id, &request.action)?; let table = { let sheet = artifact.sheet_lookup_mut( &request.action, args.sheet_name.as_deref(), args.sheet_index.map(|value| value as usize), )?; let lookup = table_lookup_from_args(&args); sheet.clear_table_filters(&request.action, lookup.clone())?; sheet.get_table_view(&request.action, lookup)? }; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Cleared table filters for `{}`", table.name), snapshot_for_artifact(artifact), ); response.table_list = Some(vec![table]); Ok(response) } fn reapply_table_filters( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: TableLookupArgs = parse_args(&request.action, &request.args)?; let artifact_id = required_artifact_id(&request)?; let artifact = self.get_artifact_mut(&artifact_id, &request.action)?; let table = { let sheet = artifact.sheet_lookup_mut( &request.action, args.sheet_name.as_deref(), args.sheet_index.map(|value| value as usize), )?; let lookup = table_lookup_from_args(&args); sheet.reapply_table_filters(&request.action, lookup.clone())?; sheet.get_table_view(&request.action, lookup)? }; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Reapplied table filters for `{}`", table.name), snapshot_for_artifact(artifact), ); response.table_list = Some(vec![table]); Ok(response) } fn rename_table_column( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: RenameTableColumnArgs = parse_args(&request.action, &request.args)?; let artifact_id = required_artifact_id(&request)?; let artifact = self.get_artifact_mut(&artifact_id, &request.action)?; let column = { let sheet = artifact.sheet_lookup_mut( &request.action, args.lookup.sheet_name.as_deref(), args.lookup.sheet_index.map(|value| value as usize), )?; sheet.rename_table_column( &request.action, table_lookup_from_args(&args.lookup), args.column_id, args.column_name.as_deref(), args.new_name, )? }; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Renamed table column to `{}`", column.name), snapshot_for_artifact(artifact), ); response.serialized_dict = Some(to_serialized_value(column)?); Ok(response) } fn set_table_column_totals( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: SetTableColumnTotalsArgs = parse_args(&request.action, &request.args)?; let artifact_id = required_artifact_id(&request)?; let artifact = self.get_artifact_mut(&artifact_id, &request.action)?; let column = { let sheet = artifact.sheet_lookup_mut( &request.action, args.lookup.sheet_name.as_deref(), args.lookup.sheet_index.map(|value| value as usize), )?; sheet.set_table_column_totals( &request.action, table_lookup_from_args(&args.lookup), args.column_id, args.column_name.as_deref(), args.totals_row_label, args.totals_row_function, )? }; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Updated totals metadata for column `{}`", column.name), snapshot_for_artifact(artifact), ); response.serialized_dict = Some(to_serialized_value(column)?); Ok(response) } fn delete_table( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: TableLookupArgs = 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_name = { let sheet = artifact.sheet_lookup_mut( &request.action, args.sheet_name.as_deref(), args.sheet_index.map(|value| value as usize), )?; let name = sheet.name.clone(); sheet.delete_table(&request.action, table_lookup_from_args(&args))?; name }; let action = request.action.clone(); let mut response = SpreadsheetArtifactResponse::new( artifact_id, action.clone(), format!("Deleted table from `{sheet_name}`"), snapshot_for_artifact(artifact), ); response.sheet_list = Some(vec![ artifact .sheet_lookup(&action, Some(&sheet_name), None)? .summary(), ]); Ok(response) } fn list_conditional_formats( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { 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 = args.range.as_deref().map(CellRange::parse).transpose()?; let formats = sheet.list_conditional_formats(range.as_ref()); let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!( "Listed {} conditional formats on `{}`", formats.len(), sheet.name ), snapshot_for_artifact(artifact), ); response.sheet_ref = Some(sheet_reference(sheet)); response.range_ref = range .as_ref() .map(|entry| SpreadsheetCellRangeRef::new(sheet.name.clone(), entry)); response.conditional_format_list = Some(formats); Ok(response) } fn add_conditional_format( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: AddConditionalFormatArgs = 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_name = artifact .sheet_lookup( &request.action, args.sheet_name.as_deref(), args.sheet_index.map(|value| value as usize), )? .name .clone(); let format_id = artifact.add_conditional_format(&request.action, &sheet_name, args.format)?; let format = artifact .sheet_lookup(&request.action, Some(&sheet_name), None)? .conditional_formats .iter() .find(|entry| entry.id == format_id) .cloned() .ok_or_else(|| SpreadsheetArtifactError::Serialization { message: format!("created conditional format `{format_id}` was not available"), })?; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Created conditional format `{format_id}`"), snapshot_for_artifact(artifact), ); response.conditional_format_list = Some(vec![format]); Ok(response) } fn delete_conditional_format( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: DeleteConditionalFormatArgs = 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_name = artifact .sheet_lookup( &request.action, args.sheet_name.as_deref(), args.sheet_index.map(|value| value as usize), )? .name .clone(); artifact.delete_conditional_format(&request.action, &sheet_name, args.id)?; let action = request.action.clone(); let mut response = SpreadsheetArtifactResponse::new( artifact_id, action.clone(), format!("Deleted conditional format `{}`", args.id), snapshot_for_artifact(artifact), ); response.sheet_list = Some(vec![ artifact .sheet_lookup(&action, Some(&sheet_name), None)? .summary(), ]); Ok(response) } fn list_pivot_tables( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { 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 = args.range.as_deref().map(CellRange::parse).transpose()?; let pivots = sheet.list_pivot_tables(range.as_ref())?; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Listed {} pivot tables on `{}`", pivots.len(), sheet.name), snapshot_for_artifact(artifact), ); response.sheet_ref = Some(sheet_reference(sheet)); response.range_ref = range .as_ref() .map(|entry| SpreadsheetCellRangeRef::new(sheet.name.clone(), entry)); response.pivot_table_list = Some(pivots); Ok(response) } fn get_pivot_table( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: PivotTableLookupArgs = 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 pivot = sheet .get_pivot_table(&request.action, pivot_lookup_from_args(&args))? .clone(); let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!( "Retrieved pivot table `{}` from `{}`", pivot.name, sheet.name ), snapshot_for_artifact(artifact), ); response.sheet_ref = Some(sheet_reference(sheet)); response.pivot_table_list = Some(vec![pivot]); Ok(response) } fn rename_sheet( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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() }; artifact.validate_conditional_formats(&request.action, &sheet_summary.name)?; 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 create_text_style( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: CreateTextStyleArgs = parse_args(&request.action, &request.args)?; let artifact_id = required_artifact_id(&request)?; let artifact = self.get_artifact_mut(&artifact_id, &request.action)?; let style_id = artifact.create_text_style( args.style, args.source_style_id, args.merge_with_existing_components.unwrap_or(false), )?; let style = artifact.get_text_style(style_id).cloned().ok_or_else(|| { SpreadsheetArtifactError::Serialization { message: format!("created text style `{style_id}` was not available"), } })?; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Created text style `{style_id}`"), snapshot_for_artifact(artifact), ); response.style_id = Some(style_id); response.serialized_dict = Some(to_serialized_value(style)?); Ok(response) } fn get_text_style( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: GetStyleArgs = parse_args(&request.action, &request.args)?; let artifact_id = required_artifact_id(&request)?; let artifact = self.get_artifact(&artifact_id, &request.action)?; let style = artifact.get_text_style(args.id).cloned().ok_or_else(|| { SpreadsheetArtifactError::InvalidArgs { action: request.action.clone(), message: format!("text style `{}` was not found", args.id), } })?; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Retrieved text style `{}`", args.id), snapshot_for_artifact(artifact), ); response.style_id = Some(args.id); response.serialized_dict = Some(to_serialized_value(style)?); Ok(response) } fn create_fill( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: CreateFillArgs = parse_args(&request.action, &request.args)?; let artifact_id = required_artifact_id(&request)?; let artifact = self.get_artifact_mut(&artifact_id, &request.action)?; let style_id = artifact.create_fill( args.fill, args.source_fill_id, args.merge_with_existing_components.unwrap_or(false), )?; let fill = artifact.get_fill(style_id).cloned().ok_or_else(|| { SpreadsheetArtifactError::Serialization { message: format!("created fill `{style_id}` was not available"), } })?; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Created fill `{style_id}`"), snapshot_for_artifact(artifact), ); response.style_id = Some(style_id); response.serialized_dict = Some(to_serialized_value(fill)?); Ok(response) } fn get_fill( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: GetStyleArgs = parse_args(&request.action, &request.args)?; let artifact_id = required_artifact_id(&request)?; let artifact = self.get_artifact(&artifact_id, &request.action)?; let fill = artifact.get_fill(args.id).cloned().ok_or_else(|| { SpreadsheetArtifactError::InvalidArgs { action: request.action.clone(), message: format!("fill `{}` was not found", args.id), } })?; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Retrieved fill `{}`", args.id), snapshot_for_artifact(artifact), ); response.style_id = Some(args.id); response.serialized_dict = Some(to_serialized_value(fill)?); Ok(response) } fn create_border( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: CreateBorderArgs = parse_args(&request.action, &request.args)?; let artifact_id = required_artifact_id(&request)?; let artifact = self.get_artifact_mut(&artifact_id, &request.action)?; let style_id = artifact.create_border( args.border, args.source_border_id, args.merge_with_existing_components.unwrap_or(false), )?; let border = artifact.get_border(style_id).cloned().ok_or_else(|| { SpreadsheetArtifactError::Serialization { message: format!("created border `{style_id}` was not available"), } })?; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Created border `{style_id}`"), snapshot_for_artifact(artifact), ); response.style_id = Some(style_id); response.serialized_dict = Some(to_serialized_value(border)?); Ok(response) } fn get_border( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: GetStyleArgs = parse_args(&request.action, &request.args)?; let artifact_id = required_artifact_id(&request)?; let artifact = self.get_artifact(&artifact_id, &request.action)?; let border = artifact.get_border(args.id).cloned().ok_or_else(|| { SpreadsheetArtifactError::InvalidArgs { action: request.action.clone(), message: format!("border `{}` was not found", args.id), } })?; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Retrieved border `{}`", args.id), snapshot_for_artifact(artifact), ); response.style_id = Some(args.id); response.serialized_dict = Some(to_serialized_value(border)?); Ok(response) } fn create_number_format( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: CreateNumberFormatArgs = parse_args(&request.action, &request.args)?; let artifact_id = required_artifact_id(&request)?; let artifact = self.get_artifact_mut(&artifact_id, &request.action)?; let style_id = artifact.create_number_format( args.number_format, args.source_number_format_id, args.merge_with_existing_components.unwrap_or(false), )?; let number_format = artifact .get_number_format(style_id) .cloned() .ok_or_else(|| SpreadsheetArtifactError::Serialization { message: format!("created number format `{style_id}` was not available"), })?; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Created number format `{style_id}`"), snapshot_for_artifact(artifact), ); response.style_id = Some(style_id); response.serialized_dict = Some(to_serialized_value(number_format)?); Ok(response) } fn get_number_format( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: GetStyleArgs = parse_args(&request.action, &request.args)?; let artifact_id = required_artifact_id(&request)?; let artifact = self.get_artifact(&artifact_id, &request.action)?; let number_format = artifact .get_number_format(args.id) .cloned() .ok_or_else(|| SpreadsheetArtifactError::InvalidArgs { action: request.action.clone(), message: format!("number format `{}` was not found", args.id), })?; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Retrieved number format `{}`", args.id), snapshot_for_artifact(artifact), ); response.style_id = Some(args.id); response.serialized_dict = Some(to_serialized_value(number_format)?); Ok(response) } fn create_cell_format( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: CreateCellFormatArgs = parse_args(&request.action, &request.args)?; let artifact_id = required_artifact_id(&request)?; let artifact = self.get_artifact_mut(&artifact_id, &request.action)?; let style_id = artifact.create_cell_format( args.format, args.source_format_id, args.merge_with_existing_components.unwrap_or(false), )?; let format = artifact.get_cell_format(style_id).cloned().ok_or_else(|| { SpreadsheetArtifactError::Serialization { message: format!("created cell format `{style_id}` was not available"), } })?; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Created cell format `{style_id}`"), snapshot_for_artifact(artifact), ); response.style_id = Some(style_id); response.serialized_dict = Some(to_serialized_value(format)?); response.cell_format_summary = artifact.cell_format_summary(style_id); Ok(response) } fn get_cell_format( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: GetStyleArgs = parse_args(&request.action, &request.args)?; let artifact_id = required_artifact_id(&request)?; let artifact = self.get_artifact(&artifact_id, &request.action)?; let format = artifact.get_cell_format(args.id).cloned().ok_or_else(|| { SpreadsheetArtifactError::InvalidArgs { action: request.action.clone(), message: format!("cell format `{}` was not found", args.id), } })?; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Retrieved cell format `{}`", args.id), snapshot_for_artifact(artifact), ); response.style_id = Some(args.id); response.serialized_dict = Some(to_serialized_value(format)?); response.cell_format_summary = artifact.cell_format_summary(args.id); Ok(response) } fn create_differential_format( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: CreateDifferentialFormatArgs = parse_args(&request.action, &request.args)?; let artifact_id = required_artifact_id(&request)?; let artifact = self.get_artifact_mut(&artifact_id, &request.action)?; let style_id = artifact.create_differential_format(args.format); let format = artifact .get_differential_format(style_id) .cloned() .ok_or_else(|| SpreadsheetArtifactError::Serialization { message: format!("created differential format `{style_id}` was not available"), })?; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Created differential format `{style_id}`"), snapshot_for_artifact(artifact), ); response.style_id = Some(style_id); response.serialized_dict = Some(to_serialized_value(format)?); Ok(response) } fn get_differential_format( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: GetStyleArgs = parse_args(&request.action, &request.args)?; let artifact_id = required_artifact_id(&request)?; let artifact = self.get_artifact(&artifact_id, &request.action)?; let format = artifact .get_differential_format(args.id) .cloned() .ok_or_else(|| SpreadsheetArtifactError::InvalidArgs { action: request.action.clone(), message: format!("differential format `{}` was not found", args.id), })?; let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Retrieved differential format `{}`", args.id), snapshot_for_artifact(artifact), ); response.style_id = Some(args.id); response.serialized_dict = Some(to_serialized_value(format)?); Ok(response) } fn get_cell_format_summary( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { 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 format summary for `{}`", args.address), snapshot_for_artifact(artifact), ); response.cell_ref = Some(sheet.cell_ref(&args.address)?); response.cell = Some(cell.clone()); response.cell_format_summary = artifact.cell_format_summary(cell.style_index); Ok(response) } fn get_range_format_summary( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { 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 top_left_style_index = sheet.top_left_style_index(&range); let mut response = SpreadsheetArtifactResponse::new( artifact_id, request.action, format!("Retrieved range format summary for `{}`", args.range), snapshot_for_artifact(artifact), ); response.range_ref = Some(SpreadsheetCellRangeRef::new(sheet.name.clone(), &range)); response.range_format = Some(sheet.range_format(&range)); response.range = Some(sheet.get_range_view(&range)); response.top_left_style_index = Some(top_left_style_index); response.cell_format_summary = artifact.cell_format_summary(top_left_style_index); Ok(response) } fn get_reference( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { let args: ReferenceArgs = 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!( "Resolved reference `{}` on `{}`", args.reference, sheet.name ), snapshot_for_artifact(artifact), ); match sheet.reference(&args.reference)? { crate::SpreadsheetSheetReference::Cell { cell_ref } => { let address = cell_ref.cell_address()?; let cell = sheet.get_cell_view(address); response.cell_format_summary = artifact.cell_format_summary(cell.style_index); response.cell = Some(cell); response.raw_cell = sheet.get_raw_cell(address); response.cell_ref = Some(cell_ref); } crate::SpreadsheetSheetReference::Range { range_ref } => { let range = range_ref.range()?; let top_left_style_index = sheet.top_left_style_index(&range); response.range = Some(sheet.get_range_view(&range)); response.range_ref = Some(range_ref); response.range_format = Some(sheet.range_format(&range)); response.top_left_style_index = Some(top_left_style_index); response.cell_format_summary = artifact.cell_format_summary(top_left_style_index); response.rendered_text = Some(sheet.to_rendered_text(Some(&range))); } } Ok(response) } fn get_cell( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { 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_format_summary = artifact.cell_format_summary(cell.style_index); response.cell = Some(cell); response.raw_cell = sheet.get_raw_cell(CellAddress::parse(&args.address)?); response.cell_ref = Some(sheet.cell_ref(&args.address)?); Ok(response) } fn get_cell_by_indices( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { 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), ); let cell = sheet.get_cell_view_by_indices(args.column_index, args.row_index); response.cell_format_summary = artifact.cell_format_summary(cell.style_index); response.cell = Some(cell); response.raw_cell = sheet.get_raw_cell(address); response.cell_ref = Some(sheet.cell_ref(address.to_a1())?); Ok(response) } fn get_cell_field( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { 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 { 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 { 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.range_ref = Some(SpreadsheetCellRangeRef::new(sheet.name.clone(), &range)); response.range_format = Some(sheet.range_format(&range)); response.top_left_style_index = Some(sheet.top_left_style_index(&range)); response.cell_format_summary = artifact.cell_format_summary(sheet.top_left_style_index(&range)); response.rendered_text = Some(sheet.to_rendered_text(Some(&range))); Ok(response) } fn set_cell_value( &mut self, request: SpreadsheetArtifactRequest, ) -> Result { 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 { 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 { 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 { 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 { 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 { 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::>>>(); { 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 { 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 { 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, sheet_index: Option, range: CellRange, style_index: u32, ) -> Result { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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, #[serde(skip_serializing_if = "Option::is_none")] pub artifact_snapshot: Option, #[serde(skip_serializing_if = "Option::is_none")] pub workbook_summary: Option, #[serde(skip_serializing_if = "Option::is_none")] pub sheet_list: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub chart_list: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub table_list: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub conditional_format_list: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub pivot_table_list: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub sheet_ref: Option, #[serde(skip_serializing_if = "Option::is_none")] pub cell_ref: Option, #[serde(skip_serializing_if = "Option::is_none")] pub range_ref: Option, #[serde(skip_serializing_if = "Option::is_none")] pub range_format: Option, #[serde(skip_serializing_if = "Option::is_none")] pub cell: Option, #[serde(skip_serializing_if = "Option::is_none")] pub raw_cell: Option, #[serde(skip_serializing_if = "Option::is_none")] pub style_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub cell_field: Option, #[serde(skip_serializing_if = "Option::is_none")] pub range: Option, #[serde(skip_serializing_if = "Option::is_none")] pub top_left_style_index: Option, #[serde(skip_serializing_if = "Option::is_none")] pub cell_format_summary: Option, #[serde(skip_serializing_if = "Option::is_none")] pub rendered_text: Option, #[serde(skip_serializing_if = "Option::is_none")] pub rendered_html: Option, #[serde(skip_serializing_if = "Option::is_none")] pub row_height: Option, #[serde(skip_serializing_if = "Option::is_none")] pub serialized_dict: Option, #[serde(skip_serializing_if = "Option::is_none")] pub serialized_json: Option, #[serde(skip_serializing_if = "Option::is_none")] pub serialized_bytes_base64: Option, } 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, chart_list: None, table_list: None, conditional_format_list: None, pivot_table_list: None, sheet_ref: None, cell_ref: None, range_ref: None, range_format: None, cell: None, raw_cell: None, style_id: None, cell_field: None, range: None, top_left_style_index: None, cell_format_summary: None, rendered_text: None, rendered_html: 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, pub sheets: Vec, } #[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, pub chart_count: usize, pub table_count: usize, pub conditional_format_count: usize, pub pivot_table_count: usize, } #[derive(Debug, Deserialize)] struct CreateArgs { name: Option, auto_recalculate: Option, } #[derive(Debug, Deserialize)] struct PathArgs { path: PathBuf, } #[derive(Debug, Deserialize)] struct SaveArgs { path: PathBuf, file_type: Option, } #[derive(Debug, Deserialize)] struct RenderArgs { output_path: Option, sheet_name: Option, sheet_index: Option, range: Option, center_address: Option, width: Option, height: Option, include_headers: Option, scale: Option, performance_mode: Option, } #[derive(Debug, Deserialize)] struct SheetLookupArgs { sheet_name: Option, sheet_index: Option, range: Option, } #[derive(Debug, Deserialize)] struct ChartLookupArgs { sheet_name: Option, sheet_index: Option, chart_id: Option, chart_index: Option, } #[derive(Debug, Deserialize)] struct CreateChartArgs { sheet_name: Option, sheet_index: Option, chart_type: crate::SpreadsheetChartType, source_sheet_name: Option, source_range: String, options: Option, } #[derive(Debug, Deserialize)] struct AddChartSeriesArgs { sheet_name: Option, sheet_index: Option, chart_id: Option, chart_index: Option, series: crate::SpreadsheetChartSeries, } #[derive(Debug, Deserialize)] struct SetChartPropertiesArgs { #[serde(flatten)] lookup: ChartLookupArgs, properties: crate::SpreadsheetChartProperties, } #[derive(Debug, Deserialize)] struct TableLookupArgs { sheet_name: Option, sheet_index: Option, name: Option, display_name: Option, id: Option, } #[derive(Debug, Deserialize)] struct CreateTableArgs { sheet_name: Option, sheet_index: Option, range: String, options: crate::SpreadsheetCreateTableOptions, } #[derive(Debug, Deserialize)] struct SetTableStyleArgs { #[serde(flatten)] lookup: TableLookupArgs, options: crate::SpreadsheetTableStyleOptions, } #[derive(Debug, Deserialize)] struct RenameTableColumnArgs { #[serde(flatten)] lookup: TableLookupArgs, column_id: Option, column_name: Option, new_name: String, } #[derive(Debug, Deserialize)] struct SetTableColumnTotalsArgs { #[serde(flatten)] lookup: TableLookupArgs, column_id: Option, column_name: Option, totals_row_label: Option, totals_row_function: Option, } #[derive(Debug, Deserialize)] struct AddConditionalFormatArgs { sheet_name: Option, sheet_index: Option, format: crate::SpreadsheetConditionalFormat, } #[derive(Debug, Deserialize)] struct DeleteConditionalFormatArgs { sheet_name: Option, sheet_index: Option, id: u32, } #[derive(Debug, Deserialize)] struct PivotTableLookupArgs { sheet_name: Option, sheet_index: Option, name: Option, index: Option, } #[derive(Debug, Deserialize)] struct CreateSheetArgs { name: String, } #[derive(Debug, Deserialize)] struct RenameSheetArgs { old_name: Option, sheet_index: Option, new_name: String, } #[derive(Debug, Deserialize)] struct DeleteSheetArgs { sheet_name: Option, sheet_index: Option, } #[derive(Debug, Deserialize)] struct SetSheetPropertiesArgs { sheet_name: Option, sheet_index: Option, default_row_height: Option, default_column_width: Option, show_grid_lines: Option, } #[derive(Debug, Deserialize)] struct SetColumnWidthsArgs { sheet_name: Option, sheet_index: Option, reference: String, width: f64, } #[derive(Debug, Deserialize)] struct SetColumnWidthsBulkArgs { sheet_name: Option, sheet_index: Option, widths: BTreeMap, } #[derive(Debug, Deserialize)] struct SetRowHeightArgs { sheet_name: Option, sheet_index: Option, row_index: u32, height: Option, } #[derive(Debug, Deserialize)] struct SetRowHeightsArgs { sheet_name: Option, sheet_index: Option, start_row_index: u32, end_row_index: u32, height: Option, } #[derive(Debug, Deserialize)] struct SetRowHeightsBulkArgs { sheet_name: Option, sheet_index: Option, heights: BTreeMap>, } #[derive(Debug, Deserialize)] struct GetRowHeightArgs { sheet_name: Option, sheet_index: Option, row_index: u32, } #[derive(Debug, Deserialize)] struct CreateTextStyleArgs { style: crate::SpreadsheetTextStyle, source_style_id: Option, merge_with_existing_components: Option, } #[derive(Debug, Deserialize)] struct CreateFillArgs { fill: crate::SpreadsheetFill, source_fill_id: Option, merge_with_existing_components: Option, } #[derive(Debug, Deserialize)] struct CreateBorderArgs { border: crate::SpreadsheetBorder, source_border_id: Option, merge_with_existing_components: Option, } #[derive(Debug, Deserialize)] struct CreateNumberFormatArgs { number_format: crate::SpreadsheetNumberFormat, source_number_format_id: Option, merge_with_existing_components: Option, } #[derive(Debug, Deserialize)] struct CreateCellFormatArgs { format: crate::SpreadsheetCellFormat, source_format_id: Option, merge_with_existing_components: Option, } #[derive(Debug, Deserialize)] struct CreateDifferentialFormatArgs { format: crate::SpreadsheetDifferentialFormat, } #[derive(Debug, Deserialize)] struct GetStyleArgs { id: u32, } #[derive(Debug, Deserialize)] struct CellAddressArgs { sheet_name: Option, sheet_index: Option, address: String, } #[derive(Debug, Deserialize)] struct ReferenceArgs { sheet_name: Option, sheet_index: Option, reference: String, } #[derive(Debug, Deserialize)] struct CellIndicesArgs { sheet_name: Option, sheet_index: Option, column_index: u32, row_index: u32, } #[derive(Debug, Deserialize)] struct CellFieldArgs { sheet_name: Option, sheet_index: Option, address: String, field: String, } #[derive(Debug, Deserialize)] struct CellFieldByIndicesArgs { sheet_name: Option, sheet_index: Option, column_index: u32, row_index: u32, field: String, } #[derive(Debug, Deserialize)] struct RangeArgs { sheet_name: Option, sheet_index: Option, range: String, } #[derive(Debug, Deserialize)] struct SetCellValueArgs { sheet_name: Option, sheet_index: Option, address: String, value: Value, recalculate: Option, } #[derive(Debug, Deserialize)] struct SetRangeValuesArgs { sheet_name: Option, sheet_index: Option, range: String, values: Vec>, recalculate: Option, } #[derive(Debug, Deserialize)] struct SetRangeValueArgs { sheet_name: Option, sheet_index: Option, range: String, value: Value, recalculate: Option, } #[derive(Debug, Deserialize)] struct SetCellFormulaArgs { sheet_name: Option, sheet_index: Option, address: String, formula: String, recalculate: Option, } #[derive(Debug, Deserialize)] struct SetRangeFormulasArgs { sheet_name: Option, sheet_index: Option, range: String, formulas: Vec>>, recalculate: Option, } #[derive(Debug, Deserialize)] struct SetRangeFormulaArgs { sheet_name: Option, sheet_index: Option, range: String, formula: String, recalculate: Option, } #[derive(Debug, Deserialize)] struct SetCellStyleArgs { sheet_name: Option, sheet_index: Option, address: String, style_index: u32, } #[derive(Debug, Deserialize)] struct SetRangeStyleArgs { sheet_name: Option, sheet_index: Option, range: String, style_index: u32, } #[derive(Debug, Deserialize)] struct ClearRangeArgs { sheet_name: Option, sheet_index: Option, range: String, fields: Option>, recalculate: Option, } #[derive(Debug, Deserialize)] struct MergeRangeArgs { sheet_name: Option, sheet_index: Option, range: String, raise_on_conflict: Option, } #[derive(Debug, Deserialize)] struct CiteRangeArgs { sheet_name: Option, sheet_index: Option, range: String, tether_id: String, start_line: Option, end_line: Option, content_reference_type: Option, source_type: Option, } #[derive(Debug, Deserialize)] struct CiteCellArgs { sheet_name: Option, sheet_index: Option, address: String, tether_id: String, start_line: Option, end_line: Option, content_reference_type: Option, source_type: Option, } #[derive(Debug, Deserialize)] struct DeserializeDictArgs { data: Value, artifact_id: Option, } #[derive(Debug, Deserialize)] struct DeserializeJsonArgs { json: String, artifact_id: Option, } #[derive(Debug, Deserialize)] struct DeserializeBytesArgs { bytes_base64: String, artifact_id: Option, } 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(), chart_count: sheet.charts.len(), table_count: sheet.tables.len(), conditional_format_count: sheet.conditional_formats.len(), pivot_table_count: sheet.pivot_tables.len(), }) .collect(), } } fn chart_lookup_from_args(args: &ChartLookupArgs) -> crate::SpreadsheetChartLookup { crate::SpreadsheetChartLookup { id: args.chart_id, index: args.chart_index.map(|value| value as usize), } } fn table_lookup_from_args(args: &TableLookupArgs) -> crate::SpreadsheetTableLookup<'_> { crate::SpreadsheetTableLookup { name: args.name.as_deref(), display_name: args.display_name.as_deref(), id: args.id, } } fn pivot_lookup_from_args(args: &PivotTableLookupArgs) -> crate::SpreadsheetPivotTableLookup<'_> { crate::SpreadsheetPivotTableLookup { name: args.name.as_deref(), index: args.index.map(|value| value as usize), } } fn sheet_reference(sheet: &crate::SpreadsheetSheet) -> SpreadsheetSheetRef { SpreadsheetSheetRef { sheet_name: sheet.name.clone(), } } fn normalize_optional_cell_value( value: Value, ) -> Result, SpreadsheetArtifactError> { if value.is_null() { Ok(None) } else { Ok(Some(SpreadsheetCellValue::try_from(value)?)) } } fn normalize_value_matrix( values: Vec>, action: &str, ) -> Result>>, 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::, _>>() }) .collect::, _>>() } fn normalize_formula(formula: String) -> String { let trimmed = formula.trim(); if trimmed.starts_with('=') { trimmed.to_string() } else { format!("={trimmed}") } } fn render_options_from_args( args: RenderArgs, ) -> Result { let scale = args.scale.unwrap_or(1.0); if scale <= 0.0 { return Err(SpreadsheetArtifactError::InvalidArgs { action: "render".to_string(), message: "render scale must be positive".to_string(), }); } Ok(crate::SpreadsheetRenderOptions { output_path: args.output_path, center_address: args.center_address, width: args.width, height: args.height, include_headers: args.include_headers.unwrap_or(true), scale, performance_mode: args.performance_mode.unwrap_or(false), }) } fn required_artifact_id( request: &SpreadsheetArtifactRequest, ) -> Result { request .artifact_id .clone() .ok_or_else(|| SpreadsheetArtifactError::MissingArtifactId { action: request.action.clone(), }) } fn parse_args Deserialize<'de>>( action: &str, value: &Value, ) -> Result { 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) } } fn to_serialized_value(value: T) -> Result { serde_json::to_value(value).map_err(|error| SpreadsheetArtifactError::Serialization { message: error.to_string(), }) }