Skip to main content

ta_changeset/output_adapters/
json.rs

1//! json.rs — JSON output adapter for machine-readable output.
2
3use crate::error::ChangeSetError;
4use crate::output_adapters::{OutputAdapter, RenderContext};
5
6#[derive(Default)]
7pub struct JsonAdapter {}
8
9impl JsonAdapter {
10    pub fn new() -> Self {
11        Self {}
12    }
13}
14
15impl OutputAdapter for JsonAdapter {
16    fn render(&self, ctx: &RenderContext) -> Result<String, ChangeSetError> {
17        // For JSON output, we serialize the entire PRPackage
18        // The detail_level and file_filters are ignored — the consumer can filter client-side
19
20        let json = serde_json::to_string_pretty(ctx.package).map_err(|e| {
21            ChangeSetError::InvalidData(format!("JSON serialization failed: {}", e))
22        })?;
23
24        Ok(json)
25    }
26
27    fn name(&self) -> &str {
28        "json"
29    }
30}
31
32#[cfg(test)]
33mod tests {
34    use super::*;
35    use crate::output_adapters::DetailLevel;
36    use crate::pr_package::*;
37    use chrono::Utc;
38    use uuid::Uuid;
39
40    #[test]
41    fn renders_valid_json() {
42        let package = PRPackage {
43            package_version: "1.0.0".to_string(),
44            package_id: Uuid::new_v4(),
45            created_at: Utc::now(),
46            goal: Goal {
47                goal_id: "goal-1".to_string(),
48                title: "Test".to_string(),
49                objective: "Test".to_string(),
50                success_criteria: vec![],
51                constraints: vec![],
52                parent_goal_title: None,
53            },
54            iteration: Iteration {
55                iteration_id: "iter-1".to_string(),
56                sequence: 1,
57                workspace_ref: WorkspaceRef {
58                    ref_type: "staging".to_string(),
59                    ref_name: "staging/1".to_string(),
60                    base_ref: None,
61                },
62            },
63            agent_identity: AgentIdentity {
64                agent_id: "agent-1".to_string(),
65                agent_type: "coder".to_string(),
66                constitution_id: "default".to_string(),
67                capability_manifest_hash: "hash".to_string(),
68                orchestrator_run_id: None,
69            },
70            summary: Summary {
71                what_changed: "Test".to_string(),
72                why: "Test".to_string(),
73                impact: "None".to_string(),
74                rollback_plan: "Revert".to_string(),
75                open_questions: vec![],
76                alternatives_considered: vec![],
77            },
78            plan: Plan {
79                completed_steps: vec![],
80                next_steps: vec![],
81                decision_log: vec![],
82            },
83            changes: Changes {
84                artifacts: vec![],
85                patch_sets: vec![],
86                pending_actions: vec![],
87            },
88            risk: Risk {
89                risk_score: 0,
90                findings: vec![],
91                policy_decisions: vec![],
92            },
93            provenance: Provenance {
94                inputs: vec![],
95                tool_trace_hash: "hash".to_string(),
96            },
97            review_requests: ReviewRequests {
98                requested_actions: vec![],
99                reviewers: vec![],
100                required_approvals: 1,
101                notes_to_reviewer: None,
102            },
103            signatures: Signatures {
104                package_hash: "hash".to_string(),
105                agent_signature: "sig".to_string(),
106                gateway_attestation: None,
107            },
108            status: PRStatus::Draft,
109            verification_warnings: vec![],
110            validation_log: vec![],
111            display_id: None,
112            tag: None,
113            vcs_status: None,
114            parent_draft_id: None,
115            pending_approvals: vec![],
116            supervisor_review: None,
117            ignored_artifacts: vec![],
118            baseline_artifacts: vec![],
119            agent_decision_log: vec![],
120            goal_shortref: None,
121            draft_seq: 0,
122            plan_phase: None,
123        };
124
125        let adapter = JsonAdapter::new();
126        let ctx = RenderContext {
127            package: &package,
128            detail_level: DetailLevel::Full,
129            file_filters: vec![],
130            diff_provider: None,
131            section_filter: None,
132        };
133
134        let output = adapter.render(&ctx).unwrap();
135        assert!(serde_json::from_str::<serde_json::Value>(&output).is_ok());
136        assert!(output.contains("package_version"));
137        assert!(output.contains("package_id"));
138    }
139}