1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct PackMeta {
9 pub name: String,
11
12 pub version: String,
14
15 #[serde(default)]
17 pub description: Option<String>,
18
19 pub digest: String,
21
22 #[serde(default)]
24 pub size: Option<u64>,
25
26 #[serde(default)]
28 pub published_at: Option<DateTime<Utc>>,
29
30 #[serde(default)]
32 pub signed: bool,
33
34 #[serde(default)]
36 pub key_id: Option<String>,
37
38 #[serde(default)]
40 pub deprecated: bool,
41
42 #[serde(default)]
44 pub deprecation_message: Option<String>,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct VersionsResponse {
50 pub name: String,
52
53 pub versions: Vec<VersionInfo>,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct VersionInfo {
60 pub version: String,
62
63 pub digest: String,
65
66 #[serde(default)]
68 pub published_at: Option<DateTime<Utc>>,
69
70 #[serde(default)]
72 pub deprecated: bool,
73}
74
75#[derive(Debug, Clone)]
77pub struct PackHeaders {
78 pub digest: Option<String>,
80
81 pub signature: Option<String>,
83
84 pub key_id: Option<String>,
86
87 pub etag: Option<String>,
89
90 pub cache_control: Option<String>,
92
93 pub content_length: Option<u64>,
95}
96
97impl PackHeaders {
98 pub fn from_headers(headers: &reqwest::header::HeaderMap) -> Self {
100 Self {
101 digest: headers
102 .get("x-pack-digest")
103 .and_then(|v| v.to_str().ok())
104 .map(String::from),
105 signature: headers
106 .get("x-pack-signature")
107 .and_then(|v| v.to_str().ok())
108 .map(String::from),
109 key_id: headers
110 .get("x-pack-key-id")
111 .and_then(|v| v.to_str().ok())
112 .map(String::from),
113 etag: headers
114 .get(reqwest::header::ETAG)
115 .and_then(|v| v.to_str().ok())
116 .map(String::from),
117 cache_control: headers
118 .get(reqwest::header::CACHE_CONTROL)
119 .and_then(|v| v.to_str().ok())
120 .map(String::from),
121 content_length: headers
122 .get(reqwest::header::CONTENT_LENGTH)
123 .and_then(|v| v.to_str().ok())
124 .and_then(|v| v.parse().ok()),
125 }
126 }
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct KeysManifest {
132 pub version: u8,
134
135 pub keys: Vec<TrustedKey>,
137
138 #[serde(default)]
140 pub expires_at: Option<DateTime<Utc>>,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct TrustedKey {
146 pub key_id: String,
148
149 pub algorithm: String,
151
152 pub public_key: String,
154
155 #[serde(default)]
157 pub description: Option<String>,
158
159 #[serde(default)]
161 pub added_at: Option<DateTime<Utc>>,
162
163 #[serde(default)]
165 pub expires_at: Option<DateTime<Utc>>,
166
167 #[serde(default)]
169 pub revoked: bool,
170}
171
172#[derive(Debug, Clone)]
174pub struct FetchResult {
175 pub content: String,
177
178 pub headers: PackHeaders,
180
181 pub computed_digest: String,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct DsseEnvelope {
188 #[serde(rename = "payloadType")]
190 pub payload_type: String,
191
192 pub payload: String,
194
195 pub signatures: Vec<DsseSignature>,
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct DsseSignature {
202 #[serde(rename = "keyid")]
204 pub key_id: String,
205
206 #[serde(rename = "sig")]
208 pub signature: String,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize)]
213pub struct RegistryConfig {
214 #[serde(default = "default_registry_url")]
216 pub url: String,
217
218 #[serde(default)]
220 pub token: Option<String>,
221
222 #[serde(default)]
224 pub allow_unsigned: bool,
225
226 #[serde(default = "default_timeout")]
228 pub timeout_secs: u64,
229
230 #[serde(default = "default_max_retries")]
232 pub max_retries: u32,
233}
234
235fn default_registry_url() -> String {
236 "https://registry.getassay.dev/v1".to_string()
237}
238
239fn default_timeout() -> u64 {
240 30
241}
242
243fn default_max_retries() -> u32 {
244 3
245}
246
247impl Default for RegistryConfig {
248 fn default() -> Self {
249 Self {
250 url: default_registry_url(),
251 token: None,
252 allow_unsigned: false,
253 timeout_secs: default_timeout(),
254 max_retries: default_max_retries(),
255 }
256 }
257}
258
259impl RegistryConfig {
260 pub fn from_env() -> Self {
268 Self {
269 url: std::env::var("ASSAY_REGISTRY_URL").unwrap_or_else(|_| default_registry_url()),
270 token: std::env::var("ASSAY_REGISTRY_TOKEN").ok(),
271 allow_unsigned: std::env::var("ASSAY_ALLOW_UNSIGNED_PACKS")
272 .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
273 .unwrap_or(false),
274 timeout_secs: std::env::var("ASSAY_REGISTRY_TIMEOUT")
275 .ok()
276 .and_then(|v| v.parse().ok())
277 .unwrap_or_else(default_timeout),
278 max_retries: std::env::var("ASSAY_REGISTRY_MAX_RETRIES")
279 .ok()
280 .and_then(|v| v.parse().ok())
281 .unwrap_or_else(default_max_retries),
282 }
283 }
284
285 pub fn with_token(mut self, token: impl Into<String>) -> Self {
287 self.token = Some(token.into());
288 self
289 }
290
291 pub fn with_url(mut self, url: impl Into<String>) -> Self {
293 self.url = url.into();
294 self
295 }
296
297 pub fn with_allow_unsigned(mut self, allow: bool) -> Self {
299 self.allow_unsigned = allow;
300 self
301 }
302}