Skip to main content

wenlan_types/
communities.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Versioned M4 community read and review surfaces.
3
4use serde::{Deserialize, Serialize};
5
6pub const COMMUNITY_READ_SCHEMA_VERSION: &str = "community-read-v1";
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(tag = "kind", rename_all = "snake_case")]
10pub enum CommunityReadScope {
11    Global,
12    Space { name: String },
13    Uncategorized,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub struct CommunitySummary {
18    pub community_id: String,
19    pub space: String,
20    pub display_name: Option<String>,
21    pub member_count: u64,
22    pub published_generation: i64,
23    pub algo_version: String,
24    pub projection_version: String,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28pub struct CommunityMember {
29    pub space: String,
30    pub node_id: String,
31    pub node_kind: String,
32    pub community_id: String,
33    pub published_generation: i64,
34    pub attachment: String,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "snake_case")]
39pub enum PageCommunityAssignmentState {
40    Assigned,
41    Held,
42    Dropped,
43}
44
45#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
46pub struct CommunityPageAssignment {
47    pub page_id: String,
48    pub space: String,
49    pub community_id: Option<String>,
50    pub state: PageCommunityAssignmentState,
51    pub score: f64,
52    pub page_version: i64,
53    pub community_published_generation: i64,
54    pub route_version: String,
55    pub updated_at: i64,
56}
57
58#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
59pub struct CommunityListResponse {
60    pub schema_version: String,
61    pub scope: CommunityReadScope,
62    pub communities: Vec<CommunitySummary>,
63    pub next_cursor: Option<String>,
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67pub struct CommunityMemberCursor {
68    pub space: String,
69    pub node_id: String,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73pub struct CommunityMembersResponse {
74    pub schema_version: String,
75    pub scope: CommunityReadScope,
76    pub members: Vec<CommunityMember>,
77    pub next_cursor: Option<CommunityMemberCursor>,
78}
79
80#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
81pub struct CommunityPageAssignmentsResponse {
82    pub schema_version: String,
83    pub scope: CommunityReadScope,
84    pub page_assignments: Vec<CommunityPageAssignment>,
85    pub next_cursor: Option<String>,
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
89#[serde(rename_all = "snake_case")]
90pub enum CommunityProposalAction {
91    CommunitySplit,
92    CommunityMerge,
93    CommunityRename,
94}
95
96#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
97#[serde(tag = "action", rename_all = "snake_case", deny_unknown_fields)]
98pub enum CommunityProposalPayload {
99    CommunitySplit {
100        space: String,
101        source_generation: i64,
102        subject_id: String,
103        old_community_ids: Vec<String>,
104        new_community_ids: Vec<String>,
105        proposed_display_name: Option<String>,
106    },
107    CommunityMerge {
108        space: String,
109        source_generation: i64,
110        subject_id: String,
111        old_community_ids: Vec<String>,
112        new_community_ids: Vec<String>,
113        proposed_display_name: Option<String>,
114    },
115    CommunityRename {
116        space: String,
117        source_generation: i64,
118        subject_id: String,
119        proposed_display_name: String,
120    },
121}
122
123impl CommunityProposalPayload {
124    pub fn action(&self) -> CommunityProposalAction {
125        match self {
126            Self::CommunitySplit { .. } => CommunityProposalAction::CommunitySplit,
127            Self::CommunityMerge { .. } => CommunityProposalAction::CommunityMerge,
128            Self::CommunityRename { .. } => CommunityProposalAction::CommunityRename,
129        }
130    }
131
132    pub fn space(&self) -> &str {
133        match self {
134            Self::CommunitySplit { space, .. }
135            | Self::CommunityMerge { space, .. }
136            | Self::CommunityRename { space, .. } => space,
137        }
138    }
139
140    pub fn source_generation(&self) -> i64 {
141        match self {
142            Self::CommunitySplit {
143                source_generation, ..
144            }
145            | Self::CommunityMerge {
146                source_generation, ..
147            }
148            | Self::CommunityRename {
149                source_generation, ..
150            } => *source_generation,
151        }
152    }
153
154    pub fn subject_id(&self) -> &str {
155        match self {
156            Self::CommunitySplit { subject_id, .. }
157            | Self::CommunityMerge { subject_id, .. }
158            | Self::CommunityRename { subject_id, .. } => subject_id,
159        }
160    }
161
162    pub fn proposed_display_name(&self) -> Option<&str> {
163        match self {
164            Self::CommunitySplit {
165                proposed_display_name,
166                ..
167            }
168            | Self::CommunityMerge {
169                proposed_display_name,
170                ..
171            } => proposed_display_name.as_deref(),
172            Self::CommunityRename {
173                proposed_display_name,
174                ..
175            } => Some(proposed_display_name),
176        }
177    }
178}
179
180#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
181pub struct CommunityProposalSummary {
182    pub id: String,
183    pub action: CommunityProposalAction,
184    pub payload: CommunityProposalPayload,
185    pub created_at: String,
186}
187
188#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
189pub struct ListCommunityProposalsResponse {
190    pub proposals: Vec<CommunityProposalSummary>,
191}
192
193#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
194pub struct CommunityProposalAcceptResponse {
195    pub id: String,
196    pub action: CommunityProposalAction,
197    pub display_name_updated: bool,
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203
204    #[test]
205    fn community_wire_is_versioned_and_proposal_payloads_are_closed() {
206        let response = CommunityListResponse {
207            schema_version: COMMUNITY_READ_SCHEMA_VERSION.to_string(),
208            scope: CommunityReadScope::Space {
209                name: "work".to_string(),
210            },
211            communities: Vec::new(),
212            next_cursor: None,
213        };
214        assert_eq!(
215            serde_json::to_value(response).unwrap()["schema_version"],
216            COMMUNITY_READ_SCHEMA_VERSION
217        );
218
219        let unknown = serde_json::json!({
220            "action": "community_rename",
221            "space": "work",
222            "source_generation": 1,
223            "subject_id": "c1",
224            "proposed_display_name": "Work",
225            "unexpected": true
226        });
227        assert!(serde_json::from_value::<CommunityProposalPayload>(unknown).is_err());
228    }
229}