fallow_api/
list_output.rs1use fallow_output::{ListEntryPointOutput, ListOutput, ListPluginOutput, WorkspacesOutput};
4use serde::Serialize;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ListJsonEnvelope {
9 Plain,
11 Boundaries,
13 Workspaces,
15}
16
17pub struct ListJsonOutputInput<Boundaries, Diagnostic> {
19 pub plugins: Option<Vec<String>>,
21 pub files: Option<Vec<String>>,
23 pub entry_points: Option<Vec<ListEntryPointOutput>>,
26 pub boundaries: Option<Boundaries>,
28 pub entry_weight: Option<fallow_output::EntryWeightListing>,
30 pub workspaces: Option<WorkspacesOutput<Diagnostic>>,
33 pub plugin_diagnostics: Vec<Diagnostic>,
39}
40
41#[must_use]
43pub fn build_list_json_output<Boundaries, Diagnostic>(
44 input: ListJsonOutputInput<Boundaries, Diagnostic>,
45) -> ListOutput<Boundaries, Diagnostic> {
46 let plugins = input.plugins.map(|plugins| {
47 plugins
48 .into_iter()
49 .map(|name| ListPluginOutput { name })
50 .collect()
51 });
52 let file_count = input.files.as_ref().map(Vec::len);
53 let entry_point_count = input.entry_points.as_ref().map(Vec::len);
54 let (workspace_count, workspaces, mut workspace_diagnostics) =
55 input.workspaces.map_or((None, None, None), |workspaces| {
56 (
57 Some(workspaces.workspace_count),
58 Some(workspaces.workspaces),
59 Some(workspaces.workspace_diagnostics),
60 )
61 });
62 if !input.plugin_diagnostics.is_empty() {
63 workspace_diagnostics
64 .get_or_insert_with(Vec::new)
65 .extend(input.plugin_diagnostics);
66 }
67
68 ListOutput {
69 plugins,
70 file_count,
71 files: input.files,
72 entry_point_count,
73 entry_points: input.entry_points,
74 boundaries: input.boundaries,
75 entry_weight: input.entry_weight,
76 workspace_count,
77 workspaces,
78 workspace_diagnostics,
79 }
80}
81
82pub fn serialize_list_json_output<Boundaries, Diagnostic>(
89 input: ListJsonOutputInput<Boundaries, Diagnostic>,
90 envelope: ListJsonEnvelope,
91) -> Result<serde_json::Value, serde_json::Error>
92where
93 Boundaries: Serialize,
94 Diagnostic: Serialize,
95{
96 let output = build_list_json_output(input);
97 match envelope {
98 ListJsonEnvelope::Plain => serde_json::to_value(output),
99 ListJsonEnvelope::Boundaries => {
100 fallow_output::serialize_list_boundaries_json_output(output)
101 }
102 ListJsonEnvelope::Workspaces => {
103 fallow_output::serialize_list_workspaces_json_output(output)
104 }
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use fallow_output::ListEntryPointOutput;
111 use serde_json::json;
112
113 use super::*;
114
115 #[test]
116 fn list_json_output_preserves_plain_legacy_body() {
117 let value = serialize_list_json_output::<serde_json::Value, serde_json::Value>(
118 ListJsonOutputInput {
119 plugins: Some(vec!["react".to_string()]),
120 files: Some(vec!["src/index.ts".to_string()]),
121 entry_points: Some(vec![ListEntryPointOutput {
122 path: "src/index.ts".to_string(),
123 source: "package.json main".to_string(),
124 }]),
125 boundaries: None,
126 entry_weight: None,
127 workspaces: None,
128 plugin_diagnostics: Vec::new(),
129 },
130 ListJsonEnvelope::Plain,
131 )
132 .expect("list output should serialize");
133
134 assert_eq!(value["plugins"][0]["name"], "react");
135 assert_eq!(value["file_count"], 1);
136 assert_eq!(value["files"], json!(["src/index.ts"]));
137 assert_eq!(value["entry_point_count"], 1);
138 assert!(value.get("kind").is_none());
139 }
140
141 #[test]
142 fn list_json_output_wraps_boundary_payloads() {
143 let value = serialize_list_json_output::<serde_json::Value, serde_json::Value>(
144 ListJsonOutputInput {
145 plugins: None,
146 files: None,
147 entry_points: None,
148 boundaries: Some(json!({"configured": false})),
149 entry_weight: None,
150 workspaces: None,
151 plugin_diagnostics: Vec::new(),
152 },
153 ListJsonEnvelope::Boundaries,
154 )
155 .expect("list output should serialize");
156
157 assert_eq!(value["kind"], "list-boundaries");
158 assert_eq!(value["boundaries"]["configured"], false);
159 }
160}