Skip to main content

stepflow_flow/workflow/
component.rs

1// Copyright 2025 DataStax Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
4// in compliance with the License. You may obtain a copy of the License at
5//
6//     http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software distributed under the License
9// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
10// or implied. See the License for the specific language governing permissions and limitations under
11// the License.
12
13use serde::{Deserialize, Serialize};
14
15/// Identifies a specific plugin and atomic functionality to execute.
16///
17/// A component is identified by a path that specifies:
18/// - The plugin name
19/// - The component name within that plugin
20/// - Optional sub-path for specific functionality
21#[derive(Debug, Eq, PartialEq, Clone, Hash, Serialize, Deserialize, Ord, PartialOrd)]
22#[repr(transparent)]
23pub struct Component(String);
24
25impl schemars::JsonSchema for Component {
26    fn schema_name() -> std::borrow::Cow<'static, str> {
27        "Component".into()
28    }
29
30    fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
31        schemars::json_schema!({
32            "type": "string",
33            "description": "Identifies a specific plugin and atomic functionality to execute. Use component name for builtins (e.g., 'eval') or path format for plugins (e.g., '/python/udf').",
34            "examples": ["/builtin/eval", "/mcpfs/list_files", "/python/udf"]
35        })
36    }
37}
38
39impl std::fmt::Display for Component {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        write!(f, "{}", self.0)
42    }
43}
44
45impl Component {
46    /// Creates a new component for a specific plugin.
47    pub fn for_plugin(plugin: &str, component_path: &str) -> Self {
48        debug_assert!(
49            component_path.starts_with('/'),
50            "component_path must start with '/'"
51        );
52        Self(format!("/{plugin}{component_path}"))
53    }
54
55    /// Creates a component from a string, handling both paths and builtin names.
56    pub fn from_string(input: impl Into<String>) -> Self {
57        Self(input.into())
58    }
59
60    /// Returns the full path string of the component.
61    pub fn path(&self) -> &str {
62        &self.0
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn test_component_new() {
72        let component = Component::for_plugin("mock", "/test");
73        assert_eq!(component.path(), "/mock/test");
74    }
75
76    #[test]
77    fn test_component_serialization() {
78        let component = Component::from_string("/mock/test");
79        let json = serde_json::to_string(&component).unwrap();
80        assert_eq!(json, "\"/mock/test\"");
81    }
82
83    #[test]
84    fn test_component_deserialization() {
85        // Test path deserialization
86        let component: Component = serde_json::from_str("\"/mock/test\"").unwrap();
87        assert_eq!(component.path(), "/mock/test");
88    }
89
90    #[test]
91    fn test_new_plugin() {
92        let component = Component::for_plugin("mcp", "/tool/component");
93        assert_eq!(component.path(), "/mcp/tool/component");
94    }
95}