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}