1use serde::Deserialize;
17
18use crate::build_spec::PackageKind;
19use crate::error::GomodError;
20
21#[derive(Clone, Debug, Deserialize)]
25#[serde(rename_all = "PascalCase")]
26pub struct GoListPackage {
27 pub dir: String,
29 pub import_path: String,
31 #[serde(default)]
33 pub name: String,
34 #[serde(default)]
38 pub root: String,
39 #[serde(default)]
42 pub standard: bool,
43 #[serde(default)]
46 pub dep_only: bool,
47 #[serde(default)]
49 pub go_files: Vec<String>,
50 #[serde(default)]
53 pub cgo_files: Vec<String>,
54 #[serde(default)]
57 pub s_files: Vec<String>,
58 #[serde(default)]
60 pub embed_patterns: Vec<String>,
61 #[serde(default)]
63 pub embed_files: Vec<String>,
64 #[serde(default)]
68 pub imports: Vec<String>,
69 #[serde(default)]
71 pub import_map: std::collections::BTreeMap<String, String>,
72 #[serde(default)]
74 pub module: Option<GoListModule>,
75}
76
77#[derive(Clone, Debug, Deserialize)]
78#[serde(rename_all = "PascalCase")]
79pub struct GoListModule {
80 #[serde(default)]
81 pub path: String,
82 #[serde(default)]
84 pub version: Option<String>,
85 #[serde(default)]
87 pub main: bool,
88 #[serde(default)]
89 pub go_version: Option<String>,
90}
91
92impl GoListPackage {
93 #[must_use]
96 pub fn kind(&self) -> PackageKind {
97 if self.standard {
98 PackageKind::Std
99 } else if self.name == "main" {
100 PackageKind::Main
101 } else {
102 PackageKind::Module
103 }
104 }
105}
106
107pub fn parse_stream(json: &str) -> Result<Vec<GoListPackage>, GomodError> {
110 let de = serde_json::Deserializer::from_str(json);
111 let mut out = Vec::new();
112 for item in de.into_iter::<GoListPackage>() {
113 let pkg = item.map_err(|e| GomodError::GoListParse(e.to_string()))?;
114 out.push(pkg);
115 }
116 Ok(out)
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 const STREAM: &str = r#"
126{
127 "Dir": "/w/internal/greet",
128 "ImportPath": "example.com/fix/internal/greet",
129 "Name": "greet",
130 "Root": "/w",
131 "DepOnly": true,
132 "GoFiles": ["greet.go", "greet_linux.go"],
133 "EmbedPatterns": ["banner.txt"],
134 "EmbedFiles": ["banner.txt"],
135 "Imports": ["embed", "fmt"],
136 "Module": {"Path": "example.com/fix", "Main": true, "GoVersion": "1.25"}
137}
138{
139 "Dir": "/w/cmd/hello",
140 "ImportPath": "example.com/fix/cmd/hello",
141 "Name": "main",
142 "Root": "/w",
143 "GoFiles": ["main.go"],
144 "Imports": ["example.com/fix/internal/greet"],
145 "Module": {"Path": "example.com/fix", "Main": true}
146}
147{
148 "Dir": "/goroot/src/fmt",
149 "ImportPath": "fmt",
150 "Name": "fmt",
151 "Root": "/goroot/src",
152 "Standard": true,
153 "DepOnly": true,
154 "GoFiles": ["print.go"]
155}
156"#;
157
158 #[test]
159 fn parses_concatenated_object_stream() {
160 let pkgs = parse_stream(STREAM).expect("stream parses");
161 assert_eq!(pkgs.len(), 3);
162 let greet = &pkgs[0];
163 assert_eq!(greet.import_path, "example.com/fix/internal/greet");
164 assert_eq!(greet.kind(), PackageKind::Module);
165 assert!(greet.dep_only);
166 assert_eq!(greet.embed_files, vec!["banner.txt"]);
167 assert_eq!(pkgs[1].kind(), PackageKind::Main);
168 assert_eq!(pkgs[2].kind(), PackageKind::Std);
169 assert!(pkgs[2].standard);
170 }
171
172 #[test]
173 fn empty_stream_is_empty_vec() {
174 assert!(parse_stream("").unwrap().is_empty());
175 assert!(parse_stream(" \n").unwrap().is_empty());
176 }
177
178 #[test]
179 fn malformed_stream_errors_not_panics() {
180 let err = parse_stream("{not json}").unwrap_err();
181 assert!(matches!(err, GomodError::GoListParse(_)));
182 }
183}