Skip to main content

product_os_connector/
lib.rs

1//! # Product OS Connector
2//!
3//! A flexible, configuration-driven framework for defining API servers, workflows, and authentication.
4//!
5//! ## Overview
6//!
7//! The `product-os-connector` crate provides a powerful abstraction for building API integration
8//! systems using declarative configuration rather than imperative code. It supports multiple
9//! protocol types (REST, GraphQL, WebSocket) and various authentication methods (`OAuth2`, JWT,
10//! API keys, etc.).
11//!
12//! ## Features
13//!
14//! - **`definition`**: Core flow definitions and routing (depends on `matchit`, `serde_json`, `regex`)
15//! - **`connectors`**: Full connector functionality with async support (depends on `async-trait`, `parking_lot`)
16//! - **`openapi`**: OpenAPI/Swagger integration for automatic API definition import
17//! - **`default`**: Enables all features with standard library support
18//!
19//! ## Architecture
20//!
21//! The crate is organized around several key concepts:
22//!
23//! - **Definition**: Declarative API specification (protocols, paths, auth, flows)
24//! - **Connector**: Runtime handler that processes inward/outward requests
25//! - **Interface**: Protocol-specific implementations (REST, GraphQL, WebSocket)
26//! - **Flows**: Inward (server endpoints) and Outward (client calls) request handling
27//! - **Authentication**: Pluggable auth methods with lock/unlock semantics
28//!
29//! ## Quick Start
30//!
31//! ```rust,no_run
32//! use product_os_connector::{Definition, ProductOSConnectors, ConnectorKind};
33//! use std::collections::BTreeMap;
34//!
35//! // Load or create connector definitions
36//! let mut definitions = BTreeMap::new();
37//! // definitions.insert("my-api".to_string(), my_definition);
38//!
39//! // Initialize connectors
40//! let connectors = ProductOSConnectors::new(definitions);
41//!
42//! // Register with router (requires product-os-router)
43//! // let mut router = ProductOSRouter::new();
44//! // connectors.setup_handlers(&mut router).await;
45//! ```
46//!
47//! ## Configuration-Driven Workflows
48//!
49//! Instead of writing code for each API endpoint, you define them in configuration:
50//!
51//! ```json
52//! {
53//!   "info": {
54//!     "identifier": "my-service",
55//!     "version": "1.0.0"
56//!   },
57//!   "kind": "rest",
58//!   "protocol": "https",
59//!   "addresses": ["api.example.com"],
60//!   "inward_root": "/api/v1",
61//!   "flows_inward": {
62//!     "/users": {
63//!       "methods": {
64//!         "get": {
65//!           "auth_method": "jwt",
66//!           "output": { /* mapping */ }
67//!         }
68//!       }
69//!     }
70//!   }
71//! }
72//! ```
73//!
74//! ## `no_std` Support
75//!
76//! This crate is `no_std` compatible via the `no-std-compat` facade, though async operations
77//! and networking features require an allocator.
78//!
79//! ## See Also
80//!
81//! - [`Definition`]: The core configuration structure
82//! - [`ProductOSConnectors`]: Main entry point for connector runtime
83//! - [`ConnectorKind`]: Supported protocol types
84
85#![no_std]
86#![warn(missing_docs)]
87#![warn(rust_2018_idioms)]
88#![warn(clippy::all)]
89#![warn(clippy::pedantic)]
90#![allow(clippy::module_name_repetitions)]
91#![allow(clippy::missing_errors_doc)]
92#![allow(clippy::missing_panics_doc)]
93
94extern crate no_std_compat as std;
95
96use std::prelude::v1::*;
97
98#[cfg(feature = "definition")]
99mod definition;
100
101#[cfg(all(feature = "connectors", feature = "definition"))]
102mod authentication;
103#[cfg(all(feature = "connectors", feature = "definition"))]
104mod rest;
105#[cfg(all(feature = "connectors", feature = "definition"))]
106pub mod connector;
107#[cfg(all(feature = "connectors", feature = "definition"))]
108mod ws;
109#[cfg(all(feature = "connectors", feature = "definition"))]
110mod graphql;
111#[cfg(all(feature = "connectors", feature = "definition"))]
112mod interface;
113
114
115#[cfg(feature = "connectors")]
116use std::collections::BTreeMap;
117
118#[cfg(feature = "connectors")]
119use std::sync::Arc;
120
121#[cfg(feature = "connectors")]
122use std::time::Duration;
123
124#[cfg(feature = "connectors")]
125use product_os_capabilities::{Feature, RegistryFeature, What};
126use serde::{Deserialize, Serialize};
127
128#[cfg(feature = "definition")]
129pub use crate::definition::Definition;
130
131#[cfg(feature = "connectors")]
132use async_trait::async_trait;
133
134#[cfg(feature = "connectors")]
135use parking_lot::Mutex;
136
137#[cfg(feature = "connectors")]
138use product_os_router::{Body, IntoResponse, Response, StatusCode};
139
140#[cfg(all(feature = "connectors", feature = "definition"))]
141use crate::graphql::GraphQL;
142#[cfg(all(feature = "connectors", feature = "definition"))]
143use crate::interface::Interface;
144#[cfg(all(feature = "connectors", feature = "definition"))]
145use crate::rest::Rest;
146#[cfg(all(feature = "connectors", feature = "definition"))]
147use crate::ws::WebSocket;
148
149
150
151/// The type of connector protocol to use for API communication.
152///
153/// Each variant represents a different network protocol with its own semantics:
154///
155/// - **REST**: Traditional HTTP/HTTPS `RESTful` APIs with standard HTTP methods
156/// - **GraphQL**: Graph Query Language APIs with schema-based queries
157/// - **WebSocket**: Bidirectional, persistent connection protocols
158///
159/// # Examples
160///
161/// ```
162/// use product_os_connector::ConnectorKind;
163///
164/// let kind = ConnectorKind::Rest;
165/// assert_eq!(format!("{:?}", kind), "Rest");
166/// ```
167///
168/// # Serialization
169///
170/// Serializes as lowercase strings in JSON/YAML:
171/// ```json
172/// { "kind": "rest" }
173/// { "kind": "graphql" }
174/// { "kind": "websocket" }
175/// ```
176#[derive(Clone, Debug, Deserialize, Serialize)]
177#[serde(rename_all = "lowercase")]
178pub enum ConnectorKind {
179    /// REST/HTTP API connector
180    Rest,
181    /// GraphQL API connector
182    GraphQL,
183    /// WebSocket bidirectional connector
184    WebSocket,
185    // EventStream,
186    // MQTT,
187    // Kafka,
188    // Soap,
189}
190
191
192/// Central coordinator for managing multiple API connectors.
193///
194/// `ProductOSConnectors` handles the lifecycle of multiple connector instances, each with
195/// its own protocol type and configuration. It manages interface registration, request routing,
196/// and cross-connector communication.
197///
198/// # Architecture
199///
200/// Each connector is wrapped in an `Arc<Mutex<dyn Interface>>` to allow:
201/// - Thread-safe shared access
202/// - Dynamic dispatch across protocol types
203/// - Safe concurrent request handling
204///
205/// # Examples
206///
207/// ```no_run
208/// use product_os_connector::{ProductOSConnectors, Definition};
209/// use std::collections::BTreeMap;
210///
211/// # async fn example() {
212/// let definitions = BTreeMap::new();
213/// let connectors = ProductOSConnectors::new(definitions);
214///
215/// // Setup routing
216/// // let mut router = product_os_router::ProductOSRouter::new();
217/// // connectors.setup_handlers(&mut router).await;
218/// # }
219/// ```
220///
221/// # Inter-Connector Communication
222///
223/// Connectors can reference each other in workflow steps, enabling complex
224/// integration patterns like:
225/// - API Gateway → Backend Service
226/// - Service Mesh Communication
227/// - Webhook Forwarding
228#[cfg(all(feature = "connectors", feature = "definition"))]
229pub struct ProductOSConnectors {
230    /// Map of connector identifier to interface implementation
231    interfaces: BTreeMap<String, Arc<Mutex<dyn Interface>>>
232    //relational_store: Option<Arc<ProductOSRelationalStore>>,
233}
234
235#[cfg(all(feature = "connectors", feature = "definition"))]
236impl ProductOSConnectors {
237    /// Creates a new connector manager from a map of definitions.
238    ///
239    /// Each definition is instantiated into its appropriate protocol handler (REST, GraphQL,
240    /// or WebSocket) and stored for request routing. Connectors are cross-registered so they
241    /// can call each other in workflow steps.
242    ///
243    /// # Arguments
244    ///
245    /// * `predefined_definitions` - Map of connector identifiers to their definitions
246    ///
247    /// # Panics
248    ///
249    /// May panic if interface registration fails due to lock timeout (10 seconds).
250    ///
251    /// # Examples
252    ///
253    /// ```no_run
254    /// use product_os_connector::{ProductOSConnectors, Definition};
255    /// use std::collections::BTreeMap;
256    ///
257    /// let mut defs = BTreeMap::new();
258    /// // defs.insert("api-1".to_string(), definition1);
259    /// // defs.insert("api-2".to_string(), definition2);
260    ///
261    /// let connectors = ProductOSConnectors::new(defs);
262    /// ```
263    pub fn new(predefined_definitions: BTreeMap<String, Definition>, /*relational_store: Option<Arc<ProductOSRelationalStore>>*/) -> Self {
264        let mut interfaces: BTreeMap<String, Arc<Mutex<dyn Interface>>> = BTreeMap::new();
265
266        tracing::debug!("Definitions: {:?}", predefined_definitions);
267
268        for definition in predefined_definitions.values() {
269            match definition.kind {
270                ConnectorKind::Rest => {
271                    let rest = Rest::new(definition);
272                    interfaces.insert(definition.info.identifier.clone(), Arc::new(Mutex::new(rest)));
273                }
274                ConnectorKind::GraphQL => {
275                    let graph_ql = GraphQL::new(definition);
276                    interfaces.insert(definition.info.identifier.clone(), Arc::new(Mutex::new(graph_ql)));
277                }
278                ConnectorKind::WebSocket => {
279                    let web_socket = WebSocket::new(definition);
280                    interfaces.insert(definition.info.identifier.clone(), Arc::new(Mutex::new(web_socket)));
281                }
282            }
283        }
284
285        let interfaces_to_register = Arc::new(interfaces.clone());
286        for interface in interfaces.values() {
287            match interface.try_lock_for(Duration::from_secs(10)) {
288                None => {}
289                Some(mut interface) => {
290                    interface.register_interfaces(Some(interfaces_to_register.clone()));
291                }
292            }
293        }
294
295        Self {
296            interfaces,
297            //relational_store
298        }
299    }
300
301    /// Registers all connector handlers with the provided router.
302    ///
303    /// This method iterates through all managed connectors and registers their inward
304    /// endpoints with the router. Each connector adds its own routes based on its
305    /// configuration (inward root path, HTTP methods, etc.).
306    ///
307    /// # Arguments
308    ///
309    /// * `router` - The router instance to register handlers with
310    ///
311    /// # Async
312    ///
313    /// This is an async method because connectors may need to fetch remote definitions
314    /// (e.g., `OpenAPI` specs) during registration.
315    ///
316    /// # Examples
317    ///
318    /// ```no_run
319    /// # use product_os_connector::ProductOSConnectors;
320    /// # use std::collections::BTreeMap;
321    /// # async fn example(connectors: ProductOSConnectors) {
322    /// let mut router = product_os_router::ProductOSRouter::new();
323    /// connectors.setup_handlers(&mut router).await;
324    /// # }
325    /// ```
326    pub async fn setup_handlers(&self, router: &mut product_os_router::ProductOSRouter) {
327        for interface in self.interfaces.values() {
328            match interface.try_lock_for(Duration::from_secs(10)) {
329                None => {}
330                Some(mut interface) => {
331                    interface.register(router).await;
332                }
333            }
334        }
335    }
336}
337
338
339
340/*
341#[async_trait]
342impl Feature for ProductOSAuthentication {
343    fn identifier(&self) -> String {
344        "Authentication".to_string()
345    }
346
347    fn register(&self, feature: Arc<dyn Feature>, base_path: String, router: &mut product_os_router::ProductOSRouter) -> RegistryFeature {
348
349    }
350
351    async fn request(&self, request: Request<Body>, version: String) -> Response {
352
353    }
354
355    async fn request_mut(&mut self, request: Request<Body>, version: String) -> Response {
356
357    }
358}
359*/
360
361
362
363
364
365#[cfg(all(feature = "connectors", feature = "definition"))]
366#[async_trait]
367impl Feature for ProductOSConnectors {
368    fn identifier(&self) -> String {
369        "Connectors".to_string()
370    }
371
372    async fn register(&self, feature: Arc<dyn Feature>, base_path: String, router: &mut product_os_router::ProductOSRouter) -> RegistryFeature {
373        let shared_base_path = base_path.clone();
374
375        self.setup_handlers(router).await;
376
377        let mut path = shared_base_path;
378        path.push_str("/*sub_path");
379
380        RegistryFeature {
381            identifier: "Connectors".to_string(),
382            paths: vec!(path),
383            feature: Some(feature),
384            feature_mut: None
385        }
386    }
387
388    async fn register_mut(&self, _feature: Arc<Mutex<dyn Feature>>, _base_path: String, _router: &mut product_os_router::ProductOSRouter) -> RegistryFeature {
389        panic!("Mutable connector server not allowed to be registered")
390    }
391
392    async fn request(&self, _action: &What, _input: &Option<serde_json::Value>, _semver: &str) -> Response<Body> {
393        Response::builder()
394            .status(StatusCode::NOT_IMPLEMENTED)
395            .body(Body::from("{}"))
396            .unwrap().into_response()
397    }
398
399    async fn request_mut(&mut self, action: &What, input: &Option<serde_json::Value>, semver: &str) -> Response<Body> {
400        self.request(action, input, semver).await
401    }
402}
403
404
405