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}