zeebe_rs/resource.rs
1use crate::proto;
2use crate::{Client, ClientError};
3use std::path::PathBuf;
4use thiserror::Error;
5
6/// Represents errors that can occure while deploying a resource
7#[derive(Error, Debug)]
8pub enum DeployResourceError {
9 /// Failed to load resource file from filesystem
10 #[error("failed to load file {file_path:?} {source:?}")]
11 ResourceLoad {
12 /// Path to file that failed to load
13 file_path: PathBuf,
14 source: std::io::Error,
15 },
16
17 /// Resource file name could not be determined
18 #[error("missing name {file_path:?}")]
19 MissingName {
20 /// Path to file missing a name
21 file_path: PathBuf,
22 },
23}
24
25#[derive(Default, Clone)]
26pub struct Initial;
27
28pub struct WithFile;
29pub struct WithDefinition;
30pub struct WithKey;
31
32pub trait DeployResourceState {}
33impl DeployResourceState for Initial {}
34impl DeployResourceState for WithFile {}
35impl DeployResourceState for WithDefinition {}
36
37/// Request to deploy one or more resources to Zeebe
38///
39/// Supports:
40/// - BPMN process definitions
41/// - DMN decision tables
42/// - Custom forms
43///
44/// # Examples
45/// ```ignore
46/// let result = client
47/// .deploy_resource()
48/// .with_resource_file(PathBuf::from("./examples/resources/hello_world.bpmn"))
49/// .read_resource_files()?
50/// .send()
51/// .await?;
52/// ```
53///
54///
55/// # Notes
56/// - Deployment is atomic - all resources succeed or none are deployed
57/// - Resources are validated before deployment
58///
59/// # Errors
60/// - PERMISSION_DENIED:
61/// - Deployment to unauthorized tenant
62/// - INVALID_ARGUMENT:
63/// - No resources provided
64/// - Invalid resource content (broken XML, invalid BPMN/DMN)
65/// - Missing/invalid tenant ID with multi-tenancy enabled
66/// - Tenant ID provided with multi-tenancy disabled
67#[derive(Debug)]
68pub struct DeployResourceRequest<T: DeployResourceState> {
69 client: Client,
70 resource_file_paths: Option<Vec<PathBuf>>,
71 resource_definitions: Vec<(String, Vec<u8>)>,
72 tenant_id: Option<String>,
73 _state: std::marker::PhantomData<T>,
74}
75
76impl<T: DeployResourceState> DeployResourceRequest<T> {
77 pub(crate) fn new(client: Client) -> DeployResourceRequest<Initial> {
78 DeployResourceRequest {
79 client,
80 resource_file_paths: None,
81 resource_definitions: vec![],
82 tenant_id: None,
83 _state: std::marker::PhantomData,
84 }
85 }
86
87 fn transition<NewState: DeployResourceState>(self) -> DeployResourceRequest<NewState> {
88 DeployResourceRequest {
89 client: self.client,
90 resource_file_paths: self.resource_file_paths,
91 resource_definitions: self.resource_definitions,
92 tenant_id: None,
93 _state: std::marker::PhantomData,
94 }
95 }
96}
97
98impl DeployResourceRequest<Initial> {
99 /// Adds a single resource file to the deployment request.
100 ///
101 /// This method allows you to specify a single resource file,
102 /// such as a BPMN, DMN, or Form file, to be included in the deployment.
103 ///
104 /// # Arguments
105 ///
106 /// * `file_path` - A `PathBuf` representing the path to the resource file.
107 ///
108 /// # Returns
109 ///
110 /// A `DeployResourceRequest<WithFile>` instance with the specified resource file added.
111 pub fn with_resource_file(mut self, file_path: PathBuf) -> DeployResourceRequest<WithFile> {
112 self.resource_file_paths = Some(vec![file_path]);
113 self.transition()
114 }
115
116 /// Adds multiple resource files to the deployment request.
117 ///
118 /// This method allows you to specify multiple resource files,
119 /// such as BPMN, DMN, or Form files, to be included in the deployment.
120 ///
121 /// # Arguments
122 ///
123 /// * `file_paths` - A `Vec<PathBuf>` containing the paths to the resource files.
124 ///
125 /// # Returns
126 ///
127 /// A `DeployResourceRequest<WithFile>` instance with the specified resource files added.
128 pub fn with_resource_files(
129 mut self,
130 file_paths: Vec<PathBuf>,
131 ) -> DeployResourceRequest<WithFile> {
132 self.resource_file_paths = Some(file_paths);
133 self.transition()
134 }
135
136 /// Adds a single resource definition to the deployment request.
137 ///
138 /// This method allows you to specify a single resource definition,
139 /// including its name and raw content bytes, to be included in the deployment.
140 ///
141 /// # Arguments
142 ///
143 /// * `name` - A `String` representing the resource name (e.g., `process.bpmn`).
144 /// * `definition` - A `Vec<u8>` containing the raw content bytes of the resource.
145 ///
146 /// # Returns
147 ///
148 /// A `DeployResourceRequest<WithDefinition>` instance with the specified resource definition added.
149 pub fn with_definition(
150 mut self,
151 name: String,
152 definition: Vec<u8>,
153 ) -> DeployResourceRequest<WithDefinition> {
154 self.resource_definitions.push((name, definition));
155 self.transition()
156 }
157
158 /// Adds multiple resource definitions to the deployment request.
159 ///
160 /// This method allows you to specify multiple resource definitions,
161 /// each including its name and raw content bytes, to be included in the deployment.
162 ///
163 /// # Arguments
164 ///
165 /// * `definitions` - A `Vec<(String, Vec<u8>)>` containing pairs of resource names and their raw content bytes.
166 ///
167 /// # Returns
168 ///
169 /// A `DeployResourceRequest<WithDefinition>` instance with the specified resource definitions added.
170 pub fn with_definitions(
171 mut self,
172 mut definitions: Vec<(String, Vec<u8>)>,
173 ) -> DeployResourceRequest<WithDefinition> {
174 self.resource_definitions.append(&mut definitions);
175 self.transition()
176 }
177}
178
179impl DeployResourceRequest<WithFile> {
180 /// Reads resource files and transitions to definition state
181 ///
182 /// # Errors
183 /// - ResourceLoad: Failed to read file
184 /// - MissingName: File name missing
185 pub fn read_resource_files(
186 mut self,
187 ) -> Result<DeployResourceRequest<WithDefinition>, ClientError> {
188 if let Some(resource_files) = self.resource_file_paths.take() {
189 let contents: Result<Vec<(String, Vec<u8>)>, DeployResourceError> = resource_files
190 .into_iter()
191 .map(|file_path| {
192 let def = std::fs::read(file_path.clone()).map_err(|source| {
193 DeployResourceError::ResourceLoad {
194 file_path: file_path.clone(),
195 source,
196 }
197 })?;
198
199 let name = file_path
200 .file_name()
201 .ok_or(DeployResourceError::MissingName {
202 file_path: file_path.clone(),
203 })?;
204 Ok((name.to_string_lossy().into_owned(), def))
205 })
206 .collect();
207 self.resource_definitions = contents?;
208 }
209
210 Ok(self.transition())
211 }
212}
213
214impl DeployResourceRequest<WithDefinition> {
215 /// Sets the tenant ID that will own the deployed resources.
216 ///
217 /// # Arguments
218 ///
219 /// * `tenant_id` - A `String` representing the ID of the tenant that will own these resources.
220 ///
221 /// # Notes
222 ///
223 /// - This field is required when multi-tenancy is enabled.
224 /// - This field must not be set when multi-tenancy is disabled.
225 pub fn with_tenant(mut self, tenant_id: String) -> DeployResourceRequest<WithDefinition> {
226 self.tenant_id = Some(tenant_id);
227 self
228 }
229
230 /// Sends the deploy resource request to the gateway.
231 ///
232 /// # Returns
233 ///
234 /// A `Result` containing:
235 /// - `Ok(DeployResourceResponse)` on success, which includes:
236 /// - Deployment key
237 /// - List of deployed resources with metadata
238 /// - Tenant ID
239 /// - `Err(ClientError)` on failure, with possible errors:
240 /// - `PERMISSION_DENIED`: Deployment to an unauthorized tenant.
241 /// - `INVALID_ARGUMENT`:
242 /// - No resources provided.
243 /// - Invalid resource content.
244 /// - Missing or invalid tenant ID when multi-tenancy is enabled.
245 /// - Tenant ID provided when multi-tenancy is disabled.
246 ///
247 /// # Errors
248 ///
249 /// This function will return an error if the request fails due to permission issues or invalid arguments.
250 pub async fn send(mut self) -> Result<DeployResourceResponse, ClientError> {
251 let resources: Vec<_> = self
252 .resource_definitions
253 .into_iter()
254 .map(|(name, content)| proto::Resource { name, content })
255 .collect();
256
257 let tenant_id = self.tenant_id.unwrap_or_default();
258
259 let res = self
260 .client
261 .gateway_client
262 .deploy_resource(proto::DeployResourceRequest {
263 resources,
264 tenant_id,
265 })
266 .await?;
267
268 Ok(res.into_inner().into())
269 }
270}
271
272#[derive(Debug, Clone)]
273pub struct ProcessMetadata {
274 bpmn_process_id: String,
275 version: i32,
276 process_definition_key: i64,
277 resource_name: String,
278 tenant_id: String,
279}
280
281/// Metadata information for a deployed BPMN process definition.
282///
283/// This struct encapsulates the identifying information and metadata for a process
284/// definition deployed to a Zeebe workflow engine. Each process definition is
285/// uniquely identified by a combination of its BPMN process ID and version number.
286impl ProcessMetadata {
287 /// Returns the BPMN process ID.
288 ///
289 /// # Returns
290 ///
291 /// A string slice representing the BPMN process ID.
292 pub fn bpmn_process_id(&self) -> &str {
293 &self.bpmn_process_id
294 }
295
296 /// Returns the version of this process definition.
297 ///
298 /// # Returns
299 ///
300 /// An integer representing the version of the process definition.
301 pub fn version(&self) -> i32 {
302 self.version
303 }
304
305 /// Returns the unique key assigned to this process definition by Zeebe.
306 ///
307 /// # Returns
308 ///
309 /// A 64-bit integer representing the unique key of the process definition.
310 pub fn process_definition_key(&self) -> i64 {
311 self.process_definition_key
312 }
313
314 /// Returns the name of the resource this process was deployed from.
315 ///
316 /// # Returns
317 ///
318 /// A string slice representing the name of the resource file.
319 pub fn resource_name(&self) -> &str {
320 &self.resource_name
321 }
322
323 /// Returns the ID of the tenant that owns this process definition.
324 ///
325 /// The tenant ID is used in multi-tenant setups to segregate process
326 /// definitions by tenant. If multi-tenancy is disabled, this will be an
327 /// empty string.
328 ///
329 /// # Returns
330 ///
331 /// A string slice representing the tenant ID.
332 pub fn tenant_id(&self) -> &str {
333 &self.tenant_id
334 }
335}
336
337impl From<proto::ProcessMetadata> for ProcessMetadata {
338 fn from(value: proto::ProcessMetadata) -> ProcessMetadata {
339 ProcessMetadata {
340 bpmn_process_id: value.bpmn_process_id,
341 version: value.version,
342 process_definition_key: value.process_definition_key,
343 resource_name: value.resource_name,
344 tenant_id: value.tenant_id,
345 }
346 }
347}
348
349/// Metadata information for a deployed DMN decision definition.
350#[derive(Debug, Clone)]
351pub struct DecisionMetadata {
352 dmn_decision_id: String,
353 dmn_decision_name: String,
354 version: i32,
355 decision_key: i64,
356 dmn_decision_requirement_id: String,
357 decision_requirements_key: i64,
358 tenant_id: String,
359}
360
361impl DecisionMetadata {
362 /// Returns the unique identifier for this DMN decision
363 ///
364 /// The ID is defined in the DMN XML via the 'id' attribute
365 pub fn dmn_decision_id(&self) -> &str {
366 &self.dmn_decision_id
367 }
368
369 /// Returns the human-readable name for this DMN decision
370 ///
371 /// The name is defined in the DMN XML via the 'name' attribute
372 pub fn dmn_decision_name(&self) -> &str {
373 &self.dmn_decision_name
374 }
375
376 /// Returns the version of this decision definition
377 ///
378 /// Version is auto-incremented when deploying a decision with same ID
379 pub fn version(&self) -> i32 {
380 self.version
381 }
382
383 /// Returns the unique key assigned to this decision by Zeebe
384 ///
385 /// Key is globally unique across the cluster
386 pub fn decision_key(&self) -> i64 {
387 self.decision_key
388 }
389
390 /// Returns the ID of the decision requirements graph this belongs to
391 ///
392 /// Links to the parent DRG that contains this decision
393 pub fn dmn_decision_requirement_id(&self) -> &str {
394 &self.dmn_decision_requirement_id
395 }
396
397 /// Returns the key of the decision requirements graph this belongs to
398 ///
399 /// Links to the parent DRG via its unique key
400 pub fn decision_requirements_key(&self) -> i64 {
401 self.decision_requirements_key
402 }
403
404 /// Returns the ID of tenant that owns this decision
405 ///
406 /// Empty if multi-tenancy is disabled
407 pub fn tenant_id(&self) -> &str {
408 &self.tenant_id
409 }
410}
411
412impl From<proto::DecisionMetadata> for DecisionMetadata {
413 fn from(value: proto::DecisionMetadata) -> DecisionMetadata {
414 DecisionMetadata {
415 dmn_decision_id: value.dmn_decision_id,
416 dmn_decision_name: value.dmn_decision_name,
417 version: value.version,
418 decision_key: value.decision_key,
419 dmn_decision_requirement_id: value.dmn_decision_requirements_id,
420 decision_requirements_key: value.decision_requirements_key,
421 tenant_id: value.tenant_id,
422 }
423 }
424}
425
426/// Metadata information for a deployed DMN decision requirement definition.
427#[derive(Debug, Clone)]
428pub struct DecisionRequirementsMetadata {
429 dmn_decision_requirements_id: String,
430 dmn_decision_requirements_name: String,
431 version: i32,
432 decision_requirements_key: i64,
433 resource_name: String,
434 tenant_id: String,
435}
436
437impl DecisionRequirementsMetadata {
438 /// Returns the unique identifier for this decision requirements graph
439 ///
440 /// The ID is defined in the DMN XML via the 'id' attribute
441 pub fn dmn_decision_requirements_id(&self) -> &str {
442 &self.dmn_decision_requirements_id
443 }
444
445 /// Returns the human-readable name for this decision requirements graph
446 ///
447 /// The name is defined in the DMN XML via the 'name' attribute
448 pub fn dmn_decision_requirements_name(&self) -> &str {
449 &self.dmn_decision_requirements_name
450 }
451
452 /// Returns the version of this decision requirements graph
453 ///
454 /// Version is auto-incremented when deploying a DRG with same ID
455 pub fn version(&self) -> i32 {
456 self.version
457 }
458
459 /// Returns the unique key assigned to this DRG by Zeebe
460 ///
461 /// Key is globally unique across the cluster
462 pub fn decision_requirements_key(&self) -> i64 {
463 self.decision_requirements_key
464 }
465
466 /// Returns the name of the resource file this was deployed from
467 ///
468 /// Usually ends with .dmn extension
469 pub fn resource_name(&self) -> &str {
470 &self.resource_name
471 }
472
473 /// Returns the ID of tenant that owns this DRG
474 ///
475 /// Empty if multi-tenancy is disabled
476 pub fn tenant_id(&self) -> &str {
477 &self.tenant_id
478 }
479}
480
481impl From<proto::DecisionRequirementsMetadata> for DecisionRequirementsMetadata {
482 fn from(value: proto::DecisionRequirementsMetadata) -> DecisionRequirementsMetadata {
483 DecisionRequirementsMetadata {
484 dmn_decision_requirements_id: value.dmn_decision_requirements_id,
485 dmn_decision_requirements_name: value.dmn_decision_requirements_name,
486 version: value.version,
487 decision_requirements_key: value.decision_requirements_key,
488 resource_name: value.resource_name,
489 tenant_id: value.tenant_id,
490 }
491 }
492}
493
494/// Metadata for a deployed form
495#[derive(Debug, Clone)]
496pub struct FormMetadata {
497 form_id: String,
498 version: i32,
499 form_key: i64,
500 resource_name: String,
501 tenant_id: String,
502}
503
504impl FormMetadata {
505 /// Returns the unique identifier for the form.
506 ///
507 /// # Returns
508 /// A string slice representing the form's unique identifier.
509 pub fn form_id(&self) -> &str {
510 &self.form_id
511 }
512
513 /// Returns the version of the form.
514 ///
515 /// # Returns
516 /// An integer representing the form's version.
517 pub fn version(&self) -> i32 {
518 self.version
519 }
520
521 /// Returns the unique key assigned by Zeebe.
522 ///
523 /// # Returns
524 /// A 64-bit integer representing the unique key.
525 pub fn form_key(&self) -> i64 {
526 self.form_key
527 }
528
529 /// Returns the name of the resource file from which this form was deployed.
530 ///
531 /// # Returns
532 /// A string slice representing the resource file name.
533 pub fn resource_name(&self) -> &str {
534 &self.resource_name
535 }
536
537 /// Returns the ID of the tenant that owns this form.
538 ///
539 /// # Returns
540 /// A string slice representing the tenant's ID.
541 pub fn tenant_id(&self) -> &str {
542 &self.tenant_id
543 }
544}
545
546impl From<proto::FormMetadata> for FormMetadata {
547 fn from(value: proto::FormMetadata) -> FormMetadata {
548 FormMetadata {
549 form_id: value.form_id,
550 version: value.version,
551 form_key: value.form_key,
552 resource_name: value.resource_name,
553 tenant_id: value.tenant_id,
554 }
555 }
556}
557
558/// Metadata for a deployed resource
559#[derive(Debug, Clone)]
560pub enum Metadata {
561 Process(ProcessMetadata),
562 Decision(DecisionMetadata),
563 DecisionRequirements(DecisionRequirementsMetadata),
564 Form(FormMetadata),
565}
566
567impl From<proto::deployment::Metadata> for Metadata {
568 fn from(value: proto::deployment::Metadata) -> Metadata {
569 match value {
570 proto::deployment::Metadata::Process(p) => Metadata::Process(p.into()),
571 proto::deployment::Metadata::Decision(d) => Metadata::Decision(d.into()),
572 proto::deployment::Metadata::DecisionRequirements(dr) => {
573 Metadata::DecisionRequirements(dr.into())
574 }
575 proto::deployment::Metadata::Form(f) => Metadata::Form(f.into()),
576 }
577 }
578}
579
580/// A successfully deployed resource
581#[derive(Debug, Clone)]
582pub struct Deployment {
583 metadata: Option<Metadata>,
584}
585
586impl Deployment {
587 /// Retrieves the metadata associated with this deployed resource, if available.
588 ///
589 /// # Returns
590 ///
591 /// An `Option` containing a reference to the `Metadata` if it exists, or `None` if the metadata is not available.
592 pub fn metadata(&self) -> Option<&Metadata> {
593 self.metadata.as_ref()
594 }
595}
596
597impl From<proto::Deployment> for Deployment {
598 fn from(value: proto::Deployment) -> Deployment {
599 Deployment {
600 metadata: value.metadata.map(|m| m.into()),
601 }
602 }
603}
604
605/// Response from deploying one or more resources
606#[derive(Debug, Clone)]
607pub struct DeployResourceResponse {
608 key: i64,
609 deployments: Vec<Deployment>,
610 tenant_id: String,
611}
612
613impl DeployResourceResponse {
614 /// Returns the unique key associated with this deployment operation.
615 ///
616 /// # Returns
617 ///
618 /// An `i64` representing the unique key.
619 pub fn key(&self) -> i64 {
620 self.key
621 }
622
623 /// Returns a slice of `Deployment` representing the successfully deployed resources.
624 ///
625 /// # Returns
626 ///
627 /// A slice of `Deployment` structs.
628 pub fn deployments(&self) -> &[Deployment] {
629 &self.deployments
630 }
631
632 /// Returns the ID of the tenant that owns these resources.
633 ///
634 /// # Returns
635 ///
636 /// A string slice representing the tenant ID.
637 pub fn tenant_id(&self) -> &str {
638 &self.tenant_id
639 }
640}
641
642impl From<proto::DeployResourceResponse> for DeployResourceResponse {
643 fn from(value: proto::DeployResourceResponse) -> DeployResourceResponse {
644 DeployResourceResponse {
645 key: value.key,
646 deployments: value.deployments.into_iter().map(|d| d.into()).collect(),
647 tenant_id: value.tenant_id,
648 }
649 }
650}
651
652pub trait DeleteResourceRequestState {}
653impl DeleteResourceRequestState for Initial {}
654impl DeleteResourceRequestState for WithKey {}
655
656/// Request to delete a deployed resource in Zeebe
657///
658/// # Examples
659///
660/// ```ignore
661/// let response = client
662/// .delete_resource()
663/// .with_resource_key(12345)
664/// .send()
665/// .await?;
666/// ```
667///
668/// # Errors
669///
670/// Sending a delete request may result in the following errors:
671/// - `NOT_FOUND`: No resource exists with the given key.
672///
673/// # Notes
674///
675/// The delete resource operation is fire-and-forget, meaning there is no detailed response.
676#[derive(Debug, Clone)]
677pub struct DeleteResourceRequest<T> {
678 client: Client,
679 resource_key: i64,
680 operation_reference: Option<u64>,
681 _state: std::marker::PhantomData<T>,
682}
683
684impl<T: DeleteResourceRequestState> DeleteResourceRequest<T> {
685 pub(crate) fn new(client: Client) -> DeleteResourceRequest<Initial> {
686 DeleteResourceRequest {
687 client,
688 resource_key: 0,
689 operation_reference: None,
690 _state: std::marker::PhantomData,
691 }
692 }
693
694 fn transition<NewState: DeleteResourceRequestState>(self) -> DeleteResourceRequest<NewState> {
695 DeleteResourceRequest {
696 client: self.client,
697 resource_key: self.resource_key,
698 operation_reference: self.operation_reference,
699 _state: std::marker::PhantomData,
700 }
701 }
702}
703
704impl DeleteResourceRequest<Initial> {
705 /// Sets the key of the resource to delete
706 ///
707 /// # Arguments
708 /// * `resource_key` - Key of process, decision, or form to delete
709 pub fn with_resource_key(mut self, resource_key: i64) -> DeleteResourceRequest<WithKey> {
710 self.resource_key = resource_key;
711 self.transition()
712 }
713}
714
715impl DeleteResourceRequest<WithKey> {
716 /// Sends the delete resource request to the gateway
717 ///
718 /// # Errors
719 /// - NOT_FOUND: No resource exists with given key
720 pub async fn send(mut self) -> Result<DeleteResourceResponse, ClientError> {
721 let res = self
722 .client
723 .gateway_client
724 .delete_resource(proto::DeleteResourceRequest {
725 resource_key: self.resource_key,
726 operation_reference: self.operation_reference,
727 })
728 .await?;
729
730 Ok(res.into_inner().into())
731 }
732
733 /// Sets a reference ID to correlate this operation with other events
734 ///
735 /// # Arguments
736 /// * `operation_reference` - Unique identifier for correlation
737 pub fn with_operation_reference(mut self, operation_reference: u64) -> Self {
738 self.operation_reference = Some(operation_reference);
739 self
740 }
741}
742
743/// Empty response since delete resource operation is fire-and-forget
744#[derive(Debug, Clone)]
745pub struct DeleteResourceResponse {}
746
747impl From<proto::DeleteResourceResponse> for DeleteResourceResponse {
748 fn from(_value: proto::DeleteResourceResponse) -> DeleteResourceResponse {
749 DeleteResourceResponse {}
750 }
751}