nology_api/
lib.rs

1use reqwest::{Client, Url};
2use serde::{Serialize, Deserialize, de::DeserializeOwned};
3use thiserror::Error;
4
5pub mod album;
6pub mod browse;
7mod entity;
8
9pub use entity::*;
10
11#[cfg(test)]
12mod test;
13
14#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
15pub struct Response<T> {
16    pub success: bool,
17    #[serde(flatten)]
18    pub body: Body<T>,
19}
20
21#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
22#[serde(rename_all="lowercase")]
23pub enum Body<T> {
24    Error(ErrorResponse),
25    Data (T),
26}
27
28impl<T> Body<T> {
29    pub fn as_result(self) -> Result<T, ErrorResponse> {
30        match self {
31            Body::Error(error) => Err(error),
32            Body::Data(data) => Ok(data),
33        }
34    }
35}
36
37#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)]
38#[error("Syno returned error with code: {code}")]
39pub struct ErrorResponse {
40    code: u32,
41}
42
43pub trait Request {
44    type Response: DeserializeOwned;
45    fn query(&self) -> String;
46}
47
48#[derive(Debug, Clone)]
49pub struct LoginRequest {
50    pub account: String,
51    pub passwd: String,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct LoginResponse {
56    pub did: String,
57    pub sid: String
58}
59
60impl Request for LoginRequest {
61    type Response = LoginResponse;
62    fn query(&self) -> String {
63        format!("api=SYNO.API.Auth&version=3&method=login&account={}&passwd={}", self.account, self.passwd)
64    }
65}
66
67#[derive(Error, Debug)]
68pub enum SynoError {
69    #[error("Syno error: {0:?}")]
70    Syno(#[from] ErrorResponse),
71    #[error("Syno error: {0}")]
72    Reqwest(#[from] reqwest::Error),
73}
74
75
76type SynoResult<T> = Result<T, SynoError>;
77
78pub struct SynoService {
79    client: Client,
80    api_url: Url,
81    sid: String,
82}
83
84impl SynoService {
85    /**
86     * api_url - for example, http://192.168.1.199:5000/webapi/entry.cgi
87     */
88    pub async fn login(client: Client, root_url: impl reqwest::IntoUrl, login_request: LoginRequest) -> SynoResult<SynoService> {
89        let api_url = root_url.into_url()?;
90        let response = client.post(api_url.clone())
91            .body(login_request.query())
92            .send().await?
93            .json::<Response<LoginResponse>>().await?;
94        let sid = response.body.as_result()?.sid;
95        Ok(Self { client, api_url, sid})
96    }
97
98    pub async fn request<R: Request>(&self, request: R) -> SynoResult<R::Response> {
99        let query = format!("{}&_sid={}", request.query(), self.sid);
100        log::trace!("query: {query}");
101        let response = self.client
102            .post(self.api_url.as_str())
103            .body(query).send().await?
104            .json::<Response<R::Response>>().await?;
105        match response.body {
106            Body::Error(e) => Err(SynoError::Syno(e)),
107            Body::Data(d) => Ok(d),
108        }
109    }
110}