liminal_cli/network/
directory.rs1use serde_json::{Value, json};
6
7use super::Rail;
8use crate::mcp::tools::ToolResult;
9
10pub fn list<R: Rail>(rail: &R) -> ToolResult {
12 let record = match rail.topology() {
13 Ok(record) => record,
14 Err(error) => return ToolResult::failed(&error.to_string(), json!({})),
15 };
16 let stations: Vec<Value> = stations_of(&record)
17 .iter()
18 .map(|station| {
19 let mut listed = json!({
20 "id": station.get("id").cloned().unwrap_or(Value::Null),
21 "name": station.get("name").cloned().unwrap_or(Value::Null),
22 "device": station.get("device").cloned().unwrap_or(Value::Null),
23 "kind": station.get("kind").cloned().unwrap_or(Value::Null),
24 "state": station.get("state").cloned().unwrap_or(Value::Null),
25 });
26 if let Some(subjects) = station.get("subjects") {
27 listed["subjects"] = subjects.clone();
28 }
29 if let Some(activities) = station.get("activities").and_then(Value::as_array) {
30 listed["activities"] = activities
31 .iter()
32 .filter_map(|activity| activity.get("name").cloned())
33 .collect();
34 }
35 listed
36 })
37 .collect();
38 ToolResult::ok(json!({
39 "rev": record.get("rev").cloned().unwrap_or(Value::Null),
40 "stations": stations,
41 }))
42}
43
44pub fn describe<R: Rail>(rail: &R, subject: &str) -> ToolResult {
46 let record = match rail.topology() {
47 Ok(record) => record,
48 Err(error) => return ToolResult::failed(&error.to_string(), json!({})),
49 };
50 let served = stations_of(&record).iter().find(|station| {
51 station
52 .get("subjects")
53 .and_then(Value::as_array)
54 .is_some_and(|subjects| subjects.iter().any(|s| s.as_str() == Some(subject)))
55 });
56 served.map_or_else(
57 || ToolResult::failed(&format!("no station serves {subject}"), json!({})),
58 |station| {
59 ToolResult::ok(json!({
60 "rev": record.get("rev").cloned().unwrap_or(Value::Null),
61 "subject": subject,
62 "station": station.get("id").cloned().unwrap_or(Value::Null),
63 "name": station.get("name").cloned().unwrap_or(Value::Null),
64 "device": station.get("device").cloned().unwrap_or(Value::Null),
65 "activities": station.get("activities").cloned().unwrap_or_else(|| json!([])),
66 }))
67 },
68 )
69}
70
71fn stations_of(record: &Value) -> &[Value] {
72 record
73 .get("stations")
74 .and_then(Value::as_array)
75 .map_or(&[], Vec::as_slice)
76}
77
78#[cfg(test)]
79#[path = "directory_tests.rs"]
80mod tests;