Skip to main content

nextcloud_ocs/client/
ocs_client.rs

1use nextcloud_sdk_core::{ApiPath, AuthProvider, Error, HttpMethod, Transport};
2use serde::Serialize;
3use serde::de::DeserializeOwned;
4
5use super::path::{require_ocs, with_query};
6use super::request::build_request;
7use super::response::decode_response;
8use crate::query::Query;
9
10/// Executes OCS calls and unwraps their envelope.
11///
12/// Generic over the [`Transport`] that carries bytes and the [`AuthProvider`]
13/// that applies credentials, so a client is built from any transport
14/// implementation without virtual dispatch. The transport already knows the
15/// instance URL and turns an [`ApiPath`] into an absolute URL, so the client
16/// holds no base URL of its own.
17///
18/// All OCS calls follow the Nextcloud Open Collaboration Services (OCS)
19/// specification (<https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/>):
20/// - Every request sets `OCS-APIRequest: true` and `Accept: application/json`.
21/// - Mutation requests (`POST`, `PUT`) serialize request bodies as JSON and set
22///   `Content-Type: application/json`.
23/// - Responses are wrapped in an [`OcsEnvelope`](crate::OcsEnvelope) carrying
24///   a `meta.statuscode` verdict (`100` or `200` for success; failure codes such
25///   as `101`, `102`, `997`, `998`, or `999` map to [`Error::Status`]).
26/// - Calling endpoints with unit response `()` succeeds on valid envelopes even
27///   when the response payload is empty (e.g. `[]` or `{}`), or when the caller
28///   only cares about verdict success.
29#[derive(Clone, Debug)]
30pub struct OcsClient<T, A> {
31    transport: T,
32    auth: A,
33}
34
35impl<T: Transport, A: AuthProvider> OcsClient<T, A> {
36    /// Pairs a transport with the credentials applied to every call.
37    pub const fn new(transport: T, auth: A) -> Self {
38        Self { transport, auth }
39    }
40
41    /// The transport this client sends through.
42    pub const fn transport(&self) -> &T {
43        &self.transport
44    }
45
46    /// The credential provider applied to every call.
47    pub const fn auth(&self) -> &A {
48        &self.auth
49    }
50
51    /// Performs a `GET` against an OCS path and returns the envelope payload.
52    ///
53    /// The call sets `Accept: application/json` and `OCS-APIRequest: true` and
54    /// applies credentials. Three cases fail: a path outside `/ocs/v2.php`, a
55    /// response status or an OCS verdict the caller cannot accept, and a body
56    /// that is not an envelope of the requested type. A failure keeps the raw
57    /// body, so the OCS code (`997` app not installed, `998` not allowed,
58    /// `999` invalid) and its message stay reachable through [`Error::body`].
59    ///
60    /// See Nextcloud OCS API documentation:
61    /// <https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/>
62    pub async fn get<Res: DeserializeOwned>(
63        &self,
64        path: ApiPath,
65        query: Query,
66    ) -> Result<Res, Error> {
67        let path = with_query(&require_ocs(path)?, &query);
68        let request = build_request(HttpMethod::Get, path.clone(), None::<&()>, &self.auth)?;
69        let response = self.transport.execute(request).await?;
70        decode_response(HttpMethod::Get, path, &response)
71    }
72
73    /// Performs a `POST` against an OCS path with a serialised request body.
74    ///
75    /// The call sets `Accept: application/json`, `Content-Type: application/json`,
76    /// and `OCS-APIRequest: true`, and applies credentials.
77    ///
78    /// When `Res` is `()`, calls succeed if the server returns an OCS envelope
79    /// reporting success (`statuscode` 100 or 200), ignoring empty or present payload data.
80    ///
81    /// See Nextcloud OCS API documentation:
82    /// <https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/>
83    pub async fn post<Req: Serialize, Res: DeserializeOwned>(
84        &self,
85        path: ApiPath,
86        body: &Req,
87    ) -> Result<Res, Error> {
88        self.post_with_query(path, Query::new(), body).await
89    }
90
91    /// Performs a `POST` against an OCS path with query parameters and a
92    /// serialised request body.
93    ///
94    /// The call sets `Accept: application/json`, `Content-Type: application/json`,
95    /// and `OCS-APIRequest: true`, and applies credentials.
96    ///
97    /// When `Res` is `()`, calls succeed if the server returns an OCS envelope
98    /// reporting success (`statuscode` 100 or 200), ignoring empty or present payload data.
99    ///
100    /// See Nextcloud OCS API documentation:
101    /// <https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/>
102    pub async fn post_with_query<Req: Serialize, Res: DeserializeOwned>(
103        &self,
104        path: ApiPath,
105        query: Query,
106        body: &Req,
107    ) -> Result<Res, Error> {
108        let path = with_query(&require_ocs(path)?, &query);
109        let request = build_request(HttpMethod::Post, path.clone(), Some(body), &self.auth)?;
110        let response = self.transport.execute(request).await?;
111        decode_response(HttpMethod::Post, path, &response)
112    }
113
114    /// Performs a `PUT` against an OCS path with a serialised request body.
115    ///
116    /// The call sets `Accept: application/json`, `Content-Type: application/json`,
117    /// and `OCS-APIRequest: true`, and applies credentials.
118    ///
119    /// When `Res` is `()`, calls succeed if the server returns an OCS envelope
120    /// reporting success (`statuscode` 100 or 200), ignoring empty or present payload data.
121    ///
122    /// See Nextcloud OCS API documentation:
123    /// <https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/>
124    pub async fn put<Req: Serialize, Res: DeserializeOwned>(
125        &self,
126        path: ApiPath,
127        body: &Req,
128    ) -> Result<Res, Error> {
129        self.put_with_query(path, Query::new(), body).await
130    }
131
132    /// Performs a `PUT` against an OCS path with query parameters and a
133    /// serialised request body.
134    ///
135    /// The call sets `Accept: application/json`, `Content-Type: application/json`,
136    /// and `OCS-APIRequest: true`, and applies credentials.
137    ///
138    /// When `Res` is `()`, calls succeed if the server returns an OCS envelope
139    /// reporting success (`statuscode` 100 or 200), ignoring empty or present payload data.
140    ///
141    /// See Nextcloud OCS API documentation:
142    /// <https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/>
143    pub async fn put_with_query<Req: Serialize, Res: DeserializeOwned>(
144        &self,
145        path: ApiPath,
146        query: Query,
147        body: &Req,
148    ) -> Result<Res, Error> {
149        let path = with_query(&require_ocs(path)?, &query);
150        let request = build_request(HttpMethod::Put, path.clone(), Some(body), &self.auth)?;
151        let response = self.transport.execute(request).await?;
152        decode_response(HttpMethod::Put, path, &response)
153    }
154
155    /// Performs a `DELETE` against an OCS path.
156    ///
157    /// The call sets `Accept: application/json` and `OCS-APIRequest: true`
158    /// and applies credentials.
159    ///
160    /// When `Res` is `()`, calls succeed if the server returns an OCS envelope
161    /// reporting success (`statuscode` 100 or 200), ignoring empty or present payload data.
162    ///
163    /// See Nextcloud OCS API documentation:
164    /// <https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/>
165    pub async fn delete<Res: DeserializeOwned>(&self, path: ApiPath) -> Result<Res, Error> {
166        self.delete_with_query(path, Query::new()).await
167    }
168
169    /// Performs a `DELETE` against an OCS path with query parameters.
170    ///
171    /// The call sets `Accept: application/json` and `OCS-APIRequest: true`
172    /// and applies credentials.
173    ///
174    /// When `Res` is `()`, calls succeed if the server returns an OCS envelope
175    /// reporting success (`statuscode` 100 or 200), ignoring empty or present payload data.
176    ///
177    /// See Nextcloud OCS API documentation:
178    /// <https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/>
179    pub async fn delete_with_query<Res: DeserializeOwned>(
180        &self,
181        path: ApiPath,
182        query: Query,
183    ) -> Result<Res, Error> {
184        let path = with_query(&require_ocs(path)?, &query);
185        let request = build_request(HttpMethod::Delete, path.clone(), None::<&()>, &self.auth)?;
186        let response = self.transport.execute(request).await?;
187        decode_response(HttpMethod::Delete, path, &response)
188    }
189}