Skip to main content

flow_execution/
service.rs

1//! Service-integration descriptor - the declarative contract a `service` node
2//! carries so the generic [`crate::Adapter`] named `service` can call an
3//! external API.
4//!
5//! The **schema here is vendor-neutral**. The concrete service (its base URL,
6//! auth parameters, operation paths) is **data authored in the node catalog**
7//! (`node_catalog.json` - the Nodes-Hub payload), never code. This keeps Flow
8//! Studio unbranded at the source level while letting operators ship as many
9//! real integrations as they like.
10
11use serde::{Deserialize, Serialize};
12
13/// Declarative API-integration descriptor attached to a `service` catalog entry.
14#[derive(Debug, Clone, Serialize, Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub struct ServiceIntegration {
17    /// Base URL of the service API; every operation `path` resolves against it.
18    pub base_url: String,
19    /// How the adapter authenticates to the service.
20    pub auth: ServiceAuth,
21    /// Operations ("features") the service exposes. A node selects one by id.
22    #[serde(default)]
23    pub operations: Vec<ServiceOperation>,
24}
25
26/// Auth contract for a service integration. `scheme` selects the strategy; the
27/// remaining fields are the declarative parameters that strategy needs.
28#[derive(Debug, Clone, Serialize, Deserialize)]
29#[serde(rename_all = "camelCase")]
30pub struct ServiceAuth {
31    /// One of `oauth2 | bearer | basic | api_key`.
32    pub scheme: String,
33    /// OAuth2 authorization endpoint (scheme == `oauth2`).
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    pub auth_url: Option<String>,
36    /// OAuth2 token endpoint (scheme == `oauth2`).
37    #[serde(default, skip_serializing_if = "Option::is_none")]
38    pub token_url: Option<String>,
39    /// OAuth2 scopes requested at consent.
40    #[serde(default)]
41    pub scopes: Vec<String>,
42    /// Request header carrying the credential for `api_key` / `bearer`
43    /// (e.g. `Authorization`, `X-Api-Key`). Defaults to `Authorization`.
44    #[serde(default, skip_serializing_if = "Option::is_none")]
45    pub header: Option<String>,
46    /// Settings key holding the operator-supplied OAuth client id (scheme == `oauth2`).
47    #[serde(default, skip_serializing_if = "Option::is_none")]
48    pub client_id_setting: Option<String>,
49    /// Optional provider console URL where the user obtains the credentials
50    /// (e.g. the OAuth client id + secret). Shown as a help link in Settings.
51    /// A branded URL - authored as catalog data, never hard-coded in the UI.
52    #[serde(default, skip_serializing_if = "Option::is_none")]
53    pub credentials_help_url: Option<String>,
54}
55
56impl ServiceAuth {
57    /// The header carrying the credential, defaulting to `Authorization`.
58    pub fn header_name(&self) -> &str {
59        self.header.as_deref().unwrap_or("Authorization")
60    }
61}
62
63/// One callable operation exposed by a service integration.
64#[derive(Debug, Clone, Serialize, Deserialize)]
65#[serde(rename_all = "camelCase")]
66pub struct ServiceOperation {
67    /// Stable id selected by the node's `operation` field.
68    pub id: String,
69    /// Human label shown in the inspector.
70    pub label: String,
71    /// HTTP method (GET/POST/PUT/PATCH/DELETE).
72    pub method: String,
73    /// Path template appended to `base_url`; `{param}` placeholders are filled
74    /// from `node.data`.
75    pub path: String,
76    /// Query-string params read from `node.data`.
77    #[serde(default)]
78    pub query: Vec<String>,
79    /// Body params read from `node.data` (for write methods).
80    #[serde(default)]
81    pub body_params: Vec<String>,
82}
83
84impl ServiceIntegration {
85    /// Find an operation by its id.
86    pub fn operation(&self, id: &str) -> Option<&ServiceOperation> {
87        self.operations.iter().find(|o| o.id == id)
88    }
89}