1use crate::error::{PusherError, Result};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct AuthConfig {
12 pub username: String,
13 pub password: String,
14}
15
16impl AuthConfig {
17 pub fn new(username: String, password: String) -> Self {
18 Self { username, password }
19 }
20
21 pub fn from_optional(username: Option<String>, password: Option<String>) -> Result<Self> {
22 match (username, password) {
23 (Some(u), Some(p)) => Ok(Self::new(u, p)),
24 (None, _) => Err(PusherError::Config(
25 "Username is required for authentication".to_string(),
26 )),
27 (_, None) => Err(PusherError::Config(
28 "Password is required for authentication".to_string(),
29 )),
30 }
31 }
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct RegistryConfig {
36 pub address: String,
37 pub repository: String,
38 pub tag: String,
39 pub auth: Option<AuthConfig>,
40 pub skip_tls: bool,
41 pub timeout: u64,
42}
43
44impl RegistryConfig {
45 pub fn new(address: String, repository: String, tag: String) -> Self {
46 Self {
47 address,
48 repository,
49 tag,
50 auth: None,
51 skip_tls: false,
52 timeout: 7200, }
54 }
55
56 pub fn with_auth(mut self, auth: AuthConfig) -> Self {
57 self.auth = Some(auth);
58 self
59 }
60
61 pub fn with_skip_tls(mut self, skip_tls: bool) -> Self {
62 self.skip_tls = skip_tls;
63 self
64 }
65
66 pub fn with_timeout(mut self, timeout: u64) -> Self {
67 self.timeout = timeout;
68 self
69 }
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct AppConfig {
74 pub registry: RegistryConfig,
75 pub file_path: String,
76 pub large_layer_threshold: u64,
77 pub max_concurrent: usize,
78 pub retry_attempts: usize,
79 pub verbose: bool,
80 pub dry_run: bool,
81}
82
83impl AppConfig {
84 pub fn new(registry_url: &str, repository: &str, tag: &str, file_path: String) -> Result<Self> {
85 let registry_config = RegistryConfig::new(
86 registry_url.to_string(),
87 repository.to_string(),
88 tag.to_string(),
89 );
90
91 Ok(Self {
92 registry: registry_config,
93 file_path,
94 large_layer_threshold: 1024 * 1024 * 1024, max_concurrent: 1,
96 retry_attempts: 3,
97 verbose: false,
98 dry_run: false,
99 })
100 }
101
102 pub fn with_auth(mut self, username: String, password: String) -> Self {
103 let auth = AuthConfig::new(username, password);
104 self.registry = self.registry.with_auth(auth);
105 self
106 }
107
108 pub fn with_optional_auth(
109 mut self,
110 username: Option<String>,
111 password: Option<String>,
112 ) -> Result<Self> {
113 if let (Some(u), Some(p)) = (username, password) {
114 self = self.with_auth(u, p);
115 }
116 Ok(self)
117 }
118
119 pub fn has_auth(&self) -> bool {
120 self.registry.auth.is_some()
121 }
122
123 pub fn with_large_layer_threshold(mut self, threshold: u64) -> Self {
124 self.large_layer_threshold = threshold;
125 self
126 }
127
128 pub fn with_timeout(mut self, timeout: u64) -> Self {
129 self.registry = self.registry.with_timeout(timeout);
130 self
131 }
132
133 pub fn with_skip_tls(mut self, skip_tls: bool) -> Self {
134 self.registry = self.registry.with_skip_tls(skip_tls);
135 self
136 }
137
138 pub fn with_verbose(mut self, verbose: bool) -> Self {
139 self.verbose = verbose;
140 self
141 }
142
143 pub fn with_dry_run(mut self, dry_run: bool) -> Self {
144 self.dry_run = dry_run;
145 self
146 }
147
148 pub fn with_max_concurrent(mut self, max_concurrent: usize) -> Self {
149 self.max_concurrent = max_concurrent;
150 self
151 }
152
153 pub fn with_retry_attempts(mut self, retry_attempts: usize) -> Self {
154 self.retry_attempts = retry_attempts;
155 self
156 }
157
158 pub fn parse_repository_url(url: &str) -> Result<(String, String, String)> {
159 let parsed_url = url::Url::parse(url)
160 .map_err(|e| PusherError::Config(format!("Invalid repository URL: {}", e)))?;
161
162 let registry_address = format!(
163 "{}://{}",
164 parsed_url.scheme(),
165 parsed_url.host_str().unwrap_or("localhost")
166 );
167
168 let path = parsed_url.path().trim_start_matches('/');
169 let (repository, tag) = if let Some(colon_pos) = path.rfind(':') {
170 let (repo, tag_part) = path.split_at(colon_pos);
171 (repo, &tag_part[1..]) } else {
173 (path, "latest")
174 };
175
176 Ok((registry_address, repository.to_string(), tag.to_string()))
177 }
178}