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 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}