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}