use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct Spreadsheet {
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "spreadsheetId"
)]
pub spreadsheet_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub properties: Option<SpreadsheetProperties>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub sheets: Vec<Sheet>,
#[serde(default, skip_serializing_if = "Vec::is_empty", rename = "namedRanges")]
pub named_ranges: Vec<NamedRange>,
}
impl Spreadsheet {
#[must_use]
pub fn title(&self) -> &str {
self.properties
.as_ref()
.map_or("", |props| props.title.as_str())
}
#[must_use]
pub fn sheet_titles(&self) -> Vec<String> {
self.sheets
.iter()
.map(|sheet| sheet.title().to_string())
.collect()
}
#[must_use]
pub fn sheet_ids(&self) -> std::collections::HashSet<i64> {
self.sheets.iter().filter_map(Sheet::sheet_id).collect()
}
#[must_use]
pub fn has_sheet_titled(&self, title: &str) -> bool {
self.sheets
.iter()
.filter_map(|sheet| sheet.properties.as_ref())
.any(|props| props.title == title)
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct SpreadsheetProperties {
#[serde(default)]
pub title: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub locale: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none", rename = "timeZone")]
pub time_zone: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "autoRecalc"
)]
pub auto_recalc: Option<RecalculationInterval>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "iterativeCalculationSettings"
)]
pub iterative_calculation_settings: Option<IterativeCalculationSettings>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum RecalculationInterval {
#[serde(rename = "RECALCULATION_INTERVAL_UNSPECIFIED")]
Unspecified,
#[serde(rename = "ON_CHANGE")]
OnChange,
#[serde(rename = "MINUTE")]
Minute,
#[serde(rename = "HOUR")]
Hour,
}
impl RecalculationInterval {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Unspecified => "RECALCULATION_INTERVAL_UNSPECIFIED",
Self::OnChange => "ON_CHANGE",
Self::Minute => "MINUTE",
Self::Hour => "HOUR",
}
}
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct IterativeCalculationSettings {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_iterations: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub convergence_threshold: Option<f64>,
}
impl IterativeCalculationSettings {
#[must_use]
pub fn describe_bounds(&self) -> String {
match (self.max_iterations, self.convergence_threshold) {
(Some(max), Some(threshold)) => {
format!(" (max {max} iterations, threshold {threshold})")
}
(Some(max), None) => format!(" (max {max} iterations)"),
(None, Some(threshold)) => format!(" (threshold {threshold})"),
(None, None) => String::new(),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct Sheet {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub properties: Option<SheetProperties>,
#[serde(
default,
skip_serializing_if = "Vec::is_empty",
rename = "protectedRanges"
)]
pub protected_ranges: Vec<ProtectedRange>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "basicFilter"
)]
pub basic_filter: Option<BasicFilter>,
#[serde(default, skip_serializing_if = "Vec::is_empty", rename = "filterViews")]
pub filter_views: Vec<FilterView>,
#[serde(
default,
skip_serializing_if = "Vec::is_empty",
rename = "conditionalFormats"
)]
pub conditional_formats: Vec<ConditionalFormatRule>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub charts: Vec<EmbeddedChart>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub slicers: Vec<Slicer>,
#[serde(
default,
skip_serializing_if = "Vec::is_empty",
rename = "bandedRanges"
)]
pub banded_ranges: Vec<BandedRange>,
#[serde(default, skip_serializing_if = "Vec::is_empty", rename = "rowGroups")]
pub row_groups: Vec<DimensionGroup>,
#[serde(
default,
skip_serializing_if = "Vec::is_empty",
rename = "columnGroups"
)]
pub column_groups: Vec<DimensionGroup>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub data: Vec<GridData>,
}
impl Sheet {
#[must_use]
pub fn title(&self) -> &str {
self.properties
.as_ref()
.map_or("", |props| props.title.as_str())
}
#[must_use]
pub fn sheet_id(&self) -> Option<i64> {
self.properties.as_ref()?.sheet_id
}
#[must_use]
pub fn hidden(&self) -> bool {
self.properties
.as_ref()
.is_some_and(|props| props.hidden.unwrap_or(false))
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct SheetProperties {
#[serde(default, skip_serializing_if = "Option::is_none", rename = "sheetId")]
pub sheet_id: Option<i64>,
#[serde(default)]
pub title: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub index: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hidden: Option<bool>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "gridProperties"
)]
pub grid_properties: Option<GridProperties>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "rightToLeft"
)]
pub right_to_left: Option<bool>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct GridProperties {
#[serde(default, skip_serializing_if = "Option::is_none", rename = "rowCount")]
pub row_count: Option<i64>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "columnCount"
)]
pub column_count: Option<i64>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "frozenRowCount"
)]
pub frozen_row_count: Option<i64>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "frozenColumnCount"
)]
pub frozen_column_count: Option<i64>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "hideGridlines"
)]
pub hide_gridlines: Option<bool>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct ValueRange {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub range: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub values: Vec<Vec<serde_json::Value>>,
}
impl ValueRange {
#[must_use]
pub fn width(&self) -> usize {
self.values.iter().map(Vec::len).max().unwrap_or(0)
}
}
impl crate::cli::drive::format::JsonlSerialize for Spreadsheet {
fn write_jsonl(&self, out: &mut dyn std::io::Write) -> anyhow::Result<()> {
crate::cli::drive::format::write_scalar_jsonl(self, out)
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct BatchGetValuesResponse {
#[serde(default, skip_serializing_if = "Vec::is_empty", rename = "valueRanges")]
pub value_ranges: Vec<ValueRange>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct UpdateValuesResponse {
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "updatedRange"
)]
pub updated_range: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "updatedRows"
)]
pub updated_rows: Option<i64>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "updatedColumns"
)]
pub updated_columns: Option<i64>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "updatedCells"
)]
pub updated_cells: Option<i64>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct AppendValuesResponse {
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "tableRange"
)]
pub table_range: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub updates: Option<UpdateValuesResponse>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct ClearValuesResponse {
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "clearedRange"
)]
pub cleared_range: Option<String>,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum BatchUpdateRequestItem {
FindReplace(FindReplaceRequest),
AddSheet(AddSheetRequest),
UpdateSheetProperties(UpdateSheetPropertiesRequest),
UpdateSpreadsheetProperties(UpdateSpreadsheetPropertiesRequest),
InsertDimension(InsertDimensionRequest),
InsertRange(InsertRangeRequest),
DeleteSheet(DeleteSheetRequest),
DeleteDimension(DeleteDimensionRequest),
MoveDimension(MoveDimensionRequest),
DeleteRange(DeleteRangeRequest),
DuplicateSheet(DuplicateSheetRequest),
RepeatCell(RepeatCellRequest),
UpdateBorders(UpdateBordersRequest),
MergeCells(MergeCellsRequest),
UnmergeCells(UnmergeCellsRequest),
AutoResizeDimensions(AutoResizeDimensionsRequest),
UpdateDimensionProperties(UpdateDimensionPropertiesRequest),
SetDataValidation(SetDataValidationRequest),
AddProtectedRange(AddProtectedRangeRequest),
UpdateProtectedRange(UpdateProtectedRangeRequest),
DeleteProtectedRange(DeleteProtectedRangeRequest),
CreateDeveloperMetadata(CreateDeveloperMetadataRequest),
UpdateDeveloperMetadata(UpdateDeveloperMetadataRequest),
DeleteDeveloperMetadata(DeleteDeveloperMetadataRequest),
SetBasicFilter(SetBasicFilterRequest),
ClearBasicFilter(ClearBasicFilterRequest),
AddFilterView(AddFilterViewRequest),
UpdateFilterView(UpdateFilterViewRequest),
DeleteFilterView(DeleteFilterViewRequest),
AddConditionalFormatRule(AddConditionalFormatRuleRequest),
UpdateConditionalFormatRule(UpdateConditionalFormatRuleRequest),
DeleteConditionalFormatRule(DeleteConditionalFormatRuleRequest),
AddNamedRange(AddNamedRangeRequest),
UpdateNamedRange(UpdateNamedRangeRequest),
DeleteNamedRange(DeleteNamedRangeRequest),
AddChart(AddChartRequest),
UpdateChartSpec(UpdateChartSpecRequest),
AddSlicer(AddSlicerRequest),
UpdateSlicerSpec(UpdateSlicerSpecRequest),
DeleteEmbeddedObject(DeleteEmbeddedObjectRequest),
UpdateEmbeddedObjectPosition(UpdateEmbeddedObjectPositionRequest),
UpdateEmbeddedObjectBorder(UpdateEmbeddedObjectBorderRequest),
UpdateCells(UpdateCellsRequest),
AddBanding(AddBandingRequest),
UpdateBanding(UpdateBandingRequest),
DeleteBanding(DeleteBandingRequest),
AddDimensionGroup(AddDimensionGroupRequest),
UpdateDimensionGroup(UpdateDimensionGroupRequest),
DeleteDimensionGroup(DeleteDimensionGroupRequest),
CutPaste(CutPasteRequest),
CopyPaste(CopyPasteRequest),
PasteData(PasteDataRequest),
AutoFill(AutoFillRequest),
SortRange(SortRangeRequest),
RandomizeRange(RandomizeRangeRequest),
TextToColumns(TextToColumnsRequest),
TrimWhitespace(TrimWhitespaceRequest),
DeleteDuplicates(DeleteDuplicatesRequest),
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct BatchUpdateRequest {
pub requests: Vec<BatchUpdateRequestItem>,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct AddSheetRequest {
pub properties: NewSheetProperties,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct NewSheetProperties {
pub title: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub index: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none", rename = "gridProperties")]
pub grid_properties: Option<GridProperties>,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct UpdateSheetPropertiesRequest {
pub properties: SheetPropertiesUpdate,
pub fields: String,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct UpdateSpreadsheetPropertiesRequest {
pub properties: SpreadsheetPropertiesUpdate,
pub fields: String,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq)]
pub struct SpreadsheetPropertiesUpdate {
#[serde(skip_serializing_if = "Option::is_none")]
pub locale: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename = "timeZone")]
pub time_zone: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename = "autoRecalc")]
pub auto_recalc: Option<RecalculationInterval>,
#[serde(
skip_serializing_if = "Option::is_none",
rename = "iterativeCalculationSettings"
)]
pub iterative_calculation_settings: Option<IterativeCalculationSettings>,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct CopySheetToAnotherSpreadsheetRequest {
#[serde(rename = "destinationSpreadsheetId")]
pub destination_spreadsheet_id: String,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq)]
pub struct SheetPropertiesUpdate {
#[serde(rename = "sheetId")]
pub sheet_id: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub index: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hidden: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none", rename = "gridProperties")]
pub grid_properties: Option<GridPropertiesUpdate>,
#[serde(skip_serializing_if = "Option::is_none", rename = "tabColorStyle")]
pub tab_color_style: Option<ColorStyle>,
#[serde(skip_serializing_if = "Option::is_none", rename = "rightToLeft")]
pub right_to_left: Option<bool>,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct GridPropertiesUpdate {
#[serde(skip_serializing_if = "Option::is_none", rename = "frozenRowCount")]
pub frozen_row_count: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none", rename = "frozenColumnCount")]
pub frozen_column_count: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none", rename = "hideGridlines")]
pub hide_gridlines: Option<bool>,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct DuplicateSheetRequest {
#[serde(rename = "sourceSheetId")]
pub source_sheet_id: i64,
#[serde(skip_serializing_if = "Option::is_none", rename = "insertSheetIndex")]
pub insert_sheet_index: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none", rename = "newSheetName")]
pub new_sheet_name: Option<String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum Dimension {
#[serde(rename = "ROWS")]
Rows,
#[serde(rename = "COLUMNS")]
Columns,
}
impl Dimension {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Rows => "ROWS",
Self::Columns => "COLUMNS",
}
}
#[must_use]
pub fn noun(self) -> &'static str {
match self {
Self::Rows => "row",
Self::Columns => "column",
}
}
#[must_use]
pub fn span_label(self, start: i64, end: i64) -> String {
format!("{} {start}:{end}", self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DimensionRange {
#[serde(default, rename = "sheetId")]
pub sheet_id: i64,
pub dimension: Dimension,
#[serde(default, rename = "startIndex")]
pub start_index: i64,
#[serde(default, rename = "endIndex")]
pub end_index: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DimensionGroup {
pub range: DimensionRange,
#[serde(default)]
pub depth: i64,
#[serde(default)]
pub collapsed: bool,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct GridRange {
#[serde(default, rename = "sheetId")]
pub sheet_id: i64,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "startRowIndex"
)]
pub start_row_index: Option<i64>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "endRowIndex"
)]
pub end_row_index: Option<i64>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "startColumnIndex"
)]
pub start_column_index: Option<i64>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "endColumnIndex"
)]
pub end_column_index: Option<i64>,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq)]
pub struct Color {
#[serde(default)]
pub red: f32,
#[serde(default)]
pub green: f32,
#[serde(default)]
pub blue: f32,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct ColorStyle {
#[serde(default, skip_serializing_if = "Option::is_none", rename = "rgbColor")]
pub rgb_color: Option<Color>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "themeColor"
)]
pub theme_color: Option<String>,
}
impl ColorStyle {
pub fn rgb(color: Color) -> Self {
Self {
rgb_color: Some(color),
theme_color: None,
}
}
#[cfg(test)]
pub fn theme(name: impl Into<String>) -> Self {
Self {
rgb_color: None,
theme_color: Some(name.into()),
}
}
#[must_use]
pub fn describe(&self) -> String {
match (&self.rgb_color, &self.theme_color) {
(Some(rgb), _) => super::format::format_hex_color(*rgb),
(None, Some(name)) => format!("theme:{name}"),
(None, None) => "(unset)".to_string(),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct TextFormat {
#[serde(skip_serializing_if = "Option::is_none")]
pub bold: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub italic: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub strikethrough: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub underline: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none", rename = "fontSize")]
pub font_size: Option<i64>,
#[serde(
skip_serializing_if = "Option::is_none",
rename = "foregroundColorStyle"
)]
pub foreground_color_style: Option<ColorStyle>,
#[serde(skip_serializing_if = "Option::is_none", rename = "fontFamily")]
pub font_family: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct NumberFormat {
#[serde(default, rename = "type")]
pub format_type: String,
#[serde(default)]
pub pattern: String,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct TextRotation {
#[serde(skip_serializing_if = "Option::is_none")]
pub angle: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vertical: Option<bool>,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct Padding {
#[serde(skip_serializing_if = "Option::is_none")]
pub top: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub right: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bottom: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub left: Option<i32>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct CellFormat {
#[serde(skip_serializing_if = "Option::is_none", rename = "textFormat")]
pub text_format: Option<TextFormat>,
#[serde(
skip_serializing_if = "Option::is_none",
rename = "backgroundColorStyle"
)]
pub background_color_style: Option<ColorStyle>,
#[serde(
skip_serializing_if = "Option::is_none",
rename = "horizontalAlignment"
)]
pub horizontal_alignment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename = "verticalAlignment")]
pub vertical_alignment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename = "numberFormat")]
pub number_format: Option<NumberFormat>,
#[serde(skip_serializing_if = "Option::is_none", rename = "wrapStrategy")]
pub wrap_strategy: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename = "textRotation")]
pub text_rotation: Option<TextRotation>,
#[serde(
skip_serializing_if = "Option::is_none",
rename = "hyperlinkDisplayType"
)]
pub hyperlink_display_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub padding: Option<Padding>,
#[serde(skip_serializing_if = "Option::is_none", rename = "textDirection")]
pub text_direction: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq)]
pub struct RepeatCellData {
#[serde(rename = "userEnteredFormat")]
pub user_entered_format: CellFormat,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq)]
pub struct RepeatCellRequest {
pub range: GridRange,
pub cell: RepeatCellData,
pub fields: String,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq)]
pub struct Border {
pub style: String,
#[serde(rename = "colorStyle")]
pub color_style: ColorStyle,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq)]
pub struct UpdateBordersRequest {
pub range: GridRange,
#[serde(skip_serializing_if = "Option::is_none")]
pub top: Option<Border>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bottom: Option<Border>,
#[serde(skip_serializing_if = "Option::is_none")]
pub left: Option<Border>,
#[serde(skip_serializing_if = "Option::is_none")]
pub right: Option<Border>,
#[serde(skip_serializing_if = "Option::is_none", rename = "innerHorizontal")]
pub inner_horizontal: Option<Border>,
#[serde(skip_serializing_if = "Option::is_none", rename = "innerVertical")]
pub inner_vertical: Option<Border>,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct MergeCellsRequest {
pub range: GridRange,
#[serde(rename = "mergeType")]
pub merge_type: String,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct UnmergeCellsRequest {
pub range: GridRange,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct AutoResizeDimensionsRequest {
pub dimensions: DimensionRange,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct DimensionProperties {
#[serde(rename = "pixelSize")]
pub pixel_size: i64,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct UpdateDimensionPropertiesRequest {
pub range: DimensionRange,
pub properties: DimensionProperties,
pub fields: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct ConditionValue {
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "userEnteredValue"
)]
pub user_entered_value: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "relativeDate"
)]
pub relative_date: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct BooleanCondition {
#[serde(default, rename = "type")]
pub condition_type: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub values: Vec<ConditionValue>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct DataValidationRule {
#[serde(default)]
pub condition: BooleanCondition,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "inputMessage"
)]
pub input_message: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub strict: Option<bool>,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct SetDataValidationRequest {
pub range: GridRange,
#[serde(skip_serializing_if = "Option::is_none")]
pub rule: Option<DataValidationRule>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct ProtectedRangeEditors {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub users: Vec<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct ProtectedRange {
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "protectedRangeId"
)]
pub protected_range_id: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub range: Option<GridRange>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "warningOnly"
)]
pub warning_only: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub editors: Option<ProtectedRangeEditors>,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct AddProtectedRangeRequest {
#[serde(rename = "protectedRange")]
pub protected_range: ProtectedRange,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct ProtectedRangeUpdate {
#[serde(rename = "protectedRangeId")]
pub protected_range_id: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename = "warningOnly")]
pub warning_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub editors: Option<ProtectedRangeEditors>,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct UpdateProtectedRangeRequest {
#[serde(rename = "protectedRange")]
pub protected_range: ProtectedRangeUpdate,
pub fields: String,
}
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
pub struct DeleteProtectedRangeRequest {
#[serde(rename = "protectedRangeId")]
pub protected_range_id: i64,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct FilterCriteria {
#[serde(
default,
skip_serializing_if = "Vec::is_empty",
rename = "hiddenValues"
)]
pub hidden_values: Vec<String>,
#[serde(flatten)]
pub extra: BTreeMap<String, serde_json::Value>,
}
impl FilterCriteria {
#[must_use]
pub fn hiding(hidden_values: Vec<String>) -> Self {
Self {
hidden_values,
extra: BTreeMap::new(),
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.hidden_values.is_empty() && self.extra.is_empty()
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum SortOrder {
Ascending,
Descending,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub struct SortSpec {
#[serde(default, rename = "dimensionIndex")]
pub dimension_index: i64,
#[serde(rename = "sortOrder")]
pub sort_order: SortOrder,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct BasicFilter {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub range: Option<GridRange>,
#[serde(default, skip_serializing_if = "Vec::is_empty", rename = "sortSpecs")]
pub sort_specs: Vec<SortSpec>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub criteria: BTreeMap<String, FilterCriteria>,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct SetBasicFilterRequest {
pub filter: BasicFilter,
}
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
pub struct ClearBasicFilterRequest {
#[serde(rename = "sheetId")]
pub sheet_id: i64,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct FilterView {
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "filterViewId"
)]
pub filter_view_id: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub range: Option<GridRange>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "namedRangeId"
)]
pub named_range_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none", rename = "tableId")]
pub table_id: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty", rename = "sortSpecs")]
pub sort_specs: Vec<SortSpec>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub criteria: BTreeMap<String, FilterCriteria>,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct AddFilterViewRequest {
pub filter: FilterView,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct FilterViewUpdate {
#[serde(rename = "filterViewId")]
pub filter_view_id: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub range: Option<GridRange>,
#[serde(skip_serializing_if = "Option::is_none", rename = "sortSpecs")]
pub sort_specs: Option<Vec<SortSpec>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub criteria: Option<BTreeMap<String, FilterCriteria>>,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct UpdateFilterViewRequest {
pub filter: FilterViewUpdate,
pub fields: String,
}
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
pub struct DeleteFilterViewRequest {
#[serde(rename = "filterId")]
pub filter_id: i64,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct ConditionalFormatRule {
pub ranges: Vec<GridRange>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "booleanRule"
)]
pub boolean_rule: Option<BooleanRule>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "gradientRule"
)]
pub gradient_rule: Option<GradientRule>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct BooleanRule {
pub condition: BooleanCondition,
pub format: CellFormat,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct GradientRule {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub minpoint: Option<InterpolationPoint>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub midpoint: Option<InterpolationPoint>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub maxpoint: Option<InterpolationPoint>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct InterpolationPoint {
#[serde(rename = "colorStyle")]
pub color_style: ColorStyle,
#[serde(default, rename = "type")]
pub point_type: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub value: Option<String>,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct AddConditionalFormatRuleRequest {
pub rule: ConditionalFormatRule,
pub index: i64,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct UpdateConditionalFormatRuleRequest {
pub rule: ConditionalFormatRule,
pub index: i64,
}
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
pub struct DeleteConditionalFormatRuleRequest {
#[serde(rename = "sheetId")]
pub sheet_id: i64,
pub index: i64,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct NamedRange {
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "namedRangeId"
)]
pub named_range_id: Option<String>,
#[serde(default)]
pub name: String,
#[serde(default)]
pub range: GridRange,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct AddNamedRangeRequest {
#[serde(rename = "namedRange")]
pub named_range: NamedRange,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct UpdateNamedRangeRequest {
#[serde(rename = "namedRange")]
pub named_range: NamedRange,
pub fields: String,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct DeleteNamedRangeRequest {
#[serde(rename = "namedRangeId")]
pub named_range_id: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct EmbeddedObjectBorder {
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "colorStyle"
)]
pub color_style: Option<ColorStyle>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct EmbeddedChart {
#[serde(default, skip_serializing_if = "Option::is_none", rename = "chartId")]
pub chart_id: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub spec: Option<ChartSpec>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub position: Option<EmbeddedObjectPosition>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub border: Option<EmbeddedObjectBorder>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct ChartSpec {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subtitle: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "basicChart"
)]
pub basic_chart: Option<BasicChartSpec>,
#[serde(default, skip_serializing_if = "Option::is_none", rename = "pieChart")]
pub pie_chart: Option<PieChartSpec>,
#[serde(flatten)]
pub extra: BTreeMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct BasicChartSpec {
#[serde(default, rename = "chartType")]
pub chart_type: String,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "legendPosition"
)]
pub legend_position: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "stackedType"
)]
pub stacked_type: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "headerCount"
)]
pub header_count: Option<i64>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub axis: Vec<BasicChartAxis>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub domains: Vec<BasicChartDomain>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub series: Vec<BasicChartSeries>,
#[serde(flatten)]
pub extra: BTreeMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct BasicChartAxis {
#[serde(default)]
pub position: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(flatten)]
pub extra: BTreeMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct BasicChartDomain {
pub domain: ChartData,
#[serde(flatten)]
pub extra: BTreeMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct BasicChartSeries {
pub series: ChartData,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "targetAxis"
)]
pub target_axis: Option<String>,
#[serde(flatten)]
pub extra: BTreeMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct PieChartSpec {
pub domain: ChartData,
pub series: ChartData,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "legendPosition"
)]
pub legend_position: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none", rename = "pieHole")]
pub pie_hole: Option<f64>,
#[serde(flatten)]
pub extra: BTreeMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct ChartData {
#[serde(rename = "sourceRange")]
pub source_range: ChartSourceRange,
#[serde(flatten)]
pub extra: BTreeMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct ChartSourceRange {
pub sources: Vec<GridRange>,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct EmbeddedObjectPosition {
#[serde(default, skip_serializing_if = "Option::is_none", rename = "sheetId")]
pub sheet_id: Option<i64>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "overlayPosition"
)]
pub overlay_position: Option<OverlayPosition>,
#[serde(default, skip_serializing_if = "Option::is_none", rename = "newSheet")]
pub new_sheet: Option<bool>,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct OverlayPosition {
#[serde(rename = "anchorCell")]
pub anchor_cell: GridCoordinate,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "offsetXPixels"
)]
pub offset_x_pixels: Option<i64>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "offsetYPixels"
)]
pub offset_y_pixels: Option<i64>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "widthPixels"
)]
pub width_pixels: Option<i64>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "heightPixels"
)]
pub height_pixels: Option<i64>,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct GridCoordinate {
#[serde(default, rename = "sheetId")]
pub sheet_id: i64,
#[serde(default, rename = "rowIndex")]
pub row_index: i64,
#[serde(default, rename = "columnIndex")]
pub column_index: i64,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct Slicer {
#[serde(default, skip_serializing_if = "Option::is_none", rename = "slicerId")]
pub slicer_id: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub spec: Option<SlicerSpec>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub position: Option<EmbeddedObjectPosition>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct SlicerSpec {
#[serde(default, skip_serializing_if = "Option::is_none", rename = "dataRange")]
pub data_range: Option<GridRange>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "filterCriteria"
)]
pub filter_criteria: Option<FilterCriteria>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "columnIndex"
)]
pub column_index: Option<i64>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "applyToPivotTables"
)]
pub apply_to_pivot_tables: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(flatten)]
pub extra: BTreeMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq)]
pub struct AddChartRequest {
pub chart: EmbeddedChart,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct UpdateChartSpecRequest {
#[serde(rename = "chartId")]
pub chart_id: i64,
pub spec: ChartSpec,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct AddSlicerRequest {
pub slicer: Slicer,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct UpdateSlicerSpecRequest {
#[serde(rename = "slicerId")]
pub slicer_id: i64,
pub spec: SlicerSpec,
pub fields: String,
}
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
pub struct DeleteEmbeddedObjectRequest {
#[serde(rename = "objectId")]
pub object_id: i64,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct UpdateEmbeddedObjectPositionRequest {
#[serde(rename = "objectId")]
pub object_id: i64,
#[serde(rename = "newPosition")]
pub new_position: EmbeddedObjectPosition,
#[serde(skip_serializing_if = "String::is_empty")]
pub fields: String,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct UpdateEmbeddedObjectBorderRequest {
#[serde(rename = "objectId")]
pub object_id: i64,
pub border: EmbeddedObjectBorder,
pub fields: String,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct UpdateCellsRequest {
pub start: GridCoordinate,
pub rows: Vec<PivotRowData>,
pub fields: String,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct PivotRowData {
pub values: Vec<PivotCellData>,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct PivotCellData {
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "pivotTable"
)]
pub pivot_table: Option<PivotTable>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PivotTable {
pub source: GridRange,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub rows: Vec<PivotGroup>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub columns: Vec<PivotGroup>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub values: Vec<PivotValue>,
#[serde(default, skip_serializing_if = "Vec::is_empty", rename = "filterSpecs")]
pub filter_specs: Vec<PivotFilterSpec>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "valueLayout"
)]
pub value_layout: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PivotGroup {
#[serde(default, rename = "sourceColumnOffset")]
pub source_column_offset: i64,
#[serde(default, rename = "showTotals")]
pub show_totals: bool,
#[serde(default, skip_serializing_if = "Option::is_none", rename = "sortOrder")]
pub sort_order: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PivotValue {
#[serde(default, rename = "sourceColumnOffset")]
pub source_column_offset: i64,
#[serde(default, rename = "summarizeFunction")]
pub summarize_function: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PivotFilterSpec {
#[serde(default, rename = "columnOffsetIndex")]
pub column_offset_index: i64,
#[serde(rename = "filterCriteria")]
pub filter_criteria: PivotFilterCriteria,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PivotFilterCriteria {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub visible_values: Vec<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct GridData {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub start_row: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub start_column: Option<i64>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub row_data: Vec<RowData>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct RowData {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub values: Vec<CellSnapshot>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct ColorStyleSnapshot {
#[serde(default, rename = "rgbColor", skip_serializing_if = "Option::is_none")]
pub rgb_color: Option<Color>,
#[serde(
default,
rename = "themeColor",
skip_serializing_if = "Option::is_none"
)]
pub theme_color: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct TextFormatSnapshot {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bold: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub italic: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub strikethrough: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub underline: Option<bool>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "foregroundColorStyle"
)]
pub foreground_color_style: Option<ColorStyleSnapshot>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct CellFormatSnapshot {
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "backgroundColorStyle"
)]
pub background_color_style: Option<ColorStyleSnapshot>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "textFormat"
)]
pub text_format: Option<TextFormatSnapshot>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "numberFormat"
)]
pub number_format: Option<NumberFormat>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "horizontalAlignment"
)]
pub horizontal_alignment: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub borders: Option<BordersSnapshot>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct BorderSnapshot {
#[serde(default)]
pub style: String,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "colorStyle"
)]
pub color_style: Option<ColorStyleSnapshot>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct BordersSnapshot {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub top: Option<BorderSnapshot>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bottom: Option<BorderSnapshot>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub left: Option<BorderSnapshot>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub right: Option<BorderSnapshot>,
}
impl BordersSnapshot {
#[must_use]
pub fn is_empty(&self) -> bool {
self.top.is_none() && self.bottom.is_none() && self.left.is_none() && self.right.is_none()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CellSnapshot {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pivot_table: Option<PivotTable>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub formatted_value: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "userEnteredFormat"
)]
pub user_entered_format: Option<CellFormatSnapshot>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub note: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "dataValidation"
)]
pub data_validation: Option<DataValidationRule>,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct InsertDimensionRequest {
pub range: DimensionRange,
#[serde(rename = "inheritFromBefore")]
pub inherit_from_before: bool,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct DeleteSheetRequest {
#[serde(rename = "sheetId")]
pub sheet_id: i64,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct DeleteDimensionRequest {
pub range: DimensionRange,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct MoveDimensionRequest {
pub source: DimensionRange,
#[serde(rename = "destinationIndex")]
pub destination_index: i64,
}
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
pub enum ShiftDimension {
#[serde(rename = "ROWS")]
Rows,
#[serde(rename = "COLUMNS")]
Columns,
}
impl ShiftDimension {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Rows => "ROWS",
Self::Columns => "COLUMNS",
}
}
}
impl From<ShiftDimension> for Dimension {
fn from(shift: ShiftDimension) -> Self {
match shift {
ShiftDimension::Rows => Self::Rows,
ShiftDimension::Columns => Self::Columns,
}
}
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct InsertRangeRequest {
pub range: GridRange,
#[serde(rename = "shiftDimension")]
pub shift_dimension: ShiftDimension,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct DeleteRangeRequest {
pub range: GridRange,
#[serde(rename = "shiftDimension")]
pub shift_dimension: ShiftDimension,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct BandedRange {
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "bandedRangeId"
)]
pub banded_range_id: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub range: Option<GridRange>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "rowProperties"
)]
pub row_properties: Option<BandingProperties>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "columnProperties"
)]
pub column_properties: Option<BandingProperties>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct BandingProperties {
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "headerColorStyle"
)]
pub header_color_style: Option<ColorStyle>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "firstBandColorStyle"
)]
pub first_band_color_style: Option<ColorStyle>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "secondBandColorStyle"
)]
pub second_band_color_style: Option<ColorStyle>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "footerColorStyle"
)]
pub footer_color_style: Option<ColorStyle>,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct AddBandingRequest {
#[serde(rename = "bandedRange")]
pub banded_range: BandedRange,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct UpdateBandingRequest {
#[serde(rename = "bandedRange")]
pub banded_range: BandedRange,
pub fields: String,
}
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
pub struct DeleteBandingRequest {
#[serde(rename = "bandedRangeId")]
pub banded_range_id: i64,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct AddDimensionGroupRequest {
pub range: DimensionRange,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct UpdateDimensionGroupRequest {
#[serde(rename = "dimensionGroup")]
pub dimension_group: DimensionGroup,
pub fields: String,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct DeleteDimensionGroupRequest {
pub range: DimensionRange,
}
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
pub enum PasteType {
#[serde(rename = "PASTE_NORMAL")]
Normal,
#[serde(rename = "PASTE_VALUES")]
Values,
#[serde(rename = "PASTE_FORMULA")]
Formula,
#[serde(rename = "PASTE_FORMAT")]
Format,
}
impl PasteType {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Normal => "PASTE_NORMAL",
Self::Values => "PASTE_VALUES",
Self::Formula => "PASTE_FORMULA",
Self::Format => "PASTE_FORMAT",
}
}
#[must_use]
pub const fn writes_values(self) -> bool {
matches!(self, Self::Normal | Self::Values | Self::Formula)
}
#[must_use]
pub const fn writes_presentation(self) -> bool {
matches!(self, Self::Normal | Self::Format)
}
}
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, Default)]
pub enum PasteOrientation {
#[default]
#[serde(rename = "NORMAL")]
Normal,
#[serde(rename = "TRANSPOSE")]
Transpose,
}
impl PasteOrientation {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Normal => "NORMAL",
Self::Transpose => "TRANSPOSE",
}
}
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct CutPasteRequest {
pub source: GridRange,
pub destination: GridCoordinate,
#[serde(rename = "pasteType")]
pub paste_type: PasteType,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct CopyPasteRequest {
pub source: GridRange,
pub destination: GridRange,
#[serde(rename = "pasteType")]
pub paste_type: PasteType,
#[serde(rename = "pasteOrientation")]
pub paste_orientation: PasteOrientation,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct PasteDataRequest {
pub coordinate: GridCoordinate,
pub data: String,
pub delimiter: String,
#[serde(rename = "type")]
pub r#type: PasteType,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct AutoFillRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub range: Option<GridRange>,
#[serde(
rename = "sourceAndDestination",
skip_serializing_if = "Option::is_none"
)]
pub source_and_destination: Option<SourceAndDestination>,
#[serde(rename = "useAlternateSeries")]
pub use_alternate_series: bool,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct SortRangeRequest {
pub range: GridRange,
#[serde(rename = "sortSpecs")]
pub sort_specs: Vec<SortSpec>,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct RandomizeRangeRequest {
pub range: GridRange,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct TextToColumnsRequest {
pub source: GridRange,
#[serde(skip_serializing_if = "Option::is_none")]
pub delimiter: Option<String>,
#[serde(rename = "delimiterType")]
pub delimiter_type: DelimiterType,
}
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
pub enum DelimiterType {
#[serde(rename = "COMMA")]
Comma,
#[serde(rename = "SEMICOLON")]
Semicolon,
#[serde(rename = "PERIOD")]
Period,
#[serde(rename = "SPACE")]
Space,
#[serde(rename = "CUSTOM")]
Custom,
#[serde(rename = "AUTODETECT")]
Autodetect,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct TrimWhitespaceRequest {
pub range: GridRange,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct DeleteDuplicatesRequest {
pub range: GridRange,
#[serde(rename = "comparisonColumns", skip_serializing_if = "Vec::is_empty")]
pub comparison_columns: Vec<DimensionRange>,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct SourceAndDestination {
pub source: GridRange,
pub dimension: Dimension,
#[serde(rename = "fillLength")]
pub fill_length: i64,
}
#[allow(missing_docs)]
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct FindReplaceRequest {
pub find: String,
pub replacement: String,
#[serde(rename = "matchCase")]
pub match_case: bool,
#[serde(rename = "matchEntireCell")]
pub match_entire_cell: bool,
#[serde(rename = "searchByRegex")]
pub search_by_regex: bool,
#[serde(rename = "includeFormulas")]
pub include_formulas: bool,
#[serde(flatten)]
pub scope: FindReplaceScope,
}
#[allow(missing_docs)]
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
#[serde(untagged)]
pub enum FindReplaceScope {
Range {
range: GridRange,
},
Sheet {
#[serde(rename = "sheetId")]
sheet_id: i64,
},
AllSheets {
#[serde(rename = "allSheets")]
all_sheets: bool,
},
}
#[allow(missing_docs)]
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
pub struct FindReplaceResponse {
#[serde(default, rename = "valuesChanged")]
pub values_changed: i64,
#[serde(default, rename = "formulasChanged")]
pub formulas_changed: i64,
#[serde(default, rename = "rowsChanged")]
pub rows_changed: i64,
#[serde(default, rename = "sheetsChanged")]
pub sheets_changed: i64,
#[serde(default, rename = "occurrencesChanged")]
pub occurrences_changed: i64,
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
pub struct TrimWhitespaceResponse {
#[serde(default, rename = "cellsChangedCount")]
pub cells_changed_count: i64,
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
pub struct DeleteDuplicatesResponse {
#[serde(default, rename = "duplicatesRemovedCount")]
pub duplicates_removed_count: i64,
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq)]
pub struct BatchUpdateResponse {
#[serde(default)]
pub replies: Vec<BatchUpdateReply>,
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq)]
pub struct BatchUpdateReply {
#[serde(default, rename = "findReplace")]
pub find_replace: Option<FindReplaceResponse>,
#[serde(default, rename = "addSheet")]
pub add_sheet: Option<AddSheetReply>,
#[serde(default, rename = "duplicateSheet")]
pub duplicate_sheet: Option<AddSheetReply>,
#[serde(default, rename = "addProtectedRange")]
pub add_protected_range: Option<AddProtectedRangeReply>,
#[serde(default, rename = "addFilterView")]
pub add_filter_view: Option<AddFilterViewReply>,
#[serde(default, rename = "addNamedRange")]
pub add_named_range: Option<AddNamedRangeReply>,
#[serde(default, rename = "addChart")]
pub add_chart: Option<AddChartReply>,
#[serde(default, rename = "addSlicer")]
pub add_slicer: Option<AddSlicerReply>,
#[serde(default, rename = "addBanding")]
pub add_banding: Option<AddBandingReply>,
#[serde(default, rename = "trimWhitespace")]
pub trim_whitespace: Option<TrimWhitespaceResponse>,
#[serde(default, rename = "deleteDuplicates")]
pub delete_duplicates: Option<DeleteDuplicatesResponse>,
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
pub struct AddProtectedRangeReply {
#[serde(default, rename = "protectedRange")]
pub protected_range: Option<ProtectedRange>,
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
pub struct AddFilterViewReply {
#[serde(default)]
pub filter: Option<FilterView>,
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
pub struct AddNamedRangeReply {
#[serde(default, rename = "namedRange")]
pub named_range: Option<NamedRange>,
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq)]
pub struct AddChartReply {
#[serde(default)]
pub chart: Option<EmbeddedChart>,
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
pub struct AddSlicerReply {
#[serde(default)]
pub slicer: Option<Slicer>,
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq)]
pub struct AddBandingReply {
#[serde(default, rename = "bandedRange")]
pub banded_range: Option<BandedRange>,
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
pub struct AddSheetReply {
#[serde(default)]
pub properties: Option<SheetProperties>,
}
pub const DOCUMENT_VISIBILITY: &str = "DOCUMENT";
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct DeveloperMetadataLocation {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub spreadsheet: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none", rename = "sheetId")]
pub sheet_id: Option<i64>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "dimensionRange"
)]
pub dimension_range: Option<DimensionRange>,
}
impl DeveloperMetadataLocation {
#[must_use]
pub fn spreadsheet() -> Self {
Self {
spreadsheet: Some(true),
..Self::default()
}
}
#[must_use]
pub fn sheet(sheet_id: i64) -> Self {
Self {
sheet_id: Some(sheet_id),
..Self::default()
}
}
#[must_use]
pub fn dimension(range: DimensionRange) -> Self {
Self {
dimension_range: Some(range),
..Self::default()
}
}
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct NewDeveloperMetadata {
#[serde(rename = "metadataKey")]
pub metadata_key: String,
#[serde(rename = "metadataValue")]
pub metadata_value: String,
pub location: DeveloperMetadataLocation,
pub visibility: String,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct CreateDeveloperMetadataRequest {
#[serde(rename = "developerMetadata")]
pub developer_metadata: NewDeveloperMetadata,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct DeveloperMetadataValueUpdate {
#[serde(rename = "metadataValue")]
pub metadata_value: String,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct UpdateDeveloperMetadataRequest {
#[serde(rename = "dataFilters")]
pub data_filters: Vec<DataFilter>,
#[serde(rename = "developerMetadata")]
pub developer_metadata: DeveloperMetadataValueUpdate,
pub fields: String,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct DeleteDeveloperMetadataRequest {
#[serde(rename = "dataFilter")]
pub data_filter: DataFilter,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct DeveloperMetadataLookupFilter {
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "metadataLocation"
)]
pub metadata_location: Option<DeveloperMetadataLocation>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "metadataKey"
)]
pub metadata_key: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub visibility: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "locationMatchingStrategy"
)]
pub location_matching_strategy: Option<String>,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct DataFilter {
#[serde(rename = "developerMetadataLookup")]
pub developer_metadata_lookup: DeveloperMetadataLookupFilter,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
pub struct DeveloperMetadata {
#[serde(default, rename = "metadataId")]
pub metadata_id: i64,
#[serde(default, rename = "metadataKey")]
pub metadata_key: String,
#[serde(default, rename = "metadataValue")]
pub metadata_value: String,
#[serde(default)]
pub location: DeveloperMetadataLocation,
#[serde(default)]
pub visibility: String,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct SearchDeveloperMetadataRequest {
#[serde(rename = "dataFilters")]
pub data_filters: Vec<DataFilter>,
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
pub struct SearchDeveloperMetadataResponse {
#[serde(default, rename = "matchedDeveloperMetadata")]
pub matched_developer_metadata: Vec<MatchedDeveloperMetadata>,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
pub struct MatchedDeveloperMetadata {
#[serde(rename = "developerMetadata")]
pub developer_metadata: DeveloperMetadata,
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn shift_dimension_as_str_matches_the_wire_spelling() {
assert_eq!(ShiftDimension::Rows.as_str(), "ROWS");
assert_eq!(ShiftDimension::Columns.as_str(), "COLUMNS");
}
#[test]
fn recalculation_interval_as_str_matches_the_wire_spelling() {
assert_eq!(
RecalculationInterval::Unspecified.as_str(),
"RECALCULATION_INTERVAL_UNSPECIFIED"
);
assert_eq!(RecalculationInterval::OnChange.as_str(), "ON_CHANGE");
assert_eq!(RecalculationInterval::Minute.as_str(), "MINUTE");
assert_eq!(RecalculationInterval::Hour.as_str(), "HOUR");
}
#[test]
fn paste_type_as_str_matches_the_wire_spelling() {
assert_eq!(PasteType::Normal.as_str(), "PASTE_NORMAL");
assert_eq!(PasteType::Values.as_str(), "PASTE_VALUES");
assert_eq!(PasteType::Formula.as_str(), "PASTE_FORMULA");
assert_eq!(PasteType::Format.as_str(), "PASTE_FORMAT");
}
#[test]
fn paste_orientation_as_str_matches_the_wire_spelling() {
assert_eq!(PasteOrientation::Normal.as_str(), "NORMAL");
assert_eq!(PasteOrientation::Transpose.as_str(), "TRANSPOSE");
}
#[test]
fn iterative_calculation_settings_describe_bounds_covers_every_combination() {
assert_eq!(
IterativeCalculationSettings {
max_iterations: Some(50),
convergence_threshold: Some(0.01),
}
.describe_bounds(),
" (max 50 iterations, threshold 0.01)"
);
assert_eq!(
IterativeCalculationSettings {
max_iterations: Some(50),
convergence_threshold: None,
}
.describe_bounds(),
" (max 50 iterations)"
);
assert_eq!(
IterativeCalculationSettings {
max_iterations: None,
convergence_threshold: Some(0.01),
}
.describe_bounds(),
" (threshold 0.01)"
);
assert_eq!(
IterativeCalculationSettings::default().describe_bounds(),
""
);
}
#[test]
fn insert_range_serializes_the_column_shift_direction() {
let request = BatchUpdateRequestItem::InsertRange(InsertRangeRequest {
range: GridRange {
sheet_id: 7,
start_row_index: Some(1),
end_row_index: Some(2),
start_column_index: Some(3),
end_column_index: Some(4),
},
shift_dimension: ShiftDimension::Columns,
});
assert_eq!(
serde_json::to_value(request).unwrap(),
serde_json::json!({
"insertRange": {
"range": {
"sheetId": 7,
"startRowIndex": 1,
"endRowIndex": 2,
"startColumnIndex": 3,
"endColumnIndex": 4,
},
"shiftDimension": "COLUMNS",
},
})
);
}
#[test]
fn cut_paste_request_serializes_the_paste_type() {
let request = BatchUpdateRequestItem::CutPaste(CutPasteRequest {
source: GridRange {
sheet_id: 1,
start_row_index: Some(0),
end_row_index: Some(2),
start_column_index: Some(0),
end_column_index: Some(2),
},
destination: GridCoordinate {
sheet_id: 1,
row_index: 5,
column_index: 5,
},
paste_type: PasteType::Values,
});
assert_eq!(
serde_json::to_value(request).unwrap(),
serde_json::json!({
"cutPaste": {
"source": {
"sheetId": 1,
"startRowIndex": 0,
"endRowIndex": 2,
"startColumnIndex": 0,
"endColumnIndex": 2,
},
"destination": {"sheetId": 1, "rowIndex": 5, "columnIndex": 5},
"pasteType": "PASTE_VALUES",
},
})
);
}
#[test]
fn copy_paste_request_serializes_type_and_orientation() {
let request = BatchUpdateRequestItem::CopyPaste(CopyPasteRequest {
source: GridRange {
sheet_id: 1,
start_row_index: Some(0),
end_row_index: Some(1),
start_column_index: Some(0),
end_column_index: Some(1),
},
destination: GridRange {
sheet_id: 1,
start_row_index: Some(5),
end_row_index: Some(6),
start_column_index: Some(5),
end_column_index: Some(6),
},
paste_type: PasteType::Normal,
paste_orientation: PasteOrientation::Transpose,
});
assert_eq!(
serde_json::to_value(request).unwrap(),
serde_json::json!({
"copyPaste": {
"source": {
"sheetId": 1, "startRowIndex": 0, "endRowIndex": 1,
"startColumnIndex": 0, "endColumnIndex": 1,
},
"destination": {
"sheetId": 1, "startRowIndex": 5, "endRowIndex": 6,
"startColumnIndex": 5, "endColumnIndex": 6,
},
"pasteType": "PASTE_NORMAL",
"pasteOrientation": "TRANSPOSE",
},
})
);
}
#[test]
fn paste_data_request_serializes_the_delimiter_form() {
let request = BatchUpdateRequestItem::PasteData(PasteDataRequest {
coordinate: GridCoordinate {
sheet_id: 2,
row_index: 0,
column_index: 0,
},
data: "a\tb\nc\td".to_string(),
delimiter: "\t".to_string(),
r#type: PasteType::Values,
});
assert_eq!(
serde_json::to_value(request).unwrap(),
serde_json::json!({
"pasteData": {
"coordinate": {"sheetId": 2, "rowIndex": 0, "columnIndex": 0},
"data": "a\tb\nc\td",
"delimiter": "\t",
"type": "PASTE_VALUES",
},
})
);
}
#[test]
fn paste_type_writes_values_and_presentation_match_the_adr_0083_4_table() {
assert!(PasteType::Normal.writes_values());
assert!(PasteType::Normal.writes_presentation());
assert!(PasteType::Values.writes_values());
assert!(!PasteType::Values.writes_presentation());
assert!(PasteType::Formula.writes_values());
assert!(!PasteType::Formula.writes_presentation());
assert!(!PasteType::Format.writes_values());
assert!(PasteType::Format.writes_presentation());
}
#[test]
fn auto_fill_serializes_the_range_form_with_no_source_and_destination_key() {
let request = BatchUpdateRequestItem::AutoFill(AutoFillRequest {
range: Some(GridRange {
sheet_id: 0,
start_row_index: Some(0),
end_row_index: Some(10),
start_column_index: Some(0),
end_column_index: Some(1),
}),
source_and_destination: None,
use_alternate_series: false,
});
assert_eq!(
serde_json::to_value(request).unwrap(),
serde_json::json!({
"autoFill": {
"range": {
"sheetId": 0,
"startRowIndex": 0,
"endRowIndex": 10,
"startColumnIndex": 0,
"endColumnIndex": 1,
},
"useAlternateSeries": false,
},
})
);
}
#[test]
fn auto_fill_serializes_the_source_and_destination_form_with_no_range_key() {
let request = BatchUpdateRequestItem::AutoFill(AutoFillRequest {
range: None,
source_and_destination: Some(SourceAndDestination {
source: GridRange {
sheet_id: 0,
start_row_index: Some(0),
end_row_index: Some(3),
start_column_index: Some(0),
end_column_index: Some(1),
},
dimension: Dimension::Rows,
fill_length: -2,
}),
use_alternate_series: true,
});
assert_eq!(
serde_json::to_value(request).unwrap(),
serde_json::json!({
"autoFill": {
"sourceAndDestination": {
"source": {
"sheetId": 0,
"startRowIndex": 0,
"endRowIndex": 3,
"startColumnIndex": 0,
"endColumnIndex": 1,
},
"dimension": "ROWS",
"fillLength": -2,
},
"useAlternateSeries": true,
},
})
);
}
#[test]
fn sort_range_serializes_its_range_and_ordered_specs() {
let request = BatchUpdateRequestItem::SortRange(SortRangeRequest {
range: GridRange {
sheet_id: 0,
start_row_index: Some(1),
end_row_index: Some(10),
start_column_index: Some(0),
end_column_index: Some(3),
},
sort_specs: vec![
SortSpec {
dimension_index: 1,
sort_order: SortOrder::Descending,
},
SortSpec {
dimension_index: 0,
sort_order: SortOrder::Ascending,
},
],
});
assert_eq!(
serde_json::to_value(request).unwrap(),
serde_json::json!({
"sortRange": {
"range": {
"sheetId": 0, "startRowIndex": 1, "endRowIndex": 10,
"startColumnIndex": 0, "endColumnIndex": 3,
},
"sortSpecs": [
{"dimensionIndex": 1, "sortOrder": "DESCENDING"},
{"dimensionIndex": 0, "sortOrder": "ASCENDING"},
],
},
})
);
}
#[test]
fn randomize_range_serializes_its_range() {
let request = BatchUpdateRequestItem::RandomizeRange(RandomizeRangeRequest {
range: GridRange {
sheet_id: 0,
start_row_index: Some(1),
end_row_index: Some(10),
start_column_index: Some(0),
end_column_index: Some(3),
},
});
assert_eq!(
serde_json::to_value(request).unwrap(),
serde_json::json!({
"randomizeRange": {
"range": {
"sheetId": 0, "startRowIndex": 1, "endRowIndex": 10,
"startColumnIndex": 0, "endColumnIndex": 3,
},
},
})
);
}
#[test]
fn text_to_columns_omits_delimiter_for_a_fixed_type() {
let request = BatchUpdateRequestItem::TextToColumns(TextToColumnsRequest {
source: GridRange {
sheet_id: 0,
start_row_index: Some(1),
end_row_index: Some(100),
start_column_index: Some(0),
end_column_index: Some(1),
},
delimiter: None,
delimiter_type: DelimiterType::Comma,
});
assert_eq!(
serde_json::to_value(request).unwrap(),
serde_json::json!({
"textToColumns": {
"source": {
"sheetId": 0, "startRowIndex": 1, "endRowIndex": 100,
"startColumnIndex": 0, "endColumnIndex": 1,
},
"delimiterType": "COMMA",
},
})
);
}
#[test]
fn text_to_columns_serializes_a_custom_delimiter() {
let request = BatchUpdateRequestItem::TextToColumns(TextToColumnsRequest {
source: GridRange {
sheet_id: 0,
start_row_index: Some(0),
end_row_index: Some(5),
start_column_index: Some(0),
end_column_index: Some(1),
},
delimiter: Some("|".to_string()),
delimiter_type: DelimiterType::Custom,
});
assert_eq!(
serde_json::to_value(request).unwrap(),
serde_json::json!({
"textToColumns": {
"source": {
"sheetId": 0, "startRowIndex": 0, "endRowIndex": 5,
"startColumnIndex": 0, "endColumnIndex": 1,
},
"delimiter": "|",
"delimiterType": "CUSTOM",
},
})
);
}
#[test]
fn spreadsheet_parses_a_realistic_fields_masked_response() {
let json = serde_json::json!({
"spreadsheetId": "sheet-1",
"properties": {"title": "Budget"},
"sheets": [
{"properties": {"sheetId": 0, "title": "Q1", "index": 0,
"gridProperties": {"rowCount": 1000, "columnCount": 26}}},
{"properties": {"sheetId": 7, "title": "My Sheet", "index": 1, "hidden": true}},
],
});
let parsed: Spreadsheet = serde_json::from_value(json).unwrap();
assert_eq!(parsed.title(), "Budget");
assert_eq!(parsed.sheet_titles(), vec!["Q1", "My Sheet"]);
assert!(!parsed.sheets[0].hidden());
assert!(parsed.sheets[1].hidden());
assert_eq!(
parsed.sheets[0]
.properties
.as_ref()
.unwrap()
.grid_properties
.as_ref()
.unwrap()
.row_count,
Some(1000)
);
}
#[test]
fn spreadsheet_tolerates_unmodelled_fields() {
let json = serde_json::json!({
"spreadsheetId": "s",
"somethingNew": {"nested": true},
"sheets": [{"properties": {"title": "A"}, "somethingElseNew": 1}],
});
let parsed: Spreadsheet = serde_json::from_value(json).unwrap();
assert_eq!(parsed.sheet_titles(), vec!["A"]);
}
#[test]
fn sheet_conditional_formats_round_trips_a_boolean_and_a_gradient_rule() {
let json = serde_json::json!({
"sheets": [{
"properties": {"title": "A"},
"conditionalFormats": [
{
"ranges": [{"sheetId": 0, "startRowIndex": 0, "endRowIndex": 1}],
"booleanRule": {
"condition": {"type": "NUMBER_GREATER", "values": [{"userEnteredValue": "10"}]},
"format": {"backgroundColorStyle": {"rgbColor": {"red": 1.0, "green": 0.0, "blue": 0.0}}},
},
},
{
"ranges": [{"sheetId": 0, "startRowIndex": 1, "endRowIndex": 2}],
"gradientRule": {
"minpoint": {
"color": {"red": 1.0, "green": 1.0, "blue": 1.0},
"colorStyle": {"rgbColor": {"red": 1.0, "green": 1.0, "blue": 1.0}},
"type": "MIN",
},
"maxpoint": {
"color": {"red": 0.0, "green": 1.0, "blue": 0.0},
"colorStyle": {"rgbColor": {"red": 0.0, "green": 1.0, "blue": 0.0}},
"type": "NUMBER",
"value": "100",
},
},
},
],
}],
});
let parsed: Spreadsheet = serde_json::from_value(json).unwrap();
let rules = &parsed.sheets[0].conditional_formats;
assert_eq!(rules.len(), 2);
assert_eq!(
rules[0]
.boolean_rule
.as_ref()
.unwrap()
.condition
.condition_type,
"NUMBER_GREATER"
);
assert!(rules[0].gradient_rule.is_none());
assert!(rules[1].boolean_rule.is_none());
let gradient = rules[1].gradient_rule.as_ref().unwrap();
assert!(gradient.midpoint.is_none());
let min = gradient.minpoint.as_ref().unwrap();
assert_eq!(min.point_type, "MIN");
assert_eq!(min.value, None);
assert_eq!(
min.color_style.rgb_color,
Some(Color {
red: 1.0,
green: 1.0,
blue: 1.0
})
);
let max = gradient.maxpoint.as_ref().unwrap();
assert_eq!(max.point_type, "NUMBER");
assert_eq!(max.value.as_deref(), Some("100"));
}
#[test]
fn spreadsheet_title_is_empty_when_the_fields_mask_omitted_it() {
let parsed: Spreadsheet = serde_json::from_value(serde_json::json!({})).unwrap();
assert_eq!(parsed.title(), "");
assert!(parsed.sheet_titles().is_empty());
}
#[test]
fn has_sheet_titled_skips_a_property_less_sheet() {
let json = serde_json::json!({
"sheets": [
{},
{"properties": {"title": "Q1"}},
],
});
let parsed: Spreadsheet = serde_json::from_value(json).unwrap();
assert!(!parsed.has_sheet_titled(""));
assert!(parsed.has_sheet_titled("Q1"));
assert!(!parsed.has_sheet_titled("nonexistent"));
}
#[test]
fn value_range_absent_values_parses_as_empty_not_an_error() {
let json = serde_json::json!({"range": "'Blank'!A1:Z1000"});
let parsed: ValueRange = serde_json::from_value(json).unwrap();
assert!(parsed.values.is_empty());
assert_eq!(parsed.width(), 0);
assert_eq!(parsed.range.as_deref(), Some("'Blank'!A1:Z1000"));
}
#[test]
fn value_range_width_is_the_widest_ragged_row() {
let json = serde_json::json!({
"range": "S!A1:C2",
"values": [["a", "b", "c"], ["d"]],
});
let parsed: ValueRange = serde_json::from_value(json).unwrap();
assert_eq!(parsed.width(), 3);
assert_eq!(parsed.values[1].len(), 1, "raggedness is preserved as-is");
}
#[test]
fn value_range_preserves_non_string_cell_types() {
let json = serde_json::json!({"range": "S!A1:C1", "values": [[1234.5, true, "x"]]});
let parsed: ValueRange = serde_json::from_value(json).unwrap();
assert!(parsed.values[0][0].is_number());
assert!(parsed.values[0][1].is_boolean());
assert!(parsed.values[0][2].is_string());
}
#[test]
fn batch_get_response_parses_and_defaults_to_empty() {
let json = serde_json::json!({
"valueRanges": [
{"range": "'A'!A1:B1", "values": [["1", "2"]]},
{"range": "'B'!A1:A1"},
],
});
let parsed: BatchGetValuesResponse = serde_json::from_value(json).unwrap();
assert_eq!(parsed.value_ranges.len(), 2);
assert!(parsed.value_ranges[1].values.is_empty());
let empty: BatchGetValuesResponse = serde_json::from_value(serde_json::json!({})).unwrap();
assert!(empty.value_ranges.is_empty());
}
#[test]
fn update_values_response_parses_the_counts() {
let json = serde_json::json!({
"spreadsheetId": "s", "updatedRange": "'Q1'!A1:B2",
"updatedRows": 2, "updatedColumns": 2, "updatedCells": 4,
});
let parsed: UpdateValuesResponse = serde_json::from_value(json).unwrap();
assert_eq!(parsed.updated_range.as_deref(), Some("'Q1'!A1:B2"));
assert_eq!(parsed.updated_cells, Some(4));
}
#[test]
fn append_values_response_nests_the_counts_under_updates() {
let json = serde_json::json!({
"tableRange": "'Q1'!A1:B3",
"updates": {"updatedRange": "'Q1'!A4:B4", "updatedRows": 1, "updatedCells": 2},
});
let parsed: AppendValuesResponse = serde_json::from_value(json).unwrap();
assert_eq!(parsed.table_range.as_deref(), Some("'Q1'!A1:B3"));
assert_eq!(parsed.updates.unwrap().updated_cells, Some(2));
}
#[test]
fn clear_values_response_parses_the_cleared_range() {
let json = serde_json::json!({"spreadsheetId": "s", "clearedRange": "'Q1'!A1:Z999"});
let parsed: ClearValuesResponse = serde_json::from_value(json).unwrap();
assert_eq!(parsed.cleared_range.as_deref(), Some("'Q1'!A1:Z999"));
}
#[test]
fn write_responses_tolerate_missing_counts() {
let parsed: UpdateValuesResponse = serde_json::from_value(serde_json::json!({})).unwrap();
assert_eq!(parsed.updated_cells, None);
let parsed: AppendValuesResponse = serde_json::from_value(serde_json::json!({})).unwrap();
assert!(parsed.updates.is_none());
}
#[test]
fn find_replace_request_serializes_exactly_one_scope_and_reply_counts() {
let request = BatchUpdateRequestItem::FindReplace(FindReplaceRequest {
find: "draft".to_string(),
replacement: "final".to_string(),
match_case: true,
match_entire_cell: false,
search_by_regex: false,
include_formulas: true,
scope: FindReplaceScope::Sheet { sheet_id: 0 },
});
assert_eq!(
serde_json::to_value(request).unwrap(),
serde_json::json!({"findReplace": {
"find": "draft", "replacement": "final", "matchCase": true,
"matchEntireCell": false, "searchByRegex": false,
"includeFormulas": true, "sheetId": 0,
}})
);
let response: BatchUpdateResponse = serde_json::from_value(serde_json::json!({
"replies": [{"findReplace": {"valuesChanged": 2, "formulasChanged": 3,
"rowsChanged": 4, "sheetsChanged": 5, "occurrencesChanged": 6}}]
}))
.unwrap();
assert_eq!(
response.replies[0]
.find_replace
.as_ref()
.unwrap()
.occurrences_changed,
6
);
}
#[test]
fn grid_coordinate_defaults_every_field_when_all_are_zero() {
let parsed: GridCoordinate = serde_json::from_value(serde_json::json!({})).unwrap();
assert_eq!(parsed.sheet_id, 0);
assert_eq!(parsed.row_index, 0);
assert_eq!(parsed.column_index, 0);
}
#[test]
fn dimension_range_defaults_sheet_and_start_index() {
let parsed: DimensionRange = serde_json::from_value(serde_json::json!({
"dimension": "ROWS",
"endIndex": 3,
}))
.unwrap();
assert_eq!(parsed.sheet_id, 0);
assert_eq!(parsed.start_index, 0);
assert_eq!(parsed.end_index, 3);
assert_eq!(parsed.dimension, Dimension::Rows);
}
#[test]
fn color_defaults_the_channels_it_wasnt_given() {
let parsed: Color = serde_json::from_value(serde_json::json!({"red": 1.0})).unwrap();
assert_eq!(
parsed,
Color {
red: 1.0,
green: 0.0,
blue: 0.0,
}
);
}
#[test]
fn color_style_defaults_an_all_omitted_rgb_color() {
let parsed: ColorStyle =
serde_json::from_value(serde_json::json!({"rgbColor": {}})).unwrap();
assert_eq!(parsed.rgb_color, Some(Color::default()));
assert_eq!(parsed.theme_color, None);
}
#[test]
fn color_style_reads_a_theme_color_only_arm() {
let parsed: ColorStyle =
serde_json::from_value(serde_json::json!({"themeColor": "ACCENT1"})).unwrap();
assert_eq!(parsed.rgb_color, None);
assert_eq!(parsed.theme_color, Some("ACCENT1".to_string()));
}
#[test]
fn color_style_reads_an_empty_object_as_neither_arm() {
let parsed: ColorStyle = serde_json::from_value(serde_json::json!({})).unwrap();
assert_eq!(parsed.rgb_color, None);
assert_eq!(parsed.theme_color, None);
}
#[test]
fn color_style_round_trips_a_theme_color_unchanged() {
let original = serde_json::json!({"themeColor": "ACCENT1"});
let parsed: ColorStyle = serde_json::from_value(original.clone()).unwrap();
assert_eq!(serde_json::to_value(&parsed).unwrap(), original);
}
#[test]
fn color_style_rgb_serializes_only_rgb_color() {
let style = ColorStyle::rgb(Color {
red: 1.0,
green: 0.0,
blue: 0.0,
});
let value = serde_json::to_value(&style).unwrap();
assert_eq!(
value,
serde_json::json!({"rgbColor": {"red": 1.0, "green": 0.0, "blue": 0.0}}),
"themeColor must not appear on an outbound rgb ColorStyle"
);
}
#[test]
fn color_style_describe_prefers_rgb_over_theme() {
let style = ColorStyle::rgb(Color {
red: 1.0,
green: 0.0,
blue: 0.0,
});
assert_eq!(style.describe(), "#FF0000");
}
#[test]
fn color_style_describe_names_a_theme_color() {
let style = ColorStyle::theme("ACCENT1");
assert_eq!(style.describe(), "theme:ACCENT1");
}
#[test]
fn color_style_describe_reports_neither_arm_as_unset() {
assert_eq!(ColorStyle::default().describe(), "(unset)");
}
#[test]
fn gradient_rule_parses_a_theme_color_only_minpoint() {
let parsed: GradientRule = serde_json::from_value(serde_json::json!({
"minpoint": {"colorStyle": {"themeColor": "ACCENT1"}, "type": "MIN"},
"maxpoint": {"colorStyle": {"rgbColor": {"red": 1.0}}, "type": "MAX"},
}))
.unwrap();
let minpoint = parsed.minpoint.unwrap();
assert_eq!(minpoint.color_style.rgb_color, None);
assert_eq!(
minpoint.color_style.theme_color,
Some("ACCENT1".to_string())
);
}
#[test]
fn pivot_group_defaults_offset_and_totals() {
let parsed: PivotGroup = serde_json::from_value(serde_json::json!({})).unwrap();
assert_eq!(parsed.source_column_offset, 0);
assert!(!parsed.show_totals);
assert_eq!(parsed.sort_order, None);
}
#[test]
fn pivot_value_defaults_the_source_column_offset() {
let parsed: PivotValue =
serde_json::from_value(serde_json::json!({"summarizeFunction": "SUM"})).unwrap();
assert_eq!(parsed.source_column_offset, 0);
assert_eq!(parsed.summarize_function, "SUM");
}
#[test]
fn pivot_filter_spec_defaults_the_column_offset_index() {
let parsed: PivotFilterSpec =
serde_json::from_value(serde_json::json!({"filterCriteria": {}})).unwrap();
assert_eq!(parsed.column_offset_index, 0);
assert!(parsed.filter_criteria.visible_values.is_empty());
}
#[test]
fn sort_spec_defaults_the_dimension_index_to_column_a() {
let parsed: SortSpec =
serde_json::from_value(serde_json::json!({"sortOrder": "ASCENDING"})).unwrap();
assert_eq!(parsed.dimension_index, 0);
assert_eq!(parsed.sort_order, SortOrder::Ascending);
}
#[test]
fn number_format_defaults_an_empty_pattern() {
let parsed: NumberFormat =
serde_json::from_value(serde_json::json!({"type": "NUMBER"})).unwrap();
assert_eq!(parsed.format_type, "NUMBER");
assert_eq!(parsed.pattern, "");
}
#[test]
fn proto3_enum_wire_strings_default_to_empty_when_omitted() {
let point: InterpolationPoint =
serde_json::from_value(serde_json::json!({"colorStyle": {"rgbColor": {}}})).unwrap();
assert_eq!(point.point_type, "");
let basic_chart: BasicChartSpec = serde_json::from_value(serde_json::json!({})).unwrap();
assert_eq!(basic_chart.chart_type, "");
let axis: BasicChartAxis = serde_json::from_value(serde_json::json!({})).unwrap();
assert_eq!(axis.position, "");
let pivot_value: PivotValue = serde_json::from_value(serde_json::json!({})).unwrap();
assert_eq!(pivot_value.summarize_function, "");
}
fn proto3_omit_zeros(value: serde_json::Value) -> serde_json::Value {
match value {
serde_json::Value::Object(map) => {
let mut out = serde_json::Map::new();
for (key, val) in map {
let val = proto3_omit_zeros(val);
let is_zero_scalar = match &val {
serde_json::Value::Bool(b) => !*b,
serde_json::Value::Number(n) => n.as_f64() == Some(0.0),
serde_json::Value::String(s) => s.is_empty(),
_ => false,
};
if !is_zero_scalar {
out.insert(key, val);
}
}
serde_json::Value::Object(out)
}
serde_json::Value::Array(items) => {
serde_json::Value::Array(items.into_iter().map(proto3_omit_zeros).collect())
}
other => other,
}
}
fn zero_boundary_spreadsheet() -> Spreadsheet {
let sheet_id = 0; let full_range = GridRange {
sheet_id,
start_row_index: Some(1),
end_row_index: Some(6),
start_column_index: Some(1),
end_column_index: Some(3),
};
Spreadsheet {
spreadsheet_id: Some("zero-boundary".to_string()),
properties: Some(SpreadsheetProperties {
title: "Zero boundaries".to_string(),
..Default::default()
}),
sheets: vec![Sheet {
properties: Some(SheetProperties {
sheet_id: Some(1), title: "Sheet1".to_string(),
..Default::default()
}),
basic_filter: Some(BasicFilter {
range: Some(full_range),
sort_specs: vec![SortSpec {
dimension_index: 0, sort_order: SortOrder::Ascending,
}],
criteria: BTreeMap::new(),
}),
conditional_formats: vec![ConditionalFormatRule {
ranges: vec![full_range],
boolean_rule: Some(BooleanRule {
condition: BooleanCondition {
condition_type: "NUMBER_GREATER".to_string(),
values: vec![ConditionValue {
user_entered_value: Some("0".to_string()),
relative_date: None,
}],
},
format: CellFormat {
background_color_style: Some(ColorStyle::rgb(Color {
red: 0.0,
green: 0.0,
blue: 0.0,
})),
number_format: Some(NumberFormat {
format_type: "NUMBER".to_string(),
pattern: String::new(),
}),
..Default::default()
},
}),
gradient_rule: None,
}],
charts: vec![EmbeddedChart {
chart_id: Some(1),
spec: Some(ChartSpec {
title: Some("Chart".to_string()),
subtitle: None,
basic_chart: Some(BasicChartSpec {
chart_type: "COLUMN".to_string(),
legend_position: None,
stacked_type: None,
header_count: None,
axis: Vec::new(),
domains: vec![BasicChartDomain {
domain: ChartData {
source_range: ChartSourceRange {
sources: vec![GridRange {
sheet_id,
start_row_index: Some(1),
end_row_index: Some(4),
start_column_index: Some(1),
end_column_index: Some(2),
}],
},
extra: BTreeMap::new(),
},
extra: BTreeMap::new(),
}],
series: vec![BasicChartSeries {
series: ChartData {
source_range: ChartSourceRange {
sources: vec![GridRange {
sheet_id,
start_row_index: Some(1),
end_row_index: Some(4),
start_column_index: Some(2),
end_column_index: Some(3),
}],
},
extra: BTreeMap::new(),
},
target_axis: None,
extra: BTreeMap::new(),
}],
extra: BTreeMap::new(),
}),
pie_chart: None,
extra: BTreeMap::new(),
}),
position: Some(EmbeddedObjectPosition {
sheet_id: None,
overlay_position: Some(OverlayPosition {
anchor_cell: GridCoordinate {
sheet_id,
row_index: 0,
column_index: 0,
},
offset_x_pixels: None,
offset_y_pixels: None,
width_pixels: None,
height_pixels: None,
}),
new_sheet: None,
}),
border: None,
}],
slicers: vec![Slicer {
slicer_id: Some(2),
spec: Some(SlicerSpec {
data_range: Some(full_range),
filter_criteria: None,
column_index: Some(2),
apply_to_pivot_tables: Some(true),
title: None,
extra: BTreeMap::new(),
}),
position: Some(EmbeddedObjectPosition {
sheet_id: None,
overlay_position: Some(OverlayPosition {
anchor_cell: GridCoordinate {
sheet_id,
row_index: 10,
column_index: 3,
},
offset_x_pixels: None,
offset_y_pixels: None,
width_pixels: None,
height_pixels: None,
}),
new_sheet: None,
}),
}],
banded_ranges: vec![BandedRange {
banded_range_id: Some(3),
range: Some(full_range),
row_properties: Some(BandingProperties {
header_color_style: None,
first_band_color_style: Some(ColorStyle::rgb(Color {
red: 0.0,
green: 0.0,
blue: 0.0,
})),
second_band_color_style: Some(ColorStyle::rgb(Color {
red: 1.0,
green: 1.0,
blue: 1.0,
})),
footer_color_style: None,
}),
column_properties: None,
}],
row_groups: vec![DimensionGroup {
range: DimensionRange {
sheet_id, dimension: Dimension::Rows,
start_index: 0, end_index: 3,
},
depth: 1,
collapsed: false,
}],
data: vec![GridData {
start_row: Some(1),
start_column: Some(1),
row_data: vec![RowData {
values: vec![CellSnapshot {
pivot_table: Some(PivotTable {
source: full_range,
rows: vec![PivotGroup {
source_column_offset: 0, show_totals: false, sort_order: Some("ASCENDING".to_string()),
}],
columns: Vec::new(),
values: vec![PivotValue {
source_column_offset: 0, summarize_function: "SUM".to_string(),
name: None,
}],
filter_specs: vec![PivotFilterSpec {
column_offset_index: 0, filter_criteria: PivotFilterCriteria {
visible_values: vec!["keep".to_string()],
},
}],
value_layout: None,
}),
formatted_value: None,
..Default::default()
}],
}],
}],
..Default::default()
}],
..Default::default()
}
}
#[test]
fn proto3_omit_zeros_strips_only_zero_valued_scalars() {
let stripped = proto3_omit_zeros(serde_json::json!({
"a": 0,
"b": 0.0,
"c": false,
"d": "",
"e": 1,
"f": "kept",
"g": [0, "", {"h": 0, "i": 1}],
"j": {},
}));
assert_eq!(
stripped,
serde_json::json!({
"e": 1,
"f": "kept",
"g": [0, "", {"i": 1}],
"j": {},
})
);
}
#[test]
fn proto3_omit_zeros_round_trip_recovers_every_zero_boundary_field() {
let original = zero_boundary_spreadsheet();
let json = serde_json::to_value(&original).unwrap();
let wire_shape = proto3_omit_zeros(json);
let round_tripped: Spreadsheet = serde_json::from_value(wire_shape)
.expect("every zero-valued scalar here must have #[serde(default)]");
assert_eq!(round_tripped, original);
}
}