Skip to main content

noether_engine/index/
text.rs

1#![warn(clippy::unwrap_used)]
2#![cfg_attr(test, allow(clippy::unwrap_used))]
3
4use noether_core::stage::Stage;
5
6/// Generate text for the signature index: "input_type -> output_type".
7pub fn signature_text(stage: &Stage) -> String {
8    format!("{} -> {}", stage.signature.input, stage.signature.output)
9}
10
11/// Generate text for the semantic/description index.
12/// Aliases and tags are appended so that vocabulary-mismatched queries still
13/// match (e.g. "strlen" → `text_length`, "network" → all IO stages).
14pub fn description_text(stage: &Stage) -> String {
15    let mut parts = vec![stage.description.clone()];
16    if !stage.aliases.is_empty() {
17        parts.push(format!("Aliases: {}", stage.aliases.join(", ")));
18    }
19    if !stage.tags.is_empty() {
20        parts.push(format!("Tags: {}", stage.tags.join(", ")));
21    }
22    parts.join("\n")
23}
24
25/// Generate text for the example index: all input/output pairs.
26pub fn examples_text(stage: &Stage) -> String {
27    stage
28        .examples
29        .iter()
30        .map(|ex| format!("{} => {}", ex.input, ex.output))
31        .collect::<Vec<_>>()
32        .join("\n")
33}
34
35#[cfg(test)]
36mod tests {
37    use super::*;
38    use noether_core::effects::EffectSet;
39    use noether_core::stage::{CostEstimate, Example, StageId, StageLifecycle, StageSignature};
40    use noether_core::types::NType;
41    use std::collections::BTreeSet;
42
43    fn test_stage() -> Stage {
44        Stage {
45            id: StageId("test".into()),
46            signature_id: None,
47            signature: StageSignature {
48                input: NType::Text,
49                output: NType::Number,
50                effects: EffectSet::pure(),
51                implementation_hash: "impl".into(),
52            },
53            capabilities: BTreeSet::new(),
54            cost: CostEstimate {
55                time_ms_p50: None,
56                tokens_est: None,
57                memory_mb: None,
58            },
59            description: "Convert text to number".into(),
60            examples: vec![
61                Example {
62                    input: serde_json::json!("42"),
63                    output: serde_json::json!(42),
64                },
65                Example {
66                    input: serde_json::json!("3"),
67                    output: serde_json::json!(3),
68                },
69            ],
70            lifecycle: StageLifecycle::Active,
71            ed25519_signature: None,
72            signer_public_key: None,
73            implementation_code: None,
74            implementation_language: None,
75            ui_style: None,
76            tags: vec![],
77            aliases: vec![],
78            name: None,
79            properties: Vec::new(),
80        }
81    }
82
83    #[test]
84    fn signature_text_formats_types() {
85        let stage = test_stage();
86        assert_eq!(signature_text(&stage), "Text -> Number");
87    }
88
89    #[test]
90    fn description_text_returns_description() {
91        let stage = test_stage();
92        assert_eq!(description_text(&stage), "Convert text to number");
93    }
94
95    #[test]
96    fn description_text_appends_aliases_and_tags() {
97        let mut stage = test_stage();
98        stage.aliases = vec!["parse_float".into(), "atof".into()];
99        stage.tags = vec!["scalar".into(), "pure".into()];
100        let text = description_text(&stage);
101        assert!(text.starts_with("Convert text to number"));
102        assert!(text.contains("Aliases: parse_float, atof"));
103        assert!(text.contains("Tags: scalar, pure"));
104    }
105
106    #[test]
107    fn examples_text_formats_pairs() {
108        let stage = test_stage();
109        let text = examples_text(&stage);
110        assert!(text.contains("\"42\" => 42"));
111        assert!(text.contains("\"3\" => 3"));
112    }
113}