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