posthog_cli/utils/
files.rs

1use anyhow::{Context, Result};
2use sha2::Digest;
3use std::path::PathBuf;
4
5use crate::sourcemaps::content::SourceMapContent;
6
7pub struct SourceFile<T: SourceContent> {
8    pub path: PathBuf,
9    pub content: T,
10}
11
12impl<T: SourceContent> SourceFile<T> {
13    pub fn new(path: PathBuf, content: T) -> Self {
14        SourceFile { path, content }
15    }
16
17    pub fn load(path: &PathBuf) -> Result<Self> {
18        let content = std::fs::read(path)?;
19        Ok(SourceFile::new(path.clone(), T::parse(content)?))
20    }
21
22    // TODO - I'm fairly sure the `dest` is redundant if it's None
23    pub fn save(&self, dest: Option<PathBuf>) -> Result<()> {
24        let final_path = dest.unwrap_or(self.path.clone());
25        std::fs::write(&final_path, &self.content.serialize()?)?;
26        Ok(())
27    }
28}
29
30pub trait SourceContent {
31    fn parse(content: Vec<u8>) -> Result<Self>
32    where
33        Self: Sized;
34
35    fn serialize(&self) -> Result<Vec<u8>>;
36}
37
38impl SourceContent for String {
39    fn parse(content: Vec<u8>) -> Result<Self> {
40        Ok(String::from_utf8(content)?)
41    }
42
43    fn serialize(&self) -> Result<Vec<u8>> {
44        Ok(self.clone().into_bytes())
45    }
46}
47
48impl SourceContent for SourceMapContent {
49    fn parse(content: Vec<u8>) -> Result<Self> {
50        Ok(serde_json::from_slice(&content)?)
51    }
52
53    fn serialize(&self) -> Result<Vec<u8>> {
54        Ok(serde_json::to_vec(self)?)
55    }
56}
57
58impl SourceContent for Vec<u8> {
59    fn parse(content: Vec<u8>) -> Result<Self> {
60        Ok(content)
61    }
62
63    fn serialize(&self) -> Result<Vec<u8>> {
64        Ok(self.clone())
65    }
66}
67
68pub fn delete_files(paths: Vec<PathBuf>) -> Result<()> {
69    // Delete local sourcemaps files from the sourcepair
70    for path in paths {
71        if path.exists() {
72            std::fs::remove_file(&path)
73                .context(format!("Failed to delete file: {}", path.display()))?;
74        }
75    }
76    Ok(())
77}
78
79// TODO - find a better home for this - it's not really a "file" operation,
80// but dumping it in the general "utils" feels gross
81pub fn content_hash<Iter, Item: AsRef<[u8]>>(upload_data: Iter) -> String
82where
83    Iter: IntoIterator<Item = Item>,
84{
85    let mut hasher = sha2::Sha512::new();
86    for data in upload_data {
87        hasher.update(data.as_ref());
88    }
89    format!("{:x}", hasher.finalize())
90}