Skip to main content

apcore_cli/
display_helpers.rs

1// apcore-cli -- Display overlay helpers for grouped commands.
2// Protocol spec: FE-09 (display overlay resolution)
3
4use serde_json::Value;
5
6/// Extract the resolved display overlay from a module's metadata.
7pub fn get_display(descriptor: &Value) -> Value {
8    descriptor
9        .get("metadata")
10        .and_then(|m| m.get("display"))
11        .cloned()
12        .unwrap_or(Value::Null)
13}
14
15/// Return (display_name, description, tags) resolved from the
16/// display overlay with CLI-specific fallback chain.
17pub fn get_cli_display_fields(descriptor: &Value) -> (String, String, Vec<String>) {
18    let display = get_display(descriptor);
19    let cli = display.get("cli").unwrap_or(&Value::Null);
20
21    let name = cli
22        .get("alias")
23        .and_then(|v| v.as_str())
24        .or_else(|| display.get("alias").and_then(|v| v.as_str()))
25        .or_else(|| descriptor.get("id").and_then(|v| v.as_str()))
26        .or_else(|| descriptor.get("module_id").and_then(|v| v.as_str()))
27        .unwrap_or("")
28        .to_string();
29
30    let desc = cli
31        .get("description")
32        .and_then(|v| v.as_str())
33        .or_else(|| descriptor.get("description").and_then(|v| v.as_str()))
34        .unwrap_or("")
35        .to_string();
36
37    let tags = display
38        .get("tags")
39        .and_then(|v| v.as_array())
40        .map(|arr| {
41            arr.iter()
42                .filter_map(|v| v.as_str().map(String::from))
43                .collect()
44        })
45        .or_else(|| {
46            descriptor
47                .get("tags")
48                .and_then(|v| v.as_array())
49                .map(|arr| {
50                    arr.iter()
51                        .filter_map(|v| v.as_str().map(String::from))
52                        .collect()
53                })
54        })
55        .unwrap_or_default();
56
57    (name, desc, tags)
58}
59
60// -------------------------------------------------------------------
61// Unit tests
62// -------------------------------------------------------------------
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use serde_json::json;
68
69    #[test]
70    fn test_get_display_returns_display_from_metadata() {
71        let descriptor = json!({
72            "metadata": {
73                "display": {
74                    "alias": "greet",
75                    "tags": ["demo"]
76                }
77            }
78        });
79        let display = get_display(&descriptor);
80        assert_eq!(display["alias"], "greet");
81    }
82
83    #[test]
84    fn test_get_display_returns_null_when_missing() {
85        let descriptor = json!({"module_id": "a.b"});
86        assert!(get_display(&descriptor).is_null());
87    }
88
89    #[test]
90    fn test_get_display_returns_null_when_no_display_key() {
91        let descriptor = json!({"metadata": {"version": "1.0"}});
92        assert!(get_display(&descriptor).is_null());
93    }
94
95    #[test]
96    fn test_cli_display_fields_cli_alias_wins() {
97        let descriptor = json!({
98            "module_id": "math.add",
99            "metadata": {
100                "display": {
101                    "alias": "top-alias",
102                    "cli": { "alias": "cli-alias" }
103                }
104            }
105        });
106        let (name, _, _) = get_cli_display_fields(&descriptor);
107        assert_eq!(name, "cli-alias");
108    }
109
110    #[test]
111    fn test_cli_display_fields_display_alias_fallback() {
112        let descriptor = json!({
113            "module_id": "math.add",
114            "metadata": {
115                "display": { "alias": "top-alias" }
116            }
117        });
118        let (name, _, _) = get_cli_display_fields(&descriptor);
119        assert_eq!(name, "top-alias");
120    }
121
122    #[test]
123    fn test_cli_display_fields_id_fallback() {
124        let descriptor = json!({"id": "my-id"});
125        let (name, _, _) = get_cli_display_fields(&descriptor);
126        assert_eq!(name, "my-id");
127    }
128
129    #[test]
130    fn test_cli_display_fields_module_id_fallback() {
131        let descriptor = json!({"module_id": "math.add"});
132        let (name, _, _) = get_cli_display_fields(&descriptor);
133        assert_eq!(name, "math.add");
134    }
135
136    #[test]
137    fn test_cli_display_fields_empty_when_no_name() {
138        let descriptor = json!({});
139        let (name, _, _) = get_cli_display_fields(&descriptor);
140        assert_eq!(name, "");
141    }
142
143    #[test]
144    fn test_cli_display_fields_description_from_cli() {
145        let descriptor = json!({
146            "description": "top-desc",
147            "metadata": {
148                "display": {
149                    "cli": { "description": "cli-desc" }
150                }
151            }
152        });
153        let (_, desc, _) = get_cli_display_fields(&descriptor);
154        assert_eq!(desc, "cli-desc");
155    }
156
157    #[test]
158    fn test_cli_display_fields_description_fallback() {
159        let descriptor = json!({"description": "top-desc"});
160        let (_, desc, _) = get_cli_display_fields(&descriptor);
161        assert_eq!(desc, "top-desc");
162    }
163
164    #[test]
165    fn test_cli_display_fields_tags_from_display() {
166        let descriptor = json!({
167            "tags": ["a"],
168            "metadata": {
169                "display": { "tags": ["b", "c"] }
170            }
171        });
172        let (_, _, tags) = get_cli_display_fields(&descriptor);
173        assert_eq!(tags, vec!["b", "c"]);
174    }
175
176    #[test]
177    fn test_cli_display_fields_tags_fallback_to_descriptor() {
178        let descriptor = json!({"tags": ["x", "y"]});
179        let (_, _, tags) = get_cli_display_fields(&descriptor);
180        assert_eq!(tags, vec!["x", "y"]);
181    }
182
183    #[test]
184    fn test_cli_display_fields_tags_empty_default() {
185        let descriptor = json!({});
186        let (_, _, tags) = get_cli_display_fields(&descriptor);
187        assert!(tags.is_empty());
188    }
189}