Skip to main content

devboy_yougile/
enricher.rs

1//! YouGile schema enricher.
2
3use devboy_core::{ToolCategory, ToolEnricher, ToolSchema};
4use serde_json::Value;
5
6use crate::metadata::YouGileMetadata;
7
8/// Schema enricher for YouGile issue tools.
9///
10/// Uses board/column metadata to refine shared issue-tool schemas with
11/// YouGile-specific status values and supported filters.
12pub struct YouGileSchemaEnricher {
13    metadata: YouGileMetadata,
14}
15
16impl YouGileSchemaEnricher {
17    pub fn new(metadata: YouGileMetadata) -> Self {
18        Self { metadata }
19    }
20}
21
22impl ToolEnricher for YouGileSchemaEnricher {
23    fn supported_categories(&self) -> &[ToolCategory] {
24        &[ToolCategory::IssueTracker]
25    }
26
27    fn enrich_schema(&self, tool_name: &str, schema: &mut ToolSchema) {
28        const REMOVE_PARAMS: &[&str] = &["issueType", "components", "projectId", "points"];
29        const GET_ISSUES_REMOVE_PARAMS: &[&str] = &["projectKey", "nativeQuery"];
30
31        schema.remove_params(REMOVE_PARAMS);
32
33        if tool_name == "get_issues" {
34            schema.remove_params(GET_ISSUES_REMOVE_PARAMS);
35            schema.add_enum_param(
36                "stateCategory",
37                &["backlog", "todo", "in_progress", "done", "cancelled"],
38                "Filter by semantic status category. Maps YouGile board columns to shared status buckets using column-title heuristics.",
39            );
40            // The shared schema calls this a username, but YouGile's
41            // `assignedTo` matches on the user id only.
42            schema.set_description(
43                "assignee",
44                "Filter by assignee **user id** (UUID) — YouGile does not match names or emails",
45            );
46            // A YouGile task exposes a single timestamp, surfaced as both
47            // created_at and updated_at, so both sorts resolve to it.
48            schema.set_description(
49                "sort_by",
50                "YouGile stores one timestamp per task; `created_at` and `updated_at` order by it identically",
51            );
52        }
53
54        let statuses: Vec<String> = self
55            .metadata
56            .columns
57            .iter()
58            .map(|column| column.title.clone())
59            .collect();
60        if !statuses.is_empty() {
61            schema.set_enum("status", &statuses);
62            schema.set_description(
63                "status",
64                &format!(
65                    "Filter by exact board column title. Available: {}",
66                    statuses.join(", ")
67                ),
68            );
69        }
70    }
71
72    fn transform_args(&self, _tool_name: &str, _args: &mut Value) {}
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use crate::metadata::{YouGileBoard, YouGileColumn};
79
80    #[test]
81    fn enrich_schema_sets_status_enum_and_state_category() {
82        let enricher = YouGileSchemaEnricher::new(YouGileMetadata {
83            boards: vec![YouGileBoard {
84                id: "board-1".to_string(),
85                title: "Platform".to_string(),
86            }],
87            columns: vec![
88                YouGileColumn {
89                    id: "col-1".to_string(),
90                    title: "Backlog".to_string(),
91                    board_id: Some("board-1".to_string()),
92                },
93                YouGileColumn {
94                    id: "col-2".to_string(),
95                    title: "In Progress".to_string(),
96                    board_id: Some("board-1".to_string()),
97                },
98            ],
99        });
100
101        let mut schema = ToolSchema::from_json(&serde_json::json!({
102            "type": "object",
103            "properties": {
104                "status": { "type": "string" },
105                "projectKey": { "type": "string" }
106            }
107        }));
108
109        enricher.enrich_schema("get_issues", &mut schema);
110
111        assert_eq!(
112            schema.properties["status"].enum_values.as_ref().unwrap(),
113            &vec!["Backlog".to_string(), "In Progress".to_string()]
114        );
115        assert!(schema.properties.contains_key("stateCategory"));
116        assert!(!schema.properties.contains_key("projectKey"));
117    }
118}