holger_rust_repository/
lib.rs

1use sha2::{Sha256, Digest};
2use std::any::Any;
3use anyhow::anyhow;
4use holger_traits::{ArtifactFormat, ArtifactId, RepositoryBackendTrait};
5
6/// Minimal RustRepo example
7pub struct RustRepo {
8    pub name: String,
9//    pub format: ArtifactFormat,
10    pub artifacts: Vec<ArtifactId>, // cached list of artifacts
11}
12
13impl RustRepo {
14    pub fn new(name: String) -> Self {
15        RustRepo {
16
17            // initialize fields if any; if none, leave empty struct
18            // Example: name
19            // name,
20            name,
21            artifacts: vec![],
22        }
23    }
24
25    /// Convert crate name to Cargo sparse 5-part path (p1, p2, name)
26    pub fn sparse_path(crate_name: &str) -> (String, String, String) {
27        let mut chars = crate_name.chars();
28
29        let p1: String = chars.by_ref().take(2).collect();
30        let mut p2: String = chars.by_ref().take(2).collect();
31
32        // Cargo uses "_" as filler if name is shorter than 4 chars
33        if p2.is_empty() {
34            p2.push('_');
35        }
36
37        (p1, p2, crate_name.to_string())
38    }
39
40    /// Reverse matcher: takes a sparse path slice ["xx","yy","name"] -> crate name
41    #[inline]
42    pub fn sparse_crate_from_parts<'a>(parts: &'a [&'a str]) -> Option<&'a str> {
43        if parts.len() == 3 {
44            Some(parts[2])
45        } else {
46            None
47        }
48    }
49    #[inline]
50    pub fn crate_sha256_hex(data: &[u8]) -> String {
51        use sha2::{Sha256, Digest};
52        use hex::encode; // Add `hex = "0.4"` to Cargo.toml
53
54        let mut hasher = Sha256::new();
55        hasher.update(data);
56        let hash = hasher.finalize(); // GenericArray<u8, U32>
57        encode(hash) // Convert to lowercase hex string
58    }
59}
60
61
62impl RepositoryBackendTrait for RustRepo {
63    fn name(&self) -> &str {
64        &self.name
65    }
66
67    fn handle_http2_request(
68        &self,
69        suburl: &str,
70        body: &[u8],
71    ) -> anyhow::Result<(u16, Vec<(String, String)>, Vec<u8>)> {
72        let _ = body;
73        println!("Rust repo handle_http2_request.suburl={}", suburl);
74
75        let parts: Vec<&str> = suburl.trim_start_matches('/').split('/').collect();
76
77        match parts.as_slice() {
78            // Sparse root config.json → /rust-prod/index/config.json
79            [repo, "index", "config.json"] if *repo == self.name() => {
80                println!("Sparse config.json requested");
81                let json = format!(
82                    r#"{{
83                        "dl": "https://127.0.0.1:8443/{}/crates",
84                        "api": null
85                    }}"#,
86                    self.name()
87                );
88                return Ok((
89                    200,
90                    vec![("Content-Type".into(), "application/json".into())],
91                    json.as_bytes().to_vec(),
92                ));
93            }
94
95            // Sparse crate metadata → /rust-prod/index/se/rd/serde
96            [repo, "index", p1, p2, crate_name] if *repo == self.name() => {
97                if let Some(actual_name) = RustRepo::sparse_crate_from_parts(&[p1, p2, crate_name]) {
98                    println!("Sparse crate metadata request: {}/{}/{}", p1, p2, actual_name);
99
100                    let fake_crate_data = b"FAKE_CRATE_CONTENT";
101                    let checksum_hex = RustRepo::crate_sha256_hex(fake_crate_data);
102
103                    let json = format!(
104                        r#"[{{"vers":"1.0.0","deps":[],"cksum":"{}"}}]"#,
105                        checksum_hex
106                    );
107
108                    return Ok((
109                        200,
110                        vec![("Content-Type".into(), "application/json".into())],
111                        json.into_bytes(),
112                    ));
113                } else {
114                    return Ok((404, Vec::new(), b"Not found".to_vec()));
115                }
116            }
117
118            // Crate download → /crates/<crate>/<version>/download
119            ["crates", crate_name, version, "download"] => {
120                println!("Download request: crate={} version={}", crate_name, version);
121                return Ok((
122                    200,
123                    vec![("Content-Type".into(), "application/octet-stream".into())],
124                    b"FAKE_CRATE_CONTENT".to_vec(),
125                ));
126            }
127
128            _ => {
129                println!("Unhandled path: {}", suburl);
130                Ok((404, Vec::new(), b"Not found".to_vec()))
131            }
132        }
133    }
134    fn format(&self) -> ArtifactFormat {
135        ArtifactFormat::Rust
136    }
137
138
139    fn is_writable(&self) -> bool {
140        todo!()
141    }
142
143    fn fetch(&self, id: &ArtifactId) -> anyhow::Result<Option<Vec<u8>>> {
144        todo!()
145    }
146
147    fn put(&self, id: &ArtifactId, data: &[u8]) -> anyhow::Result<()> {
148        todo!()
149    }
150}
151
152