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}