dnf_repofile/types.rs
1//! Value types and newtypes for DNF repository configuration options.
2//!
3//! This module defines all the typed representations for DNF/YUM `.repo` file
4//! options: identifiers ([`RepoId`], [`RepoName`]), numeric constraints
5//! ([`Priority`], [`Cost`]), boolean values ([`DnfBool`]), composite types
6//! ([`Throttle`], [`MetadataExpire`], [`ProxySetting`], [`StorageSize`]),
7//! and enums for constrained value sets ([`IpResolve`], [`ProxyAuthMethod`],
8//! [`MultilibPolicy`], etc.).
9//!
10//! Most newtypes are generated by the [`mod@nutype`] crate with built-in
11//! validation, providing `try_new()`, `FromStr`, and `Display` implementations.
12
13use nutype::nutype;
14use url::Url;
15
16// ===== Identifiers =====
17
18/// A repository identifier (the `[repo-id]` section header).
19///
20/// Must be non-empty and match `^[A-Za-z0-9\-_.:]+$`.
21///
22/// # Examples
23///
24/// ```
25/// use dnf_repofile::RepoId;
26///
27/// let id = RepoId::try_new("epel").unwrap();
28/// assert_eq!(id.as_ref(), "epel");
29/// ```
30#[nutype(
31 sanitize(trim),
32 validate(not_empty, regex = r"^[A-Za-z0-9\-_.:]+$"),
33 derive(
34 Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Display, AsRef, Deref, FromStr,
35 )
36)]
37pub struct RepoId(String);
38
39/// A human-readable repository name.
40///
41/// Must be non-empty. Displayed by DNF during operations.
42///
43/// # Examples
44///
45/// ```
46/// use dnf_repofile::RepoName;
47///
48/// let name = RepoName::try_new("Extra Packages for EPEL").unwrap();
49/// assert_eq!(name.as_ref(), "Extra Packages for EPEL");
50/// ```
51#[nutype(
52 sanitize(trim),
53 validate(not_empty),
54 derive(Debug, Clone, PartialEq, Eq, Display, AsRef, Deref, FromStr)
55)]
56pub struct RepoName(String);
57
58/// A username for repository HTTP authentication.
59///
60/// # Examples
61///
62/// ```
63/// use dnf_repofile::Username;
64///
65/// let user = Username::new("myuser");
66/// assert_eq!(user.as_ref(), "myuser");
67/// ```
68#[nutype(
69 sanitize(trim),
70 derive(Debug, Clone, PartialEq, Eq, Display, AsRef, Deref, FromStr)
71)]
72pub struct Username(String);
73
74/// A password for repository HTTP authentication.
75///
76/// # Examples
77///
78/// ```
79/// use dnf_repofile::Password;
80///
81/// let pass = Password::new("s3cret");
82/// assert!(pass.as_ref().len() > 0);
83/// ```
84#[nutype(derive(Debug, Clone, PartialEq, Eq, Display, AsRef, Deref, FromStr))]
85pub struct Password(String);
86
87/// A username for proxy HTTP authentication.
88///
89/// # Examples
90///
91/// ```
92/// use dnf_repofile::ProxyUsername;
93///
94/// let user = ProxyUsername::new("proxyuser");
95/// ```
96#[nutype(
97 sanitize(trim),
98 derive(Debug, Clone, PartialEq, Eq, Display, AsRef, Deref, FromStr)
99)]
100pub struct ProxyUsername(String);
101
102/// A password for proxy HTTP authentication.
103///
104/// # Examples
105///
106/// ```
107/// use dnf_repofile::ProxyPassword;
108///
109/// let pass = ProxyPassword::new("proxypass");
110/// ```
111#[nutype(derive(Debug, Clone, PartialEq, Eq, Display, AsRef, Deref, FromStr))]
112pub struct ProxyPassword(String);
113
114/// A custom HTTP User-Agent header value.
115///
116/// # Examples
117///
118/// ```
119/// use dnf_repofile::UserAgent;
120///
121/// let ua = UserAgent::new("MyDNFClient/1.0");
122/// ```
123#[nutype(
124 sanitize(trim),
125 derive(Debug, Clone, PartialEq, Eq, Display, AsRef, Deref, FromStr)
126)]
127pub struct UserAgent(String);
128
129/// A module platform identifier for DNF modularity.
130///
131/// # Examples
132///
133/// ```
134/// use dnf_repofile::ModulePlatformId;
135///
136/// let mp = ModulePlatformId::new("platform:f38");
137/// ```
138#[nutype(
139 sanitize(trim),
140 derive(Debug, Clone, PartialEq, Eq, Display, AsRef, Deref, FromStr)
141)]
142pub struct ModulePlatformId(String);
143
144// ===== Numerics =====
145
146/// Repository priority value (1–99, lower = higher priority).
147///
148/// Default is 99 when not explicitly set.
149///
150/// # Examples
151///
152/// ```
153/// use dnf_repofile::Priority;
154///
155/// let p = Priority::try_new(50).unwrap();
156/// assert_eq!(*p, 50);
157/// ```
158#[nutype(
159 validate(greater_or_equal = 1, less_or_equal = 99),
160 default = 99,
161 derive(
162 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Display, Deref, Default
163 )
164)]
165pub struct Priority(i32);
166
167/// Repository cost value (0 or higher, higher = less preferred).
168///
169/// Default is 1000 when not explicitly set.
170///
171/// # Examples
172///
173/// ```
174/// use dnf_repofile::Cost;
175///
176/// let c = Cost::try_new(500).unwrap();
177/// assert_eq!(*c, 500);
178/// ```
179#[nutype(
180 validate(greater_or_equal = 0),
181 default = 1000,
182 derive(
183 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Display, Deref, Default
184 )
185)]
186pub struct Cost(i32);
187
188/// Number of retries for network operations (default 10).
189///
190/// # Examples
191///
192/// ```
193/// use dnf_repofile::Retries;
194///
195/// let r = Retries::try_new(5).unwrap();
196/// assert_eq!(*r, 5);
197/// ```
198#[nutype(
199 validate(greater_or_equal = 0),
200 default = 10,
201 derive(Debug, Clone, Copy, PartialEq, Eq, Display, Deref, Default)
202)]
203pub struct Retries(u32);
204
205/// Network timeout in seconds (default 30).
206///
207/// # Examples
208///
209/// ```
210/// use dnf_repofile::TimeoutSeconds;
211///
212/// let t = TimeoutSeconds::try_new(60).unwrap();
213/// assert_eq!(*t, 60);
214/// ```
215#[nutype(
216 validate(greater_or_equal = 0),
217 default = 30,
218 derive(
219 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Display, Deref, Default
220 )
221)]
222pub struct TimeoutSeconds(u32);
223
224/// Maximum delta RPM percentage (0–100, default 75).
225///
226/// # Examples
227///
228/// ```
229/// use dnf_repofile::DeltaRpmPercentage;
230///
231/// let p = DeltaRpmPercentage::try_new(50).unwrap();
232/// ```
233#[nutype(
234 validate(greater_or_equal = 0, less_or_equal = 100),
235 default = 75,
236 derive(Debug, Clone, Copy, PartialEq, Eq, Display, Deref, Default)
237)]
238pub struct DeltaRpmPercentage(u32);
239
240/// Maximum parallel downloads (0–20, default 3).
241///
242/// # Examples
243///
244/// ```
245/// use dnf_repofile::MaxParallelDownloads;
246///
247/// let m = MaxParallelDownloads::try_new(5).unwrap();
248/// ```
249#[nutype(
250 validate(greater_or_equal = 0, less_or_equal = 20),
251 default = 3,
252 derive(Debug, Clone, Copy, PartialEq, Eq, Display, Deref, Default)
253)]
254pub struct MaxParallelDownloads(u32);
255
256/// Debug message verbosity level (0–10, default 2).
257///
258/// # Examples
259///
260/// ```
261/// use dnf_repofile::DebugLevel;
262///
263/// let d = DebugLevel::try_new(5).unwrap();
264/// ```
265#[nutype(
266 validate(greater_or_equal = 0, less_or_equal = 10),
267 default = 2,
268 derive(Debug, Clone, Copy, PartialEq, Eq, Display, Deref, Default)
269)]
270pub struct DebugLevel(u8);
271
272/// Log file verbosity level (0–10, default 9).
273///
274/// # Examples
275///
276/// ```
277/// use dnf_repofile::LogLevel;
278///
279/// let l = LogLevel::try_new(5).unwrap();
280/// ```
281#[nutype(
282 validate(greater_or_equal = 0, less_or_equal = 10),
283 default = 9,
284 derive(Debug, Clone, Copy, PartialEq, Eq, Display, Deref, Default)
285)]
286pub struct LogLevel(u8);
287
288/// Maximum number of install-only kernel packages to keep (default 3, cannot be 1).
289///
290/// # Examples
291///
292/// ```
293/// use dnf_repofile::InstallOnlyLimit;
294///
295/// let limit = InstallOnlyLimit::try_new(3).unwrap();
296/// ```
297#[nutype(
298 validate(greater_or_equal = 0, predicate = |x| *x != 1),
299 default = 3,
300 derive(Debug, Clone, Copy, PartialEq, Eq, Display, Deref, Default),
301)]
302pub struct InstallOnlyLimit(u32);
303
304/// Number of log files to keep before rotation (default 4).
305///
306/// # Examples
307///
308/// ```
309/// use dnf_repofile::LogRotate;
310///
311/// let r = LogRotate::try_new(7).unwrap();
312/// ```
313#[nutype(
314 validate(greater_or_equal = 0),
315 default = 4,
316 derive(Debug, Clone, Copy, PartialEq, Eq, Display, Deref, Default)
317)]
318pub struct LogRotate(u32);
319
320/// Time in seconds between metadata timer syncs (default 10800 = 3 hours).
321///
322/// # Examples
323///
324/// ```
325/// use dnf_repofile::MetadataTimerSync;
326///
327/// let t = MetadataTimerSync::try_new(3600).unwrap();
328/// ```
329#[nutype(
330 validate(greater_or_equal = 0),
331 default = 10800,
332 derive(Debug, Clone, Copy, PartialEq, Eq, Display, Deref, Default)
333)]
334pub struct MetadataTimerSync(u32);
335
336/// Error message verbosity level (0–10, default 3).
337///
338/// # Examples
339///
340/// ```
341/// use dnf_repofile::ErrorLevel;
342///
343/// let e = ErrorLevel::try_new(5).unwrap();
344/// ```
345#[nutype(
346 validate(greater_or_equal = 0, less_or_equal = 10),
347 default = 3,
348 derive(Debug, Clone, Copy, PartialEq, Eq, Display, Deref, Default)
349)]
350pub struct ErrorLevel(u8);
351
352// ===== Composite value types =====
353
354/// A storage size in bytes (e.g., bandwidth, minrate, log_size).
355///
356/// Supports human-readable suffixes: `K` (kibibytes), `M` (mebibytes),
357/// `G` (gibibytes). Parsed from `.repo` file values like `"100M"` or `"10G"`.
358///
359/// # Examples
360///
361/// ```
362/// use dnf_repofile::StorageSize;
363///
364/// let size = StorageSize(1024 * 1024); // 1 MiB
365/// assert_eq!(size.0, 1048576);
366/// ```
367#[derive(Debug, Clone, Copy, PartialEq, Eq)]
368pub struct StorageSize(pub u64);
369
370/// How long metadata is considered valid before a refresh is required.
371///
372/// Can be a specific duration in seconds, or `Never` to disable expiration.
373///
374/// # Examples
375///
376/// ```
377/// use dnf_repofile::MetadataExpire;
378///
379/// let expire = MetadataExpire::Duration(3600); // 1 hour
380/// let never = MetadataExpire::Never;
381/// ```
382#[derive(Debug, Clone, Copy, PartialEq, Eq)]
383pub enum MetadataExpire {
384 /// Metadata expires after this many seconds.
385 Duration(u64),
386 /// Metadata never expires.
387 Never,
388}
389
390/// Bandwidth throttle: absolute storage size or percentage of total bandwidth.
391///
392/// Parsed from `.repo` file values like `"100M"` (absolute) or `"50%"` (percentage).
393///
394/// # Examples
395///
396/// ```
397/// use dnf_repofile::Throttle;
398///
399/// let abs = Throttle::Absolute(dnf_repofile::StorageSize(1048576));
400/// let pct = Throttle::Percent(50);
401/// ```
402#[derive(Debug, Clone, Copy, PartialEq, Eq)]
403pub enum Throttle {
404 /// Absolute bandwidth limit in bytes.
405 Absolute(StorageSize),
406 /// Percentage of total available bandwidth.
407 Percent(u8),
408}
409
410/// Proxy configuration for a repository.
411///
412/// Can be unset (use system default), explicitly disabled, a specific
413/// proxy URL, or a raw unparseable string.
414///
415/// # Examples
416///
417/// ```
418/// use dnf_repofile::ProxySetting;
419///
420/// let disabled = ProxySetting::Disabled;
421/// let url_proxy = ProxySetting::Url("http://proxy:8080/".parse().unwrap());
422/// ```
423#[non_exhaustive]
424#[derive(Debug, Clone, PartialEq, Eq)]
425pub enum ProxySetting {
426 /// Use the default proxy configuration (no explicit setting).
427 Unset,
428 /// Explicitly disable proxy (`proxy=_none_`).
429 Disabled,
430 /// Use this specific proxy URL.
431 Url(Url),
432 /// A proxy value that could not be parsed as a valid URL (preserved for
433 /// round-trip fidelity).
434 Raw(String),
435}
436
437// ===== DNF Boolean =====
438
439/// DNF boolean value.
440///
441/// DNF accepts multiple textual representations for booleans in `.repo` files:
442///
443/// | Input | Result |
444/// |-----------|-----------------|
445/// | `1` | `True` |
446/// | `yes` | `True` |
447/// | `true` | `True` |
448/// | `on` | `True` |
449/// | `0` | `False` |
450/// | `no` | `False` |
451/// | `false` | `False` |
452/// | `off` | `False` |
453///
454/// # Examples
455///
456/// ```
457/// use dnf_repofile::DnfBool;
458///
459/// // Parsing
460/// let enabled = DnfBool::parse("1").unwrap();
461/// assert_eq!(enabled, DnfBool::True);
462///
463/// // Convenience constructors
464/// assert_eq!(DnfBool::yes(), DnfBool::True);
465/// assert_eq!(DnfBool::no(), DnfBool::False);
466///
467/// // Conversion from bool
468/// let d: DnfBool = true.into();
469/// assert_eq!(d, DnfBool::True);
470///
471/// // Conversion to bool
472/// let b: bool = DnfBool::True.into();
473/// assert!(b);
474///
475/// // Display as "1" or "0"
476/// assert_eq!(DnfBool::True.to_string(), "1");
477/// assert_eq!(DnfBool::False.to_string(), "0");
478/// ```
479#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
480pub enum DnfBool {
481 /// Boolean true (parsed from `1`, `yes`, `true`, `on`).
482 True,
483 /// Boolean false (parsed from `0`, `no`, `false`, `off`).
484 False,
485}
486
487impl DnfBool {
488 /// Parse a DNF boolean string into a [`DnfBool`].
489 ///
490 /// Accepted values (case-insensitive):
491 /// - `"1"`, `"yes"`, `"true"`, `"on"` → [`DnfBool::True`]
492 /// - `"0"`, `"no"`, `"false"`, `"off"` → [`DnfBool::False`]
493 ///
494 /// # Errors
495 ///
496 /// Returns [`crate::error::ParseBoolError`] if the input does not
497 /// match any known boolean value.
498 ///
499 /// # Examples
500 ///
501 /// ```
502 /// use dnf_repofile::DnfBool;
503 ///
504 /// assert!(DnfBool::parse("1").is_ok());
505 /// assert!(DnfBool::parse("yes").is_ok());
506 /// assert!(DnfBool::parse("TRUE").is_ok());
507 /// assert!(DnfBool::parse("maybe").is_err());
508 /// ```
509 pub fn parse(s: &str) -> std::result::Result<Self, crate::error::ParseBoolError> {
510 let trimmed = s.trim();
511 if trimmed.eq_ignore_ascii_case("1")
512 || trimmed.eq_ignore_ascii_case("yes")
513 || trimmed.eq_ignore_ascii_case("true")
514 || trimmed.eq_ignore_ascii_case("on")
515 {
516 return Ok(DnfBool::True);
517 }
518 if trimmed.eq_ignore_ascii_case("0")
519 || trimmed.eq_ignore_ascii_case("no")
520 || trimmed.eq_ignore_ascii_case("false")
521 || trimmed.eq_ignore_ascii_case("off")
522 {
523 return Ok(DnfBool::False);
524 }
525 Err(crate::error::ParseBoolError {
526 input: s.to_owned(),
527 })
528 }
529
530 /// Convenience constructor for [`DnfBool::True`] (enabled).
531 ///
532 /// Equivalent to `DnfBool::True`.
533 pub fn yes() -> Self {
534 DnfBool::True
535 }
536
537 /// Convenience constructor for [`DnfBool::False`] (disabled).
538 ///
539 /// Equivalent to `DnfBool::False`.
540 pub fn no() -> Self {
541 DnfBool::False
542 }
543}
544
545impl std::fmt::Display for DnfBool {
546 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
547 match self {
548 DnfBool::True => write!(f, "1"),
549 DnfBool::False => write!(f, "0"),
550 }
551 }
552}
553
554impl From<bool> for DnfBool {
555 fn from(b: bool) -> Self {
556 if b {
557 DnfBool::True
558 } else {
559 DnfBool::False
560 }
561 }
562}
563
564impl From<DnfBool> for bool {
565 fn from(d: DnfBool) -> bool {
566 matches!(d, DnfBool::True)
567 }
568}
569
570// ===== Enums =====
571
572/// IP protocol version preference for network connections.
573///
574/// # Examples
575///
576/// ```
577/// use dnf_repofile::IpResolve;
578///
579/// let v4 = IpResolve::V4;
580/// let v6 = IpResolve::V6;
581/// ```
582#[non_exhaustive]
583#[derive(Debug, Clone, Copy, PartialEq, Eq)]
584pub enum IpResolve {
585 /// Prefer IPv4 connections.
586 V4,
587 /// Prefer IPv6 connections.
588 V6,
589}
590
591/// HTTP proxy authentication method.
592///
593/// # Examples
594///
595/// ```
596/// use dnf_repofile::ProxyAuthMethod;
597///
598/// let method = ProxyAuthMethod::Basic;
599/// ```
600#[non_exhaustive]
601#[derive(Debug, Clone, Copy, PartialEq, Eq)]
602pub enum ProxyAuthMethod {
603 /// Any available authentication method.
604 Any,
605 /// No authentication.
606 None_,
607 /// HTTP Basic authentication.
608 Basic,
609 /// HTTP Digest authentication.
610 Digest,
611 /// HTTP Negotiate authentication.
612 Negotiate,
613 /// NTLM authentication.
614 Ntlm,
615 /// Microsoft Digest IE authentication.
616 DigestIe,
617 /// NTLM Windows Bridge authentication.
618 NtlmWb,
619}
620
621/// Repository metadata type (e.g., `rpm-md`).
622///
623/// # Examples
624///
625/// ```
626/// use dnf_repofile::RepoMetadataType;
627///
628/// let rpmmd = RepoMetadataType::RpmMd;
629/// ```
630#[non_exhaustive]
631#[derive(Debug, Clone, PartialEq, Eq)]
632pub enum RepoMetadataType {
633 /// RPM-MD metadata format (standard).
634 RpmMd,
635}
636
637/// Multilib package installation policy.
638///
639/// # Examples
640///
641/// ```
642/// use dnf_repofile::MultilibPolicy;
643///
644/// let policy = MultilibPolicy::Best;
645/// ```
646#[non_exhaustive]
647#[derive(Debug, Clone, Copy, PartialEq, Eq)]
648pub enum MultilibPolicy {
649 /// Install the best-matching architecture.
650 Best,
651 /// Install all available architectures.
652 All,
653}
654
655/// SQLite database persistence mode for repository metadata.
656///
657/// # Examples
658///
659/// ```
660/// use dnf_repofile::Persistence;
661///
662/// let mode = Persistence::Auto;
663/// ```
664#[non_exhaustive]
665#[derive(Debug, Clone, Copy, PartialEq, Eq)]
666pub enum Persistence {
667 /// Automatically determine persistence.
668 Auto,
669 /// Do not persist metadata (keep in memory only).
670 Transient,
671 /// Persist metadata to disk.
672 Persist,
673}
674
675/// RPM transaction verbosity level.
676///
677/// # Examples
678///
679/// ```
680/// use dnf_repofile::RpmVerbosity;
681///
682/// let verbose = RpmVerbosity::Info;
683/// ```
684#[non_exhaustive]
685#[derive(Debug, Clone, Copy, PartialEq, Eq)]
686pub enum RpmVerbosity {
687 /// Critical messages only.
688 Critical,
689 /// Emergency messages only.
690 Emergency,
691 /// Error messages.
692 Error,
693 /// Warning messages.
694 Warn,
695 /// Informational messages.
696 Info,
697 /// Debug messages (most verbose).
698 Debug,
699}
700
701/// RPM transaction flag controlling scripts, triggers, and other behaviors.
702///
703/// # Examples
704///
705/// ```
706/// use dnf_repofile::TsFlag;
707///
708/// let flags = vec![TsFlag::NoScripts, TsFlag::NoDocs];
709/// ```
710#[non_exhaustive]
711#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
712pub enum TsFlag {
713 /// Disable script execution.
714 NoScripts,
715 /// Test the transaction without making changes.
716 Test,
717 /// Disable trigger execution.
718 NoTriggers,
719 /// Skip documentation files.
720 NoDocs,
721 /// Only update the RPM database.
722 JustDb,
723 /// Disable SELinux contexts.
724 NoContexts,
725 /// Disable file capabilities.
726 NoCaps,
727 /// Disable cryptographic signature checks.
728 NoCrypto,
729 /// Detect and resolve dependency loops.
730 Deploops,
731 /// Disable RPM plugins.
732 NoPlugins,
733}
734
735/// Describes how a repository advertises its metadata source.
736///
737/// Returned by [`Repo::url_source()`](crate::Repo::url_source).
738///
739/// # Examples
740///
741/// ```
742/// use dnf_repofile::{Repo, RepoId, UrlSource};
743///
744/// let mut repo = Repo::new(RepoId::try_new("example").unwrap());
745/// repo.metalink = Some("https://example.com/metalink".parse().unwrap());
746///
747/// match repo.url_source().unwrap() {
748/// UrlSource::Metalink(url) => println!("Metalink: {}", url),
749/// _ => unreachable!(),
750/// }
751/// ```
752#[non_exhaustive]
753#[derive(Debug, Clone, PartialEq, Eq)]
754pub enum UrlSource {
755 /// One or more direct base URLs for the repository.
756 BaseUrl(Vec<Url>),
757 /// A mirror list URL that resolves to a list of mirrors.
758 MirrorList(Url),
759 /// A metalink URL providing a signed list of mirrors and checksums.
760 Metalink(Url),
761}