Skip to main content

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}