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}