pocketbase_rs/records/crud/
create.rs

1use serde::{Deserialize, Serialize};
2use thiserror::Error;
3
4use crate::Collection;
5use crate::error::{BadRequestError, BadRequestResponse};
6
7/// Represents the various errors that can be obtained after a `create` request.
8#[derive(Error, Debug)]
9pub enum CreateError {
10    /// Communication with the `PocketBase` API was successful,
11    /// but returned a [400 Bad Request]("https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400") HTTP error response.
12    ///
13    /// Missing required value. `PocketBase`.
14    #[error("Failed to create record: {0:?}")]
15    BadRequest(Vec<BadRequestError>),
16    /// Communication with the `PocketBase` API was successful,
17    /// but returned a [403 Forbidden]("https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403") HTTP error response.
18    ///
19    /// You are not allowed to perform this request.
20    #[error("You are not allowed to perform this request.")]
21    Forbidden,
22    /// Communication with the `PocketBase` API was successful,
23    /// but returned a [404 Not Found]("https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404") HTTP error response.
24    ///
25    /// The requested resource wasn't found. Missing collection context.
26    #[error("The requested resource wasn't found. Missing collection context.")]
27    NotFound,
28    /// Communication with the `PocketBase` API failed.
29    ///
30    /// This could be caused by an internet outage, an error in the link given to the `PocketBase` SDK
31    /// and similar errors.
32    #[error("The communication with the PocketBase API failed: {0}")]
33    Unreachable(String),
34    /// The response could not be parsed into the expected data structure.
35    #[error(
36        "Could not parse response into the expected data structure. It usually means that there is a mismatch between the provided Generic Type Parameter and your Collection definition: {0}"
37    )]
38    ParseError(String),
39    /// An unexpected error occurred.
40    /// The response from the `PocketBase` instance API was unexpected.
41    /// If you think its an error, please [open an issue on GitHub]("https://github.com/fromhorizons/pocketbase-rs/issues").
42    #[error("An unhandled status code was returned by the PocketBase API: {0}")]
43    UnexpectedResponse(String),
44}
45
46// TODO: Include the actual record data based on Generic type parameter.
47//
48// pub struct CreateResponse<T> {
49//     pub collection_name: String,
50//     pub collection_id: String,
51//     pub id: String,
52//     pub updated: String,
53//     pub created: String,
54//     #[serde(flatten)]
55//     pub record: T, // The actual record data
56// }
57
58/// Contains information about the successfully created Record
59#[derive(Deserialize, Clone, Debug)]
60#[serde(rename_all = "camelCase")]
61pub struct CreateResponse {
62    pub collection_name: String,
63    pub collection_id: String,
64    pub id: String,
65    pub updated: String,
66    pub created: String,
67}
68
69impl Collection<'_> {
70    /// Create a new record.
71    ///
72    /// For file uploads, use [`Collection::create_multipart()`].
73    ///
74    /// # Example
75    /// ```rust,ignore
76    /// #[derive(Default, Serialize, Clone, Debug)]
77    /// struct Article {
78    ///     name: String,
79    ///     content: String,
80    /// }
81    ///
82    /// let article = pb
83    ///     .collection("articles")
84    ///     .create::<Article>(Article {
85    ///         name: "test".to_string(),
86    ///         content: "an interesting article content.".to_string(),
87    ///     })
88    ///     .await?;
89    /// ```
90    pub async fn create<T: Default + Serialize + Clone + Send>(
91        self,
92        record: T,
93    ) -> Result<CreateResponse, CreateError> {
94        let endpoint = format!(
95            "{}/api/collections/{}/records",
96            self.client.base_url, self.name
97        );
98
99        let request = self
100            .client
101            .request_post_json(&endpoint, &record)
102            .send()
103            .await;
104
105        create_processing(request).await
106    }
107
108    /// Create a new record with multipart form data (e.g., for file uploads).
109    ///
110    /// For simple JSON records without files, use [`Collection::create()`].
111    ///
112    /// # Example
113    /// ```rust,ignore
114    /// use std::fs;
115    /// use pocketbase_rs::{Form, Part};
116    ///
117    /// let image = fs::read("./vulpes_vulpes.jpg")?;
118    ///
119    /// let image_part = Part::bytes(image)
120    ///     .file_name("vulpes_vulpes")
121    ///     .mime_str("image/jpeg")?;
122    ///
123    /// let form = Form::new()
124    ///     .text("name", "Red Fox")
125    ///     .part("illustration", image_part);
126    ///
127    /// let record = pb
128    ///     .collection("foxes")
129    ///     .create_multipart(form)
130    ///     .await?;
131    /// ```
132    pub async fn create_multipart(
133        self,
134        form: reqwest::multipart::Form,
135    ) -> Result<CreateResponse, CreateError> {
136        let collection_name = self.name;
137
138        let endpoint = format!(
139            "{}/api/collections/{}/records",
140            self.client.base_url, collection_name
141        );
142
143        let request = self.client.request_post_form(&endpoint, form).send().await;
144
145        create_processing(request).await
146    }
147}
148
149async fn create_processing(
150    request: Result<reqwest::Response, reqwest::Error>,
151) -> Result<CreateResponse, CreateError> {
152    match request {
153        Ok(response) => match response.status() {
154            reqwest::StatusCode::OK => {
155                let data = response.json::<CreateResponse>().await;
156
157                match data {
158                    Ok(data) => Ok(data),
159                    Err(error) => Err(CreateError::ParseError(error.to_string())),
160                }
161            }
162
163            reqwest::StatusCode::BAD_REQUEST => {
164                let data = response.json::<BadRequestResponse>().await;
165
166                match data {
167                    Ok(bad_response) => {
168                        let mut errors: Vec<BadRequestError> = vec![];
169
170                        for (error_name, error_data) in bad_response.data {
171                            errors.push(BadRequestError {
172                                name: error_name,
173                                code: error_data.code,
174                                message: error_data.message,
175                            });
176                        }
177
178                        Err(CreateError::BadRequest(errors))
179                    }
180                    Err(error) => Err(CreateError::ParseError(error.to_string())),
181                }
182            }
183
184            reqwest::StatusCode::FORBIDDEN => Err(CreateError::Forbidden),
185            reqwest::StatusCode::NOT_FOUND => Err(CreateError::NotFound),
186
187            _ => Err(CreateError::UnexpectedResponse(
188                response.status().to_string(),
189            )),
190        },
191
192        Err(error) => Err(CreateError::Unreachable(error.to_string())),
193    }
194}