use std::sync::Arc;
use serde::{Deserialize, Serialize};
use crate::{
Result,
cache::AnytypeCache,
client::AnytypeClient,
filters::Query,
http_client::{GetPaged, HttpClient},
prelude::*,
verify::{VerifyConfig, VerifyPolicy, resolve_verify, verify_available},
};
#[derive(
Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Default, strum::Display, strum::EnumString,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum SpaceModel {
#[default]
Space,
Chat,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Space {
pub id: String,
pub name: String,
pub object: SpaceModel,
pub description: Option<String>,
pub icon: Option<Icon>,
pub gateway_url: Option<String>,
pub network_id: Option<String>,
}
impl Space {
pub fn is_chat(&self) -> bool {
self.object == SpaceModel::Chat
}
pub fn is_space(&self) -> bool {
self.object == SpaceModel::Space
}
}
#[derive(Deserialize)]
struct SpaceResponse {
space: Space,
}
#[derive(Debug, Serialize)]
struct CreateSpaceRequestBody {
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
}
#[derive(Debug, Serialize, Default)]
struct UpdateSpaceRequestBody {
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
}
#[derive(Debug)]
pub struct SpaceRequest {
client: Arc<HttpClient>,
space_id: String,
cache: Arc<AnytypeCache>,
}
impl SpaceRequest {
pub(crate) fn new(
client: Arc<HttpClient>,
space_id: impl Into<String>,
cache: Arc<AnytypeCache>,
) -> Self {
Self {
client,
space_id: space_id.into(),
cache,
}
}
pub async fn get(self) -> Result<Space> {
if self.cache.is_enabled() {
if let Some(space) = self.cache.get_space(&self.space_id) {
return Ok(space);
}
if !self.cache.has_spaces() {
let query = Query::default().add_filters(&[]);
let spaces = self
.client
.get_request_paged("/v1/spaces", query)
.await?
.collect_all()
.await?;
self.cache.set_spaces(spaces);
if let Some(space) = self.cache.get_space(&self.space_id) {
return Ok(space);
}
}
return Err(AnytypeError::NotFound {
obj_type: "Space".into(),
key: self.space_id.clone(),
});
}
let response: SpaceResponse = self
.client
.get_request(&format!("/v1/spaces/{}", self.space_id), Default::default())
.await?;
Ok(response.space)
}
}
#[derive(Debug)]
pub struct NewSpaceRequest {
client: Arc<HttpClient>,
name: String,
description: Option<String>,
verify_policy: VerifyPolicy,
verify_config: Option<VerifyConfig>,
}
impl NewSpaceRequest {
pub(crate) fn new(
client: Arc<HttpClient>,
name: impl Into<String>,
verify_config: Option<VerifyConfig>,
) -> Self {
Self {
client,
name: name.into(),
description: None,
verify_policy: VerifyPolicy::Default,
verify_config,
}
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn ensure_available(mut self) -> Self {
self.verify_policy = VerifyPolicy::Enabled;
self
}
pub fn ensure_available_with(mut self, config: VerifyConfig) -> Self {
self.verify_policy = VerifyPolicy::Enabled;
self.verify_config = Some(config);
self
}
pub fn no_verify(mut self) -> Self {
self.verify_policy = VerifyPolicy::Disabled;
self
}
pub async fn create(self) -> Result<Space> {
let request_body = CreateSpaceRequestBody {
name: self.name,
description: self.description,
};
let response: SpaceResponse = self
.client
.post_request("/v1/spaces", &request_body, Default::default())
.await?;
let space = response.space;
if let Some(config) = resolve_verify(self.verify_policy, &self.verify_config) {
return verify_available(&config, "Space", &space.id, || async {
let response: SpaceResponse = self
.client
.get_request(&format!("/v1/spaces/{}", space.id), Default::default())
.await?;
Ok(response.space)
})
.await;
}
Ok(space)
}
}
#[derive(Debug)]
pub struct UpdateSpaceRequest {
client: Arc<HttpClient>,
space_id: String,
name: Option<String>,
description: Option<String>,
verify_policy: VerifyPolicy,
verify_config: Option<VerifyConfig>,
}
impl UpdateSpaceRequest {
pub(crate) fn new(
client: Arc<HttpClient>,
space_id: impl Into<String>,
verify_config: Option<VerifyConfig>,
) -> Self {
Self {
client,
space_id: space_id.into(),
name: None,
description: None,
verify_policy: VerifyPolicy::Default,
verify_config,
}
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn ensure_available(mut self) -> Self {
self.verify_policy = VerifyPolicy::Enabled;
self
}
pub fn ensure_available_with(mut self, config: VerifyConfig) -> Self {
self.verify_policy = VerifyPolicy::Enabled;
self.verify_config = Some(config);
self
}
pub fn no_verify(mut self) -> Self {
self.verify_policy = VerifyPolicy::Disabled;
self
}
pub async fn update(self) -> Result<Space> {
if self.name.is_none() && self.description.is_none() {
return Err(AnytypeError::Validation {
message:
"update_space: must set at least one field to update (name or description)"
.to_string(),
});
}
let request_body = UpdateSpaceRequestBody {
name: self.name,
description: self.description,
};
let response: SpaceResponse = self
.client
.patch_request(&format!("/v1/spaces/{}", self.space_id), &request_body)
.await?;
let space = response.space;
if let Some(config) = resolve_verify(self.verify_policy, &self.verify_config) {
return verify_available(&config, "Space", &space.id, || async {
let response: SpaceResponse = self
.client
.get_request(&format!("/v1/spaces/{}", space.id), Default::default())
.await?;
Ok(response.space)
})
.await;
}
Ok(space)
}
}
#[derive(Debug)]
pub struct ListSpacesRequest {
client: Arc<HttpClient>,
limit: Option<usize>,
offset: Option<usize>,
filters: Vec<Filter>,
cache: Arc<AnytypeCache>,
}
impl ListSpacesRequest {
pub(crate) fn new(client: Arc<HttpClient>, cache: Arc<AnytypeCache>) -> Self {
Self {
client,
limit: None,
offset: None,
filters: Vec::new(),
cache,
}
}
pub fn limit(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
pub fn offset(mut self, offset: usize) -> Self {
self.offset = Some(offset);
self
}
pub fn filter(mut self, filter: Filter) -> Self {
self.filters.push(filter);
self
}
pub fn filters(mut self, filters: impl IntoIterator<Item = Filter>) -> Self {
self.filters.extend(filters);
self
}
pub async fn list(self) -> Result<PagedResult<Space>> {
if self.cache.is_enabled()
&& self.limit.is_none()
&& self.offset.unwrap_or_default() == 0
&& self.filters.is_empty()
{
if let Some(spaces) = self.cache.spaces() {
return Ok(PagedResult::from_items(spaces));
}
let query = Query::default().add_filters(&[]);
let spaces = self
.client
.get_request_paged("/v1/spaces", query)
.await?
.collect_all()
.await?;
self.cache.set_spaces(spaces.clone());
return Ok(PagedResult::from_items(spaces));
}
let query = Query::default()
.set_limit_opt(&self.limit)
.set_offset_opt(&self.offset)
.add_filters(&self.filters);
self.client.get_request_paged("/v1/spaces", query).await
}
}
impl AnytypeClient {
pub fn space(&self, space_id: impl Into<String>) -> SpaceRequest {
SpaceRequest::new(self.client.clone(), space_id, self.cache.clone())
}
pub fn new_space(&self, name: impl Into<String>) -> NewSpaceRequest {
NewSpaceRequest::new(self.client.clone(), name, self.config.verify.clone())
}
pub fn update_space(&self, space_id: impl Into<String>) -> UpdateSpaceRequest {
UpdateSpaceRequest::new(self.client.clone(), space_id, self.config.verify.clone())
}
pub fn spaces(&self) -> ListSpacesRequest {
ListSpacesRequest::new(self.client.clone(), self.cache.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_space_model_default() {
let model: SpaceModel = Default::default();
assert_eq!(model, SpaceModel::Space);
}
#[test]
fn test_space_model_display() {
assert_eq!(SpaceModel::Space.to_string(), "space");
assert_eq!(SpaceModel::Chat.to_string(), "chat");
}
#[test]
fn test_space_model_from_string() {
use std::str::FromStr;
assert_eq!(SpaceModel::from_str("space").unwrap(), SpaceModel::Space);
assert_eq!(SpaceModel::from_str("chat").unwrap(), SpaceModel::Chat);
}
#[test]
fn test_create_space_request_body_serialization() {
let body = CreateSpaceRequestBody {
name: "Test Space".to_string(),
description: Some("A test space".to_string()),
};
let json = serde_json::to_string(&body).unwrap();
assert!(json.contains("\"name\":\"Test Space\""));
assert!(json.contains("\"description\":\"A test space\""));
}
#[test]
fn test_create_space_request_body_no_description() {
let body = CreateSpaceRequestBody {
name: "Test Space".to_string(),
description: None,
};
let json = serde_json::to_string(&body).unwrap();
assert!(json.contains("\"name\":\"Test Space\""));
assert!(!json.contains("description"));
}
#[test]
fn test_update_space_request_body_empty() {
let body = UpdateSpaceRequestBody::default();
let json = serde_json::to_string(&body).unwrap();
assert_eq!(json, "{}");
}
#[test]
fn test_space_is_chat() {
let space = Space {
id: "test".to_string(),
name: "Test".to_string(),
object: SpaceModel::Chat,
description: None,
icon: None,
gateway_url: None,
network_id: None,
};
assert!(space.is_chat());
assert!(!space.is_space());
}
#[test]
fn test_space_is_space() {
let space = Space {
id: "test".to_string(),
name: "Test".to_string(),
object: SpaceModel::Space,
description: None,
icon: None,
gateway_url: None,
network_id: None,
};
assert!(space.is_space());
assert!(!space.is_chat());
}
}