Skip to main content

zrx_id/id/
builder.rs

1// Copyright (c) 2025-2026 Zensical and contributors
2
3// SPDX-License-Identifier: MIT
4// All contributions are certified under the DCO
5
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to
8// deal in the Software without restriction, including without limitation the
9// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10// sell copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22// IN THE SOFTWARE.
23
24// ----------------------------------------------------------------------------
25
26//! Identifier builder.
27
28use ahash::AHasher;
29use std::borrow::Cow;
30use std::hash::{Hash, Hasher};
31
32use super::error::{Error, Result};
33use super::format::{self, Format};
34use super::Id;
35
36// ----------------------------------------------------------------------------
37// Structs
38// ----------------------------------------------------------------------------
39
40/// Identifier builder.
41#[derive(Clone, Debug)]
42pub struct Builder<'a> {
43    /// Format builder.
44    format: format::Builder<'a, 7>,
45}
46
47// ----------------------------------------------------------------------------
48// Implementations
49// ----------------------------------------------------------------------------
50
51impl Id {
52    /// Creates an identifier builder.
53    ///
54    /// # Examples
55    ///
56    /// ```
57    /// use zrx_id::Id;
58    ///
59    /// // Create identifier builder
60    /// let builder = Id::builder();
61    /// ```
62    #[inline]
63    #[must_use]
64    pub fn builder<'a>() -> Builder<'a> {
65        Builder::default()
66    }
67
68    /// Creates a builder from the identifier.
69    ///
70    /// This method creates a builder from the current identifier, which allows
71    /// to modify components and build a new identifier from an existing one.
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// # use std::error::Error;
77    /// # fn main() -> Result<(), Box<dyn Error>> {
78    /// use zrx_id::Id;
79    ///
80    /// // Create identifier from string
81    /// let id: Id = "zri:file:::docs:index.md:".parse()?;
82    ///
83    /// // Create identifier builder
84    /// let builder = id.to_builder().location("README.md");
85    ///
86    /// // Create identifier from builder
87    /// let id = builder.build()?;
88    /// assert_eq!(id.as_str(), "zri:file:::docs:README.md:");
89    /// # Ok(())
90    /// # }
91    /// ```
92    #[inline]
93    #[must_use]
94    pub fn to_builder(&self) -> Builder<'_> {
95        Builder {
96            format: self.format.to_builder().with(0, "zri"),
97        }
98    }
99}
100
101// ----------------------------------------------------------------------------
102
103impl<'a> Builder<'a> {
104    /// Sets the `provider` component.
105    ///
106    /// # Examples
107    ///
108    /// ```
109    /// use zrx_id::Id;
110    ///
111    /// // Create identifier builder and set provider
112    /// let builder = Id::builder().provider("git");
113    /// ```
114    #[inline]
115    #[must_use]
116    pub fn provider<S>(mut self, value: S) -> Self
117    where
118        S: Into<Cow<'a, str>>,
119    {
120        self.format.set(1, value);
121        self
122    }
123
124    /// Sets the `resource` component.
125    ///
126    /// # Examples
127    ///
128    /// ```
129    /// use zrx_id::Id;
130    ///
131    /// // Create identifier builder and set resource
132    /// let builder = Id::builder().resource("master");
133    /// ```
134    #[inline]
135    #[must_use]
136    pub fn resource<S>(mut self, value: S) -> Self
137    where
138        S: Into<Cow<'a, str>>,
139    {
140        self.format.set(2, value);
141        self
142    }
143
144    /// Sets the `variant` component.
145    ///
146    /// # Examples
147    ///
148    /// ```
149    /// use zrx_id::Id;
150    ///
151    /// // Create identifier builder and set variant
152    /// let builder = Id::builder().variant("en");
153    /// ```
154    #[inline]
155    #[must_use]
156    pub fn variant<S>(mut self, value: S) -> Self
157    where
158        S: Into<Cow<'a, str>>,
159    {
160        self.format.set(3, value);
161        self
162    }
163
164    /// Sets the `context` component.
165    ///
166    /// # Examples
167    ///
168    /// ```
169    /// use zrx_id::Id;
170    ///
171    /// // Create identifier builder and set context
172    /// let builder = Id::builder().context("docs");
173    /// ```
174    #[inline]
175    #[must_use]
176    pub fn context<S>(mut self, value: S) -> Self
177    where
178        S: Into<Cow<'a, str>>,
179    {
180        self.format.set(4, value);
181        self
182    }
183
184    /// Sets the `location` component.
185    ///
186    /// # Examples
187    ///
188    /// ```
189    /// use zrx_id::Id;
190    ///
191    /// // Create identifier builder and set location
192    /// let builder = Id::builder().location("docs");
193    /// ```
194    #[inline]
195    #[must_use]
196    pub fn location<S>(mut self, value: S) -> Self
197    where
198        S: Into<Cow<'a, str>>,
199    {
200        self.format.set(5, value);
201        self
202    }
203
204    /// Sets the `fragment` component.
205    ///
206    /// # Examples
207    ///
208    /// ```
209    /// use zrx_id::Id;
210    ///
211    /// // Create identifier builder and set fragment
212    /// let builder = Id::builder().fragment("anchor");
213    /// ```
214    #[inline]
215    #[must_use]
216    pub fn fragment<S>(mut self, value: S) -> Self
217    where
218        S: Into<Cow<'a, str>>,
219    {
220        self.format.set(6, value);
221        self
222    }
223
224    /// Builds the identifier.
225    ///
226    /// # Errors
227    ///
228    /// Returns [`Error::Component`] if any of the `provider`, `context` or
229    /// `location` components are not set. In case of low-level format errors,
230    /// [`Error::Format`] is returned.
231    ///
232    /// # Examples
233    ///
234    /// ```
235    /// # use std::error::Error;
236    /// # fn main() -> Result<(), Box<dyn Error>> {
237    /// use zrx_id::Id;
238    ///
239    /// // Create identifier builder
240    /// let builder = Id::builder()
241    ///     .provider("file")
242    ///     .context("docs")
243    ///     .location("index.md");
244    ///
245    /// // Create identifier from builder
246    /// let id = builder.build()?;
247    /// assert_eq!(id.as_str(), "zri:file:::docs:index.md:");
248    /// # Ok(())
249    /// # }
250    /// ```
251    pub fn build(self) -> Result<Id> {
252        let format = self.format.build()?;
253
254        // Ensure provider is set
255        if format.get(1).is_empty() {
256            Err(Error::Component("provider"))?;
257        }
258
259        // Ensure context is set
260        if format.get(4).is_empty() {
261            Err(Error::Component("context"))?;
262        }
263
264        // Ensure location is set
265        if format.get(5).is_empty() {
266            Err(Error::Component("location"))?;
267        }
268
269        // Precompute hash for fast hashing
270        let hash = {
271            let mut hasher = AHasher::default();
272            format.hash(&mut hasher);
273            hasher.finish()
274        };
275
276        // No errors occurred
277        Ok(Id { format, hash })
278    }
279}
280
281// ----------------------------------------------------------------------------
282// Trait implementations
283// ----------------------------------------------------------------------------
284
285impl Default for Builder<'_> {
286    /// Creates an identifier builder.
287    ///
288    /// # Examples
289    ///
290    /// ```
291    /// use zrx_id::Id;
292    ///
293    /// // Create identifier builder
294    /// let builder = Id::builder();
295    /// ```
296    #[inline]
297    fn default() -> Self {
298        Self {
299            format: Format::builder().with(0, "zri"),
300        }
301    }
302}