Skip to main content

anytype_rpc/
views.rs

1//! Helpers for working with dataview-based views.
2
3use std::collections::HashMap;
4use std::convert::TryFrom;
5
6use prost_types::value::Kind;
7use tonic::Request;
8use tonic::transport::Channel;
9
10use crate::anytype::ClientCommandsClient;
11use crate::anytype::rpc::object::show::Request as ObjectShowRequest;
12use crate::auth::with_token;
13use crate::error::AuthError;
14pub use crate::error::ViewError;
15use crate::model;
16use crate::model::block::ContentValue;
17use crate::model::block::content::Dataview as BlockDataview;
18use crate::model::block::content::dataview;
19
20/// Column metadata for a grid (table) view.
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct GridViewColumn {
23    pub relation_key: String,
24    pub name: String,
25    pub format: Option<model::RelationFormat>,
26    pub formula: dataview::relation::FormulaType,
27    pub is_visible: bool,
28    pub width: i32,
29}
30
31/// Grid (table) view metadata and columns.
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct GridViewInfo {
34    pub block_id: String,
35    pub view_id: String,
36    pub view_name: String,
37    pub columns: Vec<GridViewColumn>,
38}
39
40/// Fetch table/list view column metadata for a type object.
41pub async fn fetch_grid_view_columns(
42    client: &mut ClientCommandsClient<Channel>,
43    token: &str,
44    space_id: &str,
45    type_id: &str,
46    view_id: &str,
47) -> Result<GridViewInfo, ViewError> {
48    let request = ObjectShowRequest {
49        object_id: type_id.to_string(),
50        space_id: space_id.to_string(),
51        include_relations_as_dependent_objects: true,
52        ..Default::default()
53    };
54    let request = with_token(Request::new(request), token).map_err(|err: AuthError| {
55        ViewError::ApiResponse {
56            code: 0,
57            description: err.to_string(),
58        }
59    })?;
60
61    let response = client.object_show(request).await?.into_inner();
62    if let Some(error) = response.error
63        && error.code != 0
64    {
65        return Err(ViewError::ApiResponse {
66            code: error.code,
67            description: error.description,
68        });
69    }
70
71    let object_view = response.object_view.ok_or(ViewError::MissingObjectView)?;
72    let relation_names = relation_name_index(&object_view.details);
73
74    let (block_id, dataview) = find_dataview_block(&object_view.blocks, view_id)?;
75    let view = dataview
76        .views
77        .iter()
78        .find(|view| view.id == view_id)
79        .ok_or_else(|| ViewError::MissingView {
80            view_id: view_id.to_string(),
81        })?;
82
83    let view_type =
84        dataview::view::Type::try_from(view.r#type).unwrap_or(dataview::view::Type::Table);
85    if view_type != dataview::view::Type::Table && view_type != dataview::view::Type::List {
86        return Err(ViewError::NotSupportedView {
87            view_id: view_id.to_string(),
88            actual: view.r#type,
89        });
90    }
91
92    let relation_formats = relation_format_index(&dataview);
93    let columns = view
94        .relations
95        .iter()
96        .map(|relation| {
97            let formula = dataview::relation::FormulaType::try_from(relation.formula)
98                .unwrap_or(dataview::relation::FormulaType::None);
99            let name = relation_names
100                .get(&relation.key)
101                .cloned()
102                .unwrap_or_else(|| relation.key.clone());
103            let format = relation_formats.get(&relation.key).cloned();
104
105            GridViewColumn {
106                relation_key: relation.key.clone(),
107                name,
108                format,
109                formula,
110                is_visible: relation.is_visible,
111                width: relation.width,
112            }
113        })
114        .collect::<Vec<_>>();
115
116    Ok(GridViewInfo {
117        block_id,
118        view_id: view.id.clone(),
119        view_name: view.name.clone(),
120        columns,
121    })
122}
123
124fn find_dataview_block(
125    blocks: &[model::Block],
126    view_id: &str,
127) -> Result<(String, BlockDataview), ViewError> {
128    for block in blocks {
129        if let Some(ContentValue::Dataview(dataview)) = block.content_value.as_ref()
130            && dataview.views.iter().any(|view| view.id == view_id)
131        {
132            return Ok((block.id.clone(), dataview.clone()));
133        }
134    }
135
136    Err(ViewError::MissingDataviewBlock {
137        view_id: view_id.to_string(),
138    })
139}
140
141fn relation_format_index(dataview: &BlockDataview) -> HashMap<String, model::RelationFormat> {
142    let mut map = HashMap::new();
143    for link in &dataview.relation_links {
144        if let Ok(format) = model::RelationFormat::try_from(link.format) {
145            map.insert(link.key.clone(), format);
146        }
147    }
148    map
149}
150
151fn relation_name_index(details: &[model::object_view::DetailsSet]) -> HashMap<String, String> {
152    let mut map = HashMap::new();
153    for detail_set in details {
154        let Some(details) = detail_set.details.as_ref() else {
155            continue;
156        };
157        let relation_key =
158            string_field(details, "relationKey").or_else(|| string_field(details, "key"));
159        let name = string_field(details, "name");
160        if let (Some(relation_key), Some(name)) = (relation_key, name) {
161            map.insert(relation_key, name);
162        }
163    }
164    map
165}
166
167fn string_field(details: &prost_types::Struct, key: &str) -> Option<String> {
168    details.fields.get(key).and_then(|value| match &value.kind {
169        Some(Kind::StringValue(value)) => Some(value.clone()),
170        _ => None,
171    })
172}