yuque 0.1.3

Yuque Client SDK in Rust
Documentation
use std::{fmt::Display};
use anyhow::Result;
use serde::Serialize;

use crate::{serializer::{DocSerializer, APIResponse}, yuque::Yuque};

#[derive(Debug)]
pub enum DocFormat {
    Markdown,
    Lake,
    Html,
}

impl Serialize for DocFormat {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(match self {
            DocFormat::Markdown => "markdown",
            DocFormat::Lake => "lake",
            DocFormat::Html => "html",
        })
    }
}

#[derive(Debug, Serialize, Default)]
pub struct CreateDocRequest {
    pub title: String,
    pub slug: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub format: Option<DocFormat>,
    pub body: String,
}

#[derive(Debug, Serialize, Default)]
pub struct UpdateDocRequest {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub slug: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub body: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub _force_asl: Option<u8>,
}

impl Yuque {
    pub async fn list_docs<T: Display>(&self, repo: T, offset: Option<u32>, limit: Option<u8>, optional_properties: Option<Vec<String>>) -> Result<Vec<DocSerializer>> {
        let endpoint = format!("/repos/{}/docs", repo);
        let api = Yuque::build_api(&endpoint, None);

        let mut query = Vec::new();
        if let Some(offset) = offset {
            query.push(("offset", offset.to_string()));
        }
        if let Some(limit) = limit {
            query.push(("limit", limit.to_string()));
        }
        if let Some(optional_properties) = &optional_properties {
            let props = optional_properties.join(",");
            query.push(("optional_properties", props));
        }

        let resp = self.client.get(api).query(&query).send().await?;
        if resp.status().is_success() {
            let resp = resp.json::<APIResponse<Vec<DocSerializer>>>().await?;
            Ok(resp.data)
        } else {
            Err(anyhow::anyhow!("{} {}", resp.status(), resp.text().await?))
        }
    }

    pub async fn get_doc<T: Display>(&self, repo: T, doc: T, raw: Option<bool>) -> Result<DocSerializer> {
        let endpoint = format!("/repos/{}/docs/{}", repo, doc);
        let api = Yuque::build_api(&endpoint, None);

        let mut query = Vec::new();
        if let Some(raw) = raw {
            if raw {
                query.push(("raw", 1));
            }
        }

        let resp = self.client.get(api).query(&query).send().await?;
        if resp.status().is_success() {
            let resp = resp.json::<APIResponse<DocSerializer>>().await?;
            Ok(resp.data)
        } else {
            Err(anyhow::anyhow!("{} {}", resp.status(), resp.text().await?))
        }
    }

    pub async fn create_doc<T: Display>(&self, repo: T, req: &CreateDocRequest) -> Result<DocSerializer> {
        let endpoint = format!("/repos/{}/docs", repo);
        let api = Yuque::build_api(&endpoint, None);
        let resp = self.client.post(api).json(req).send().await?;
        if resp.status().is_success() {
            let resp = resp.json::<APIResponse<DocSerializer>>().await?;
            Ok(resp.data)
        } else {
            Err(anyhow::anyhow!("{} {}", resp.status(), resp.text().await?))
        }
    }

    pub async fn delete_doc<T: Display>(&self, repo: T, doc_id: u32) -> Result<()> {
        let endpoint = format!("/repos/{}/docs/{}", repo, doc_id);
        let api = Yuque::build_api(&endpoint, None);
        let resp = self.client.delete(api).send().await?;
        if resp.status().is_success() {
            Ok(())
        } else {
            Err(anyhow::anyhow!("{} {}", resp.status(), resp.text().await?))
        }
    }

    pub async fn update_doc<T: Display>(&self, repo: T, doc_id: u32, req: &mut UpdateDocRequest) -> Result<DocSerializer> {
        let endpoint = format!("/repos/{}/docs/{}", repo, doc_id);
        let api = Yuque::build_api(&endpoint, None);
        if let Some(force_asl) = req._force_asl {
            if force_asl > 0 {
                req._force_asl = Some(1);
            } else {
                req._force_asl = None;
            }
        }
        let resp = self.client.put(api).json(req).send().await?;
        if resp.status().is_success() {
            let resp = resp.json::<APIResponse<DocSerializer>>().await?;
            Ok(resp.data)
        } else {
            Err(anyhow::anyhow!("{} {}", resp.status(), resp.text().await?))
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::utils::gen_rand_str;
    use crate::yuque::Yuque;
    use crate::doc::{CreateDocRequest, UpdateDocRequest};

    #[tokio::test]
    async fn test_list_repos() {
        let token = option_env!("YUQUE_TOKEN").unwrap_or_default();
        let yuque = Yuque::new(token).unwrap();
        let repo = "k8scat/programming";
        let repos = yuque.list_docs(repo, None, None, Some(vec!["hits".to_string()])).await.unwrap();
        assert_ne!(repos.len(), 0);
        assert_ne!(repos[0].hits, None);
    }

    #[tokio::test]
    async fn test_get_repo() {
        let token = option_env!("YUQUE_TOKEN").unwrap_or_default();
        let yuque = Yuque::new(token).unwrap();
        let repo_slug = "yuque";
        let repo = "k8scat/opensource";
        let repo = yuque.get_doc(repo, repo_slug, Some(true)).await.unwrap();
        assert_eq!(repo.slug, repo_slug);
    }

    #[tokio::test]
    async fn test_create_repo() {
        let token = option_env!("YUQUE_TOKEN").unwrap_or_default();
        let yuque = Yuque::new(token).unwrap();
        let repo = "k8scat/opensource";
        let rand_str = gen_rand_str();
        let content = "# Hello World";
        let req = CreateDocRequest {
            title: rand_str.clone(),
            slug: rand_str.clone(),
            body: content.to_string(),
            ..CreateDocRequest::default()
        };
        let doc = yuque.create_doc(repo, &req).await.unwrap();
        assert_eq!(doc.title, rand_str);
        assert_eq!(doc.slug, rand_str);
        assert_ne!(doc.body, None);
        if let Some(body) = &doc.body {
            assert_eq!(body, content);
        }
        
        yuque.delete_doc(repo, doc.object.id).await.unwrap();
    }

    #[tokio::test]
    async fn test_update_doc() {
        let token = option_env!("YUQUE_TOKEN").unwrap_or_default();
        let yuque = Yuque::new(token).unwrap();
        let repo = "k8scat/opensource";
        let rand_str = gen_rand_str();
        let content = "# Hello World";
        let req = CreateDocRequest {
            title: rand_str.clone(),
            slug: rand_str.clone(),
            body: content.to_string(),
            ..CreateDocRequest::default()
        };
        let doc = yuque.create_doc(repo, &req).await.unwrap();
        assert_eq!(doc.title, rand_str);
        assert_eq!(doc.slug, rand_str);
        assert_ne!(doc.body, None);
        if let Some(body) = &doc.body {
            assert_eq!(body, content);
        }

        let rand_str = gen_rand_str();
        let content = "# Updated";
        let mut req = UpdateDocRequest {
            title: Some(rand_str.clone()),
            slug: Some(rand_str.clone()),
            body: Some(content.to_string()),
            ..UpdateDocRequest::default()
        };
        let doc = yuque.update_doc(repo, doc.object.id, &mut req).await.unwrap();
        assert_eq!(doc.title, rand_str);
        assert_eq!(doc.slug, rand_str);
        assert_ne!(doc.body, None);
        if let Some(body) = &doc.body {
            assert_eq!(body, content);
        }
        
        yuque.delete_doc(repo, doc.object.id).await.unwrap();
    }
}