use serde::Deserialize;
use crate::build_spec::PackageKind;
use crate::error::GomodError;
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct GoListPackage {
pub dir: String,
pub import_path: String,
#[serde(default)]
pub name: String,
#[serde(default)]
pub root: String,
#[serde(default)]
pub standard: bool,
#[serde(default)]
pub dep_only: bool,
#[serde(default)]
pub go_files: Vec<String>,
#[serde(default)]
pub cgo_files: Vec<String>,
#[serde(default)]
pub s_files: Vec<String>,
#[serde(default)]
pub embed_patterns: Vec<String>,
#[serde(default)]
pub embed_files: Vec<String>,
#[serde(default)]
pub imports: Vec<String>,
#[serde(default)]
pub import_map: std::collections::BTreeMap<String, String>,
#[serde(default)]
pub module: Option<GoListModule>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct GoListModule {
#[serde(default)]
pub path: String,
#[serde(default)]
pub version: Option<String>,
#[serde(default)]
pub main: bool,
#[serde(default)]
pub go_version: Option<String>,
}
impl GoListPackage {
#[must_use]
pub fn kind(&self) -> PackageKind {
if self.standard {
PackageKind::Std
} else if self.name == "main" {
PackageKind::Main
} else {
PackageKind::Module
}
}
}
pub fn parse_stream(json: &str) -> Result<Vec<GoListPackage>, GomodError> {
let de = serde_json::Deserializer::from_str(json);
let mut out = Vec::new();
for item in de.into_iter::<GoListPackage>() {
let pkg = item.map_err(|e| GomodError::GoListParse(e.to_string()))?;
out.push(pkg);
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
const STREAM: &str = r#"
{
"Dir": "/w/internal/greet",
"ImportPath": "example.com/fix/internal/greet",
"Name": "greet",
"Root": "/w",
"DepOnly": true,
"GoFiles": ["greet.go", "greet_linux.go"],
"EmbedPatterns": ["banner.txt"],
"EmbedFiles": ["banner.txt"],
"Imports": ["embed", "fmt"],
"Module": {"Path": "example.com/fix", "Main": true, "GoVersion": "1.25"}
}
{
"Dir": "/w/cmd/hello",
"ImportPath": "example.com/fix/cmd/hello",
"Name": "main",
"Root": "/w",
"GoFiles": ["main.go"],
"Imports": ["example.com/fix/internal/greet"],
"Module": {"Path": "example.com/fix", "Main": true}
}
{
"Dir": "/goroot/src/fmt",
"ImportPath": "fmt",
"Name": "fmt",
"Root": "/goroot/src",
"Standard": true,
"DepOnly": true,
"GoFiles": ["print.go"]
}
"#;
#[test]
fn parses_concatenated_object_stream() {
let pkgs = parse_stream(STREAM).expect("stream parses");
assert_eq!(pkgs.len(), 3);
let greet = &pkgs[0];
assert_eq!(greet.import_path, "example.com/fix/internal/greet");
assert_eq!(greet.kind(), PackageKind::Module);
assert!(greet.dep_only);
assert_eq!(greet.embed_files, vec!["banner.txt"]);
assert_eq!(pkgs[1].kind(), PackageKind::Main);
assert_eq!(pkgs[2].kind(), PackageKind::Std);
assert!(pkgs[2].standard);
}
#[test]
fn empty_stream_is_empty_vec() {
assert!(parse_stream("").unwrap().is_empty());
assert!(parse_stream(" \n").unwrap().is_empty());
}
#[test]
fn malformed_stream_errors_not_panics() {
let err = parse_stream("{not json}").unwrap_err();
assert!(matches!(err, GomodError::GoListParse(_)));
}
}