use openlark_core::api::{ApiResponseTrait, ResponseFormat};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ReadSingleRangeRequest {
pub spreadsheet_token: String,
pub range: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_format: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub value_render_option: Option<String>,
}
impl ReadSingleRangeRequest {
pub fn validate(&self) -> Result<(), String> {
if self.spreadsheet_token.trim().is_empty() {
return Err("电子表格token不能为空".to_string());
}
if self.range.trim().is_empty() {
return Err("读取范围不能为空".to_string());
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, PartialEq, Default)]
pub struct ReadSingleRangeResponse {
pub value_range: Option<ValueRange>,
#[serde(skip_serializing_if = "Option::is_none")]
pub spreadsheet_id: Option<String>,
}
impl<'de> Deserialize<'de> for ReadSingleRangeResponse {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde_json::Value;
let raw_value = Value::deserialize(deserializer)?;
let spreadsheet_id = raw_value
.get("spreadsheetId")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let value_range = raw_value.get("valueRange").and_then(|v| {
let range = v
.get("range")
.and_then(|r| r.as_str())
.map(|s| s.to_string());
let major_dimension = v
.get("majorDimension")
.and_then(|m| m.as_str())
.map(|s| s.to_string());
let values = v.get("values").and_then(|vals| vals.as_array()).map(|arr| {
arr.iter()
.filter_map(|row| row.as_array())
.map(|row| row.to_vec())
.collect::<Vec<_>>()
});
if range.is_some() || major_dimension.is_some() || values.is_some() {
Some(ValueRange {
range,
major_dimension,
values,
})
} else {
None
}
});
Ok(ReadSingleRangeResponse {
value_range,
spreadsheet_id,
})
}
}
impl ApiResponseTrait for ReadSingleRangeResponse {
fn data_format() -> ResponseFormat {
ResponseFormat::Data
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ReadMultipleRangesRequest {
pub spreadsheet_token: String,
pub ranges: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_format: Option<bool>,
}
impl ReadMultipleRangesRequest {
pub fn validate(&self) -> Result<(), String> {
if self.spreadsheet_token.trim().is_empty() {
return Err("电子表格token不能为空".to_string());
}
if self.ranges.is_empty() {
return Err("读取范围列表不能为空".to_string());
}
if self.ranges.len() > 10 {
return Err("读取范围数量不能超过10个".to_string());
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, PartialEq, Default)]
pub struct ReadMultipleRangesResponse {
pub value_ranges: Option<Vec<ValueRange>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub spreadsheet_id: Option<String>,
}
impl<'de> Deserialize<'de> for ReadMultipleRangesResponse {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde_json::Value;
let raw_value = Value::deserialize(deserializer)?;
let spreadsheet_id = raw_value
.get("spreadsheetId")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let value_ranges = raw_value
.get("valueRanges")
.and_then(|vals| vals.as_array())
.and_then(|arr| {
let mut ranges = vec![];
for v in arr {
let range = v
.get("range")
.and_then(|r| r.as_str())
.map(|s| s.to_string());
let major_dimension = v
.get("majorDimension")
.and_then(|m| m.as_str())
.map(|s| s.to_string());
let values = v.get("values").and_then(|vals| vals.as_array()).map(|arr| {
arr.iter()
.filter_map(|row| row.as_array())
.map(|row| row.to_vec())
.collect::<Vec<_>>()
});
if range.is_some() || major_dimension.is_some() || values.is_some() {
ranges.push(ValueRange {
range,
major_dimension,
values,
});
}
}
if !ranges.is_empty() {
Some(ranges)
} else {
None
}
});
Ok(ReadMultipleRangesResponse {
value_ranges,
spreadsheet_id,
})
}
}
impl ApiResponseTrait for ReadMultipleRangesResponse {
fn data_format() -> ResponseFormat {
ResponseFormat::Data
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct WriteSingleRangeRequest {
pub spreadsheet_token: String,
pub range: String,
pub values: Vec<Vec<serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub value_input_option: Option<String>,
}
impl WriteSingleRangeRequest {
pub fn validate(&self) -> Result<(), String> {
if self.spreadsheet_token.trim().is_empty() {
return Err("电子表格token不能为空".to_string());
}
if self.range.trim().is_empty() {
return Err("写入范围不能为空".to_string());
}
if self.values.is_empty() {
return Err("写入数据不能为空".to_string());
}
if self.values.len() > 1000 {
return Err("写入数据行数不能超过1000行".to_string());
}
for row in &self.values {
if row.len() > 1000 {
return Err("写入数据列数不能超过1000列".to_string());
}
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct WriteSingleRangeResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_range: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_rows: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_columns: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_cells: Option<i32>,
}
impl ApiResponseTrait for WriteSingleRangeResponse {
fn data_format() -> ResponseFormat {
ResponseFormat::Data
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct WriteMultipleRangesRequest {
pub spreadsheet_token: String,
pub data: Vec<WriteData>,
#[serde(skip_serializing_if = "Option::is_none")]
pub value_input_option: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct WriteData {
pub range: String,
pub values: Vec<Vec<serde_json::Value>>,
}
impl WriteMultipleRangesRequest {
pub fn validate(&self) -> Result<(), String> {
if self.spreadsheet_token.trim().is_empty() {
return Err("电子表格token不能为空".to_string());
}
if self.data.is_empty() {
return Err("写入数据列表不能为空".to_string());
}
if self.data.len() > 10 {
return Err("写入数据数量不能超过10个".to_string());
}
for write_data in &self.data {
if write_data.range.trim().is_empty() {
return Err("写入范围不能为空".to_string());
}
if write_data.values.is_empty() {
return Err("写入数据不能为空".to_string());
}
if write_data.values.len() > 1000 {
return Err("写入数据行数不能超过1000行".to_string());
}
for row in &write_data.values {
if row.len() > 1000 {
return Err("写入数据列数不能超过1000列".to_string());
}
}
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct WriteMultipleRangesResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub replies: Option<Vec<WriteSingleRangeResponse>>,
}
impl ApiResponseTrait for WriteMultipleRangesResponse {
fn data_format() -> ResponseFormat {
ResponseFormat::Data
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AppendDataRequest {
pub spreadsheet_token: String,
pub range: String,
pub values: Vec<Vec<serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub value_input_option: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub insert_data_option: Option<String>,
}
impl AppendDataRequest {
pub fn validate(&self) -> Result<(), String> {
if self.spreadsheet_token.trim().is_empty() {
return Err("电子表格token不能为空".to_string());
}
if self.range.trim().is_empty() {
return Err("追加范围不能为空".to_string());
}
if self.values.is_empty() {
return Err("追加数据不能为空".to_string());
}
if self.values.len() > 1000 {
return Err("追加数据行数不能超过1000行".to_string());
}
for row in &self.values {
if row.len() > 1000 {
return Err("追加数据列数不能超过1000列".to_string());
}
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct AppendDataResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub table_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updates: Option<UpdateInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct UpdateInfo {
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_range: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_rows: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_columns: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_cells: Option<i32>,
}
impl ApiResponseTrait for AppendDataResponse {
fn data_format() -> ResponseFormat {
ResponseFormat::Data
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct InsertDimensionRequest {
pub spreadsheet_token: String,
pub dimension: String,
pub start_index: i32,
pub end_index: i32,
#[serde(skip_serializing_if = "Option::is_none")]
pub inherit_style_before: Option<bool>,
}
impl InsertDimensionRequest {
pub fn validate(&self) -> Result<(), String> {
if self.spreadsheet_token.trim().is_empty() {
return Err("电子表格token不能为空".to_string());
}
if !["ROWS", "COLUMNS"].contains(&self.dimension.as_str()) {
return Err("维度类型必须是ROWS或COLUMNS".to_string());
}
if self.start_index < 0 {
return Err("起始索引不能小于0".to_string());
}
if self.end_index < self.start_index {
return Err("结束索引不能小于起始索引".to_string());
}
let count = self.end_index - self.start_index;
if count > 5000 {
return Err("插入行列数量不能超过5000".to_string());
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct InsertDimensionResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub spreadsheet_id: Option<String>,
}
impl ApiResponseTrait for InsertDimensionResponse {
fn data_format() -> ResponseFormat {
ResponseFormat::Data
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DeleteDimensionRequest {
pub spreadsheet_token: String,
pub dimension: String,
pub start_index: i32,
pub end_index: i32,
}
impl DeleteDimensionRequest {
pub fn validate(&self) -> Result<(), String> {
if self.spreadsheet_token.trim().is_empty() {
return Err("电子表格token不能为空".to_string());
}
if !["ROWS", "COLUMNS"].contains(&self.dimension.as_str()) {
return Err("维度类型必须是ROWS或COLUMNS".to_string());
}
if self.start_index < 0 {
return Err("起始索引不能小于0".to_string());
}
if self.end_index < self.start_index {
return Err("结束索引不能小于起始索引".to_string());
}
let count = self.end_index - self.start_index;
if count > 5000 {
return Err("删除行列数量不能超过5000".to_string());
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct DeleteDimensionResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub spreadsheet_id: Option<String>,
}
impl ApiResponseTrait for DeleteDimensionResponse {
fn data_format() -> ResponseFormat {
ResponseFormat::Data
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BatchUpdateSheetRequest {
pub spreadsheet_token: String,
pub requests: Vec<BatchUpdateRequest>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_format: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BatchUpdateRequest {
#[serde(rename = "type")]
pub request_type: String,
pub params: serde_json::Value,
}
impl BatchUpdateSheetRequest {
pub fn validate(&self) -> Result<(), String> {
if self.spreadsheet_token.trim().is_empty() {
return Err("电子表格token不能为空".to_string());
}
if self.requests.is_empty() {
return Err("更新操作列表不能为空".to_string());
}
if self.requests.len() > 100 {
return Err("更新操作数量不能超过100个".to_string());
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct BatchUpdateSheetResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub replies: Option<Vec<serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub spreadsheet_id: Option<String>,
}
impl ApiResponseTrait for BatchUpdateSheetResponse {
fn data_format() -> ResponseFormat {
ResponseFormat::Data
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GetSheetMetaRequest {
pub spreadsheet_token: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_format: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_data_validation: Option<bool>,
}
impl GetSheetMetaRequest {
pub fn validate(&self) -> Result<(), String> {
if self.spreadsheet_token.trim().is_empty() {
return Err("电子表格token不能为空".to_string());
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct GetSheetMetaResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub properties: Option<SheetProperties>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sheets: Option<Vec<SheetInfo>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub spreadsheet_id: Option<String>,
}
impl ApiResponseTrait for GetSheetMetaResponse {
fn data_format() -> ResponseFormat {
ResponseFormat::Data
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct SheetProperties {
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_time: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub modified_time: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub time_zone: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub locale: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct SheetInfo {
#[serde(skip_serializing_if = "Option::is_none")]
pub sheet_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sheet_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub grid_properties: Option<GridProperties>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct GridProperties {
#[serde(skip_serializing_if = "Option::is_none")]
pub row_count: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub column_count: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub show_grid_lines: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct ValueRange {
pub range: Option<String>,
pub major_dimension: Option<String>,
pub values: Option<Vec<Vec<serde_json::Value>>>,
}
impl Serialize for ValueRange {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("ValueRange", 3)?;
if let Some(ref range) = self.range {
state.serialize_field("range", range)?;
}
if let Some(ref dimension) = self.major_dimension {
state.serialize_field("majorDimension", dimension)?;
}
if let Some(ref values) = self.values {
state.serialize_field("values", values)?;
}
state.end()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SetCellStyleRequest {
pub spreadsheet_token: String,
pub range: String,
pub style: CellStyle,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct CellStyle {
#[serde(skip_serializing_if = "Option::is_none")]
pub background_color: Option<Color>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text_color: Option<Color>,
#[serde(skip_serializing_if = "Option::is_none")]
pub font_size: Option<i32>,
#[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 underline: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub horizontal_alignment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vertical_alignment: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct Color {
#[serde(skip_serializing_if = "Option::is_none")]
pub red: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub green: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub blue: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub alpha: Option<f32>,
}
impl SetCellStyleRequest {
pub fn validate(&self) -> Result<(), String> {
if self.spreadsheet_token.trim().is_empty() {
return Err("电子表格token不能为空".to_string());
}
if self.range.trim().is_empty() {
return Err("设置范围不能为空".to_string());
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct SetCellStyleResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub spreadsheet_id: Option<String>,
}
impl ApiResponseTrait for SetCellStyleResponse {
fn data_format() -> ResponseFormat {
ResponseFormat::Data
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json;
#[test]
fn test_serialization_roundtrip() {
let json = r#"{"test": "value"}"#;
assert!(serde_json::from_str::<serde_json::Value>(json).is_ok());
}
#[test]
fn test_deserialization_from_json() {
let json = r#"{"field": "data"}"#;
let value: serde_json::Value = serde_json::from_str(json).unwrap();
assert_eq!(value["field"], "data");
}
#[test]
fn test_read_single_range_request_validation() {
let valid_request = ReadSingleRangeRequest {
spreadsheet_token: "token123".to_string(),
range: "Sheet1!A1:C10".to_string(),
include_format: Some(true),
value_render_option: Some("FORMATTED_VALUE".to_string()),
};
assert!(valid_request.validate().is_ok());
let empty_token_request = ReadSingleRangeRequest {
spreadsheet_token: "".to_string(),
range: "Sheet1!A1:C10".to_string(),
include_format: None,
value_render_option: None,
};
assert!(empty_token_request.validate().is_err());
let empty_range_request = ReadSingleRangeRequest {
spreadsheet_token: "token123".to_string(),
range: "".to_string(),
include_format: None,
value_render_option: None,
};
assert!(empty_range_request.validate().is_err());
}
#[test]
fn test_read_single_range_response_deserialization() {
let json = r#"{
"valueRange": {
"range": "Sheet1!A1:C10",
"values": [["1", "2", "3"], ["4", "5", "6"]]
},
"spreadsheetId": "spreadsheet_id_123"
}"#;
let response: ReadSingleRangeResponse = serde_json::from_str(json).unwrap();
assert!(response.value_range.is_some());
assert_eq!(
response.spreadsheet_id,
Some("spreadsheet_id_123".to_string())
);
}
}