powerplatform_dataverse_service_client/batch.rs
1use std::fmt::{Display, Write};
2
3use uuid::Uuid;
4
5use crate::{
6 client::VERSION,
7 entity::WriteEntity,
8 reference::Reference,
9 result::{IntoDataverseResult, Result},
10};
11
12/**
13Represents a batch of Microsoft Dataverse Requests
14
15Some restrictions apply for creating batches:
16- the batch size may not exceed 1000 calls
17- the batch execution time may not exceed 2 minutes
18
19the second restriction is especially tricky to handle because the execution time
20depends on the complexity of the entity in dataverse.
21So it is possible to create 300 records of an entity with low complexity
22but only 50 records of an entity with high complexity in that timeframe.
23
24Based on experience a batch size of 50 should be safe for all entities though
25
26# Examples
27```rust
28use uuid::Uuid;
29use serde::Serialize;
30use powerplatform_dataverse_service_client::{
31 batch::Batch,
32 client::Client,
33 entity::WriteEntity,
34 reference::{Reference, ReferenceStruct},
35 result::{Result, IntoDataverseResult}
36};
37
38async fn test() -> Result<()> {
39 let testy_contact = Contact {
40 contactid: Uuid::parse_str("12345678-1234-1234-1234-123456789012").into_dataverse_result()?,
41 firstname: String::from("Testy"),
42 lastname: String::from("McTestface"),
43 };
44
45 let marianne_contact = Contact {
46 contactid: Uuid::parse_str("12345678-1234-1234-1234-123456789abc").into_dataverse_result()?,
47 firstname: String::from("Marianne"),
48 lastname: String::from("McTestface"),
49 };
50
51 // this batch creates both contacts in one call
52 let mut batch = Batch::new("https://instance.crm.dynamics.com/");
53 batch.create(&testy_contact)?;
54 batch.create(&marianne_contact)?;
55
56 let client = Client::new_dummy(); // Please replace this with your preferred authentication method
57 client.execute(&batch).await?;
58 Ok(())
59}
60
61#[derive(Serialize)]
62struct Contact {
63 contactid: Uuid,
64 firstname: String,
65 lastname: String,
66}
67
68impl WriteEntity for Contact {}
69
70impl Reference for Contact {
71 fn get_reference(&self) -> ReferenceStruct {
72 ReferenceStruct::new(
73 "contacts",
74 self.contactid,
75 )
76 }
77}
78```
79*/
80pub struct Batch {
81 url: &'static str,
82 batch_id: Uuid,
83 dataset_id: Uuid,
84 payload: String,
85 next_content_id: u16,
86}
87
88impl Batch {
89 /// Creates a new empty batch with its own batch id and dataset id
90 pub fn new(url: &'static str) -> Self {
91 Self {
92 url,
93 batch_id: Uuid::new_v4(),
94 dataset_id: Uuid::new_v4(),
95 payload: String::new(),
96 next_content_id: 1,
97 }
98 }
99
100 /**
101 Clears the batch of its contents and generates a new batch id and
102 a new dataset id
103
104 Note that this can be used to prevent frequent allocations by reusing
105 the `Batch` instance and its buffer
106 */
107 pub fn reset(&mut self) {
108 self.batch_id = Uuid::new_v4();
109 self.dataset_id = Uuid::new_v4();
110 self.payload.clear();
111 self.next_content_id = 1;
112 }
113
114 /// returns the current batch id (This will change after a call to `reset()`)
115 pub fn get_batch_id(&self) -> Uuid {
116 self.batch_id
117 }
118
119 /// returns the current dataset id (This will change after a call to `reset()`)
120 pub fn get_dataset_id(&self) -> Uuid {
121 self.dataset_id
122 }
123
124 /// returns the current count of requests in this batch
125 pub fn get_count(&self) -> u16 {
126 self.next_content_id - 1
127 }
128
129 /**
130 Adds a Create Request for the given entity to this batch
131
132 Please note that this function can fail if a serde serialization error occurs
133
134 # Examples
135 ```rust
136 use uuid::Uuid;
137 use serde::Serialize;
138 use powerplatform_dataverse_service_client::{
139 batch::Batch,
140 client::Client,
141 entity::WriteEntity,
142 reference::{Reference, ReferenceStruct},
143 result::{Result, IntoDataverseResult}
144 };
145
146 async fn test() -> Result<()> {
147 let testy_contact = Contact {
148 contactid: Uuid::parse_str("12345678-1234-1234-1234-123456789012").into_dataverse_result()?,
149 firstname: String::from("Testy"),
150 lastname: String::from("McTestface"),
151 };
152
153 let marianne_contact = Contact {
154 contactid: Uuid::parse_str("12345678-1234-1234-1234-123456789abc").into_dataverse_result()?,
155 firstname: String::from("Marianne"),
156 lastname: String::from("McTestface"),
157 };
158
159 // this batch creates both contacts in one call
160 let mut batch = Batch::new("https://instance.crm.dynamics.com/");
161 batch.create(&testy_contact)?;
162 batch.create(&marianne_contact)?;
163
164 let client = Client::new_dummy(); // Please replace this with your preferred authentication method
165 client.execute(&batch).await?;
166 Ok(())
167 }
168
169 #[derive(Serialize)]
170 struct Contact {
171 contactid: Uuid,
172 firstname: String,
173 lastname: String,
174 }
175
176 impl WriteEntity for Contact {}
177
178 impl Reference for Contact {
179 fn get_reference(&self) -> ReferenceStruct {
180 ReferenceStruct::new(
181 "contacts",
182 self.contactid,
183 )
184 }
185 }
186 ```
187 */
188 pub fn create(&mut self, entity: &impl WriteEntity) -> Result<()> {
189 let reference = entity.get_reference();
190 let entity = serde_json::to_string(entity).into_dataverse_result()?;
191
192 write!(
193 self.payload,
194 "--changeset_{}\nContent-Type: application/http\nContent-Transfer-Encoding:binary\nContent-Id: {}\n\nPOST {}api/data/v{}/{} HTTP/1.1\nContent-Type: application/json;type=entry\n\n{}\n",
195 self.dataset_id.as_simple(),
196 self.next_content_id,
197 self.url,
198 VERSION,
199 reference.entity_name,
200 entity
201 ).into_dataverse_result()?;
202
203 self.next_content_id += 1;
204 Ok(())
205 }
206
207 /**
208 Adds an Update Request for the given entity to this batch
209
210 Please note that this function can fail if a serde serialization error occurs
211
212 # Examples
213 ```rust
214 use uuid::Uuid;
215 use serde::Serialize;
216 use powerplatform_dataverse_service_client::{
217 batch::Batch,
218 client::Client,
219 entity::WriteEntity,
220 reference::{Reference, ReferenceStruct},
221 result::{Result, IntoDataverseResult}
222 };
223
224 async fn test() -> Result<()> {
225 let testy_contact = Contact {
226 contactid: Uuid::parse_str("12345678-1234-1234-1234-123456789012").into_dataverse_result()?,
227 firstname: String::from("Testy"),
228 lastname: String::from("McTestface"),
229 };
230
231 let marianne_contact = Contact {
232 contactid: Uuid::parse_str("12345678-1234-1234-1234-123456789abc").into_dataverse_result()?,
233 firstname: String::from("Marianne"),
234 lastname: String::from("McTestface"),
235 };
236
237 // this batch creates both contacts in one call
238 let mut batch = Batch::new("https://instance.crm.dynamics.com/");
239 batch.update(&testy_contact)?;
240 batch.update(&marianne_contact)?;
241
242 let client = Client::new_dummy(); // Please replace this with your preferred authentication method
243 client.execute(&batch).await?;
244 Ok(())
245 }
246
247 #[derive(Serialize)]
248 struct Contact {
249 contactid: Uuid,
250 firstname: String,
251 lastname: String,
252 }
253
254 impl WriteEntity for Contact {}
255
256 impl Reference for Contact {
257 fn get_reference(&self) -> ReferenceStruct {
258 ReferenceStruct::new(
259 "contacts",
260 self.contactid,
261 )
262 }
263 }
264 ```
265 */
266 pub fn update(&mut self, entity: &impl WriteEntity) -> Result<()> {
267 let reference = entity.get_reference();
268 let entity = serde_json::to_string(entity).into_dataverse_result()?;
269
270 write!(
271 self.payload,
272 "--changeset_{}\nContent-Type: application/http\nContent-Transfer-Encoding:binary\nContent-Id: {}\n\nPATCH {}api/data/v{}/{}({}) HTTP/1.1\nContent-Type: application/json;type=entry\nIf-Match: *\n\n{}\n",
273 self.dataset_id.as_simple(),
274 self.next_content_id,
275 self.url,
276 VERSION,
277 reference.entity_name,
278 reference.entity_id,
279 entity
280 ).into_dataverse_result()?;
281
282 self.next_content_id += 1;
283 Ok(())
284 }
285
286 /**
287 Adds an Upsert Request for the given entity to this batch
288
289 Please note that this function can fail if a serde serialization error occurs
290
291 # Examples
292 ```rust
293 use uuid::Uuid;
294 use serde::Serialize;
295 use powerplatform_dataverse_service_client::{
296 batch::Batch,
297 client::Client,
298 entity::WriteEntity,
299 reference::{Reference, ReferenceStruct},
300 result::{Result, IntoDataverseResult}
301 };
302
303 async fn test() -> Result<()> {
304 let testy_contact = Contact {
305 contactid: Uuid::parse_str("12345678-1234-1234-1234-123456789012").into_dataverse_result()?,
306 firstname: String::from("Testy"),
307 lastname: String::from("McTestface"),
308 };
309
310 let marianne_contact = Contact {
311 contactid: Uuid::parse_str("12345678-1234-1234-1234-123456789abc").into_dataverse_result()?,
312 firstname: String::from("Marianne"),
313 lastname: String::from("McTestface"),
314 };
315
316 // this batch creates both contacts in one call
317 let mut batch = Batch::new("https://instance.crm.dynamics.com/");
318 batch.upsert(&testy_contact)?;
319 batch.upsert(&marianne_contact)?;
320
321 let client = Client::new_dummy(); // Please replace this with your preferred authentication method
322 client.execute(&batch).await?;
323 Ok(())
324 }
325
326 #[derive(Serialize)]
327 struct Contact {
328 contactid: Uuid,
329 firstname: String,
330 lastname: String,
331 }
332
333 impl WriteEntity for Contact {}
334
335 impl Reference for Contact {
336 fn get_reference(&self) -> ReferenceStruct {
337 ReferenceStruct::new(
338 "contacts",
339 self.contactid,
340 )
341 }
342 }
343 ```
344 */
345 pub fn upsert(&mut self, entity: &impl WriteEntity) -> Result<()> {
346 let reference = entity.get_reference();
347 let entity = serde_json::to_string(entity).into_dataverse_result()?;
348
349 write!(
350 self.payload,
351 "--changeset_{}\nContent-Type: application/http\nContent-Transfer-Encoding:binary\nContent-Id: {}\n\nPATCH {}api/data/v{}/{}({}) HTTP/1.1\nContent-Type: application/json;type=entry\n\n{}\n",
352 self.dataset_id.as_simple(),
353 self.next_content_id,
354 self.url,
355 VERSION,
356 reference.entity_name,
357 reference.entity_id,
358 entity
359 ).into_dataverse_result()?;
360
361 self.next_content_id += 1;
362 Ok(())
363 }
364
365 /**
366 Adds a Delete Request for the given entity reference to this batch
367
368 Please note that this function can fail if a serde serialization error occurs
369
370 # Examples
371 ```rust
372 use uuid::Uuid;
373 use serde::Serialize;
374 use powerplatform_dataverse_service_client::{
375 batch::Batch,
376 client::Client,
377 entity::WriteEntity,
378 reference::{Reference, ReferenceStruct},
379 result::{Result, IntoDataverseResult}
380 };
381
382 async fn test() -> Result<()> {
383 let testy_contact = ReferenceStruct::new(
384 "contacts",
385 Uuid::parse_str("12345678-1234-1234-1234-123456789012").into_dataverse_result()?
386 );
387
388 let marianne_contact = ReferenceStruct::new(
389 "contacts",
390 Uuid::parse_str("12345678-1234-1234-1234-123456789abc").into_dataverse_result()?
391 );
392
393 // this batch creates both contacts in one call
394 let mut batch = Batch::new("https://instance.crm.dynamics.com/");
395 batch.delete(&testy_contact)?;
396 batch.delete(&marianne_contact)?;
397
398 let client = Client::new_dummy(); // Please replace this with your preferred authentication method
399 client.execute(&batch).await?;
400 Ok(())
401 }
402 ```
403 */
404 pub fn delete(&mut self, entity: &impl Reference) -> Result<()> {
405 let reference = entity.get_reference();
406
407 write!(
408 self.payload,
409 "--changeset_{}\nContent-Type: application/http\nContent-Transfer-Encoding:binary\nContent-Id: {}\n\nDELETE {}api/data/v{}/{}({}) HTTP/1.1\n\n",
410 self.dataset_id.as_simple(),
411 self.next_content_id,
412 self.url,
413 VERSION,
414 reference.entity_name,
415 reference.entity_id
416 ).into_dataverse_result()?;
417
418 self.next_content_id += 1;
419 Ok(())
420 }
421}
422
423impl Display for Batch {
424 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
425 let batch_id = self.batch_id.as_simple();
426 let dataset_id = self.dataset_id.as_simple();
427
428 f.write_fmt(
429 format_args!(
430 "--batch_{}\nContent-Type: multipart/mixed; boundary=changeset_{}\n\n{}--changeset_{}--\n--batch_{}--",
431 batch_id,
432 dataset_id,
433 self.payload,
434 dataset_id,
435 batch_id,
436 )
437 )
438 }
439}