Skip to main content

dex_sdk/client/
flow_definitions.rs

1// Copyright (c) 2026 Super Durable, Inc.
2//
3// Licensed under the Sustainable Use License 1.0.
4// You may not use this file except in compliance with the License.
5// See the LICENSE file in the repository root.
6//
7// SPDX-License-Identifier: LicenseRef-Sustainable-Use-1.0
8
9use crate::flow_definition::{
10    map_upload_results, rewrite_source_paths, upload_requests, validate_document,
11};
12use crate::sdk_error::FlowTargetRequirement;
13use crate::{Client, FlowDefinitionDocument, FlowDefinitionUploadResult, SdkError, SdkResult};
14
15const UPLOAD_FLOW_DEFINITIONS: &str = "upload_flow_definitions";
16
17impl Client {
18    /// Uploads Flow Definition Graph documents so Dex Web can draw their Flow types.
19    ///
20    /// Use this from a deploy or CI script. Workers upload their configured documents at start;
21    /// see [`crate::WorkerOptions::flow_definition_paths`]. The Dex Server decides each Flow type
22    /// separately. It stores a document for a new Flow type, keeps an identical one, and replaces
23    /// a different one only when the uploaded generation time is strictly later. With `force`, a
24    /// different document always replaces the stored one; use it to roll a definition back on
25    /// purpose. Before sending, an absolute `source.path` in a document is replaced by a path
26    /// relative to the working directory, or by the file name when the file is elsewhere, exactly
27    /// as a Worker does. Documents are sent in requests of at most 256 documents and 16 MiB of
28    /// content; a larger document goes alone. A failed request returns an error; documents in earlier requests stay stored, and repeating the call is safe. An empty
29    /// list sends nothing.
30    ///
31    /// Returns one result for each document, in order.
32    ///
33    /// Since 1.45.0.
34    ///
35    /// ```no_run
36    /// use std::time::{Duration, SystemTime};
37    /// use dex_sdk::{Client, FlowDefinitionDocument, FlowDefinitionUploadOutcome, SdkResult};
38    ///
39    /// fn upload(client: &Client, content: Vec<u8>) -> SdkResult<()> {
40    ///     let generated_at = SystemTime::UNIX_EPOCH + Duration::from_secs(1_793_491_200);
41    ///     let document = FlowDefinitionDocument::new("OrderFlow", content, generated_at);
42    ///     for result in client.upload_flow_definitions([document], false)? {
43    ///         if result.outcome() == FlowDefinitionUploadOutcome::KeptExisting {
44    ///             eprintln!("{} kept {}", result.flow_type(), result.stored_digest());
45    ///         }
46    ///     }
47    ///     Ok(())
48    /// }
49    /// ```
50    ///
51    /// # Errors
52    ///
53    /// Returns [`SdkError::InvalidArgument`] before any request when a document is not a UTF-8 JSON
54    /// object whose `schemaVersion` starts with `2.` and whose `flow.name` equals its Flow type.
55    /// Returns a service-backed
56    /// [`SdkError`] when Dex rejects a request, for example with `INVALID_ARGUMENT` for an invalid
57    /// document, or with `UNIMPLEMENTED` on a Server without Flow definition storage.
58    pub fn upload_flow_definitions(
59        &self,
60        documents: impl IntoIterator<Item = FlowDefinitionDocument>,
61        force: bool,
62    ) -> SdkResult<Vec<FlowDefinitionUploadResult>> {
63        let documents = documents.into_iter().collect::<Vec<_>>();
64        if documents.is_empty() {
65            return Ok(Vec::new());
66        }
67        for document in &documents {
68            validate_document(document).map_err(|message| SdkError::InvalidArgument { message })?;
69        }
70        let working_directory = crate::flow_definition::current_working_directory();
71        let prepared = rewrite_source_paths(documents, working_directory.as_deref())?;
72        self.require_server_protocol(UPLOAD_FLOW_DEFINITIONS)?;
73        let mut results = Vec::new();
74        for request in upload_requests(prepared, force) {
75            let mut service = self.service.clone();
76            let response = self
77                .runtime
78                .block_on(service.upload_flow_definitions(request))
79                .map_err(|status| {
80                    SdkError::from_status(
81                        status,
82                        UPLOAD_FLOW_DEFINITIONS,
83                        None,
84                        FlowTargetRequirement::None,
85                    )
86                })?;
87            results.extend(map_upload_results(response.into_inner()));
88        }
89        Ok(results)
90    }
91}