Skip to main content

cratestack_client_rust/client/
crud.rs

1use reqwest::Method;
2use serde::Serialize;
3use serde::de::DeserializeOwned;
4
5use crate::client::TypedResponse;
6use crate::client::core::CratestackClient;
7use crate::client::decode::{decode_typed_response, decode_typed_response_with_metadata};
8use crate::codec::HttpClientCodec;
9use crate::error::{ClientError, HeaderPair, QueryPair};
10
11impl<C> CratestackClient<C>
12where
13    C: HttpClientCodec,
14{
15    pub async fn get<Output>(
16        &self,
17        path: &str,
18        query: &[QueryPair<'_>],
19        headers: &[HeaderPair<'_>],
20    ) -> Result<Output, ClientError>
21    where
22        Output: DeserializeOwned,
23    {
24        let response = self
25            .request_raw(Method::GET, path, None, query, headers)
26            .await?;
27        decode_typed_response(&self.codec, &response)
28    }
29
30    /// Same request as [`Self::get`], but returns the status and
31    /// headers alongside the decoded body (issue #493) — the way a
32    /// caller reads the `ETag` off an `@version` model's `GET` before
33    /// sending it back as `If-Match` on `patch_with_response`/`patch`.
34    pub async fn get_with_response<Output>(
35        &self,
36        path: &str,
37        query: &[QueryPair<'_>],
38        headers: &[HeaderPair<'_>],
39    ) -> Result<TypedResponse<Output>, ClientError>
40    where
41        Output: DeserializeOwned,
42    {
43        let response = self
44            .request_raw(Method::GET, path, None, query, headers)
45            .await?;
46        decode_typed_response_with_metadata(&self.codec, &response)
47    }
48
49    pub async fn post<Input, Output>(
50        &self,
51        path: &str,
52        input: &Input,
53        headers: &[HeaderPair<'_>],
54    ) -> Result<Output, ClientError>
55    where
56        Input: Serialize,
57        Output: DeserializeOwned,
58    {
59        if self.compact.is_some() {
60            super::compact_identity::refuse_duplicates(headers)?;
61        }
62        let body = self.codec.encode(input)?;
63        let response = self
64            .request_raw(Method::POST, path, Some(body), &[], headers)
65            .await?;
66        decode_typed_response(&self.codec, &response)
67    }
68
69    /// Same request as [`Self::post`], but returns the status and
70    /// headers alongside the decoded body (issue #493) — e.g. to read
71    /// `Idempotency-Replayed` off a create sent through an
72    /// idempotency-key-aware server.
73    pub async fn post_with_response<Input, Output>(
74        &self,
75        path: &str,
76        input: &Input,
77        headers: &[HeaderPair<'_>],
78    ) -> Result<TypedResponse<Output>, ClientError>
79    where
80        Input: Serialize,
81        Output: DeserializeOwned,
82    {
83        if self.compact.is_some() {
84            super::compact_identity::refuse_duplicates(headers)?;
85        }
86        let body = self.codec.encode(input)?;
87        let response = self
88            .request_raw(Method::POST, path, Some(body), &[], headers)
89            .await?;
90        decode_typed_response_with_metadata(&self.codec, &response)
91    }
92
93    pub async fn patch<Input, Output>(
94        &self,
95        path: &str,
96        input: &Input,
97        headers: &[HeaderPair<'_>],
98    ) -> Result<Output, ClientError>
99    where
100        Input: Serialize,
101        Output: DeserializeOwned,
102    {
103        if self.compact.is_some() {
104            super::compact_identity::refuse_duplicates(headers)?;
105        }
106        let body = self.codec.encode(input)?;
107        let response = self
108            .request_raw(Method::PATCH, path, Some(body), &[], headers)
109            .await?;
110        decode_typed_response(&self.codec, &response)
111    }
112
113    /// Same request as [`Self::patch`], but returns the status and
114    /// headers alongside the decoded body (issue #493) — the response
115    /// carries the *new* `ETag` after a successful `@version` update,
116    /// which a caller chaining further updates needs to read back out.
117    pub async fn patch_with_response<Input, Output>(
118        &self,
119        path: &str,
120        input: &Input,
121        headers: &[HeaderPair<'_>],
122    ) -> Result<TypedResponse<Output>, ClientError>
123    where
124        Input: Serialize,
125        Output: DeserializeOwned,
126    {
127        if self.compact.is_some() {
128            super::compact_identity::refuse_duplicates(headers)?;
129        }
130        let body = self.codec.encode(input)?;
131        let response = self
132            .request_raw(Method::PATCH, path, Some(body), &[], headers)
133            .await?;
134        decode_typed_response_with_metadata(&self.codec, &response)
135    }
136
137    pub async fn delete<Output>(
138        &self,
139        path: &str,
140        headers: &[HeaderPair<'_>],
141    ) -> Result<Output, ClientError>
142    where
143        Output: DeserializeOwned,
144    {
145        let response = self
146            .request_raw(Method::DELETE, path, None, &[], headers)
147            .await?;
148        decode_typed_response(&self.codec, &response)
149    }
150
151    /// Same request as [`Self::delete`], but returns the status and
152    /// headers alongside the decoded body (issue #493) — e.g. to read
153    /// a `Retry-After` on a `429`, or any other out-of-band signal a
154    /// server sends on a delete response.
155    ///
156    /// Part of the `@version` optimistic-locking round trip since
157    /// cratestack#519: on an `@version` model the server requires an
158    /// `If-Match` header here exactly as it does on
159    /// `patch_with_response`, and returns `412` on a stale or missing
160    /// value. Pass the `ETag` learned from a prior
161    /// [`Self::get_with_response`] (or from a previous
162    /// `*_with_response` call's `TypedResponse::header("etag")`) as
163    /// `If-Match` in `headers`.
164    pub async fn delete_with_response<Output>(
165        &self,
166        path: &str,
167        headers: &[HeaderPair<'_>],
168    ) -> Result<TypedResponse<Output>, ClientError>
169    where
170        Output: DeserializeOwned,
171    {
172        let response = self
173            .request_raw(Method::DELETE, path, None, &[], headers)
174            .await?;
175        decode_typed_response_with_metadata(&self.codec, &response)
176    }
177}