Skip to main content

jj_core/
object_id.rs

1// Copyright 2020-2024 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Identifiers for objects (commits, changes, trees, etc.), and utilities for
16//! parsing and matching identifier prefixes.
17
18use std::fmt;
19use std::fmt::Debug;
20
21use crate::hex_util;
22
23/// An identifier for an object, such as a commit or a tree, usually the output
24/// of a hash function.
25pub trait ObjectId {
26    /// A lowercase name identifying the type of object (e.g. "commit").
27    fn object_type(&self) -> String;
28    /// The identifier as a byte slice.
29    fn as_bytes(&self) -> &[u8];
30    /// The identifier as an owned byte vector.
31    fn to_bytes(&self) -> Vec<u8>;
32    /// String representation of the identifier using hex digits.
33    fn hex(&self) -> String;
34}
35
36/// Defines a new struct type representing an object ID, with visibility `vis`
37/// and name `ident`.
38///
39/// The struct contains a single `Vec<u8>` used to store the identifier
40/// (typically the output of a hash function) as bytes. Types defined using this
41/// macro automatically implement the `ObjectId` and `ContentHash` traits.
42/// Documentation comments written inside the macro definition will be captured
43/// and associated with the type defined by the macro.
44///
45/// Example:
46/// ```
47/// # use jj_core::object_id::*;
48/// id_type!(
49///     /// My favorite id type.
50///     pub MyId { hex() }
51/// );
52/// ```
53#[doc(hidden)] // hide jj_core::_id_type!()
54#[macro_export]
55macro_rules! _id_type {
56    (   $(#[$attr:meta])*
57        $vis:vis $name:ident { $hex_method:ident() }
58    ) => {
59        $(#[$attr])*
60        #[derive($crate::content_hash::ContentHash, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
61        $vis struct $name(Vec<u8>);
62        $crate::object_id::_impl_id_type!($name, $hex_method);
63    };
64}
65
66#[doc(hidden)]
67#[macro_export]
68macro_rules! _impl_id_type {
69    ($name:ident, $hex_method:ident) => {
70        #[allow(dead_code)]
71        impl $name {
72            /// Creates a new instance of this id type from the given bytes.
73            pub fn new(value: Vec<u8>) -> Self {
74                Self(value)
75            }
76
77            /// Creates a new instance of this id type from the given byte slice.
78            pub fn from_bytes(bytes: &[u8]) -> Self {
79                Self(bytes.to_vec())
80            }
81
82            /// Parses the given hex string into an ObjectId.
83            ///
84            /// The given string must be valid. A static str is required to
85            /// prevent API misuse.
86            pub fn from_hex(hex: &'static str) -> Self {
87                Self::try_from_hex(hex).unwrap()
88            }
89
90            /// Parses the given hex string into an ObjectId.
91            pub fn try_from_hex(hex: impl AsRef<[u8]>) -> Option<Self> {
92                $crate::hex_util::decode_hex(hex).map(Self)
93            }
94        }
95
96        impl std::fmt::Debug for $name {
97            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
98                // TODO: should we use $hex_method here?
99                f.debug_tuple(stringify!($name)).field(&self.hex()).finish()
100            }
101        }
102
103        impl std::fmt::Display for $name {
104            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
105                f.pad(&self.$hex_method())
106            }
107        }
108
109        impl serde::Serialize for $name {
110            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
111            where
112                S: serde::Serializer,
113            {
114                if serializer.is_human_readable() {
115                    self.$hex_method().serialize(serializer)
116                } else {
117                    self.as_bytes().serialize(serializer)
118                }
119            }
120        }
121
122        impl $crate::object_id::ObjectId for $name {
123            fn object_type(&self) -> String {
124                stringify!($name)
125                    .strip_suffix("Id")
126                    .unwrap()
127                    .to_ascii_lowercase()
128                    .to_string()
129            }
130
131            fn as_bytes(&self) -> &[u8] {
132                &self.0
133            }
134
135            fn to_bytes(&self) -> Vec<u8> {
136                self.0.clone()
137            }
138
139            fn hex(&self) -> String {
140                $crate::hex_util::encode_hex(&self.0)
141            }
142        }
143    };
144}
145
146#[doc(inline)]
147pub use _id_type as id_type;
148pub use _impl_id_type;
149
150/// An identifier prefix (typically from a type implementing the [`ObjectId`]
151/// trait) with facilities for converting between bytes and a hex string.
152#[derive(Clone, PartialEq, Eq)]
153pub struct HexPrefix {
154    // For odd-length prefixes, the lower 4 bits of the last byte are
155    // zero-filled (e.g. the prefix "abc" is stored in two bytes as "abc0").
156    min_prefix_bytes: Vec<u8>,
157    has_odd_byte: bool,
158}
159
160impl HexPrefix {
161    /// Returns a new `HexPrefix` or `None` if `prefix` cannot be decoded from
162    /// hex to bytes.
163    pub fn try_from_hex(prefix: impl AsRef<[u8]>) -> Option<Self> {
164        let (min_prefix_bytes, has_odd_byte) = hex_util::decode_hex_prefix(prefix)?;
165        Some(Self {
166            min_prefix_bytes,
167            has_odd_byte,
168        })
169    }
170
171    /// Returns a new `HexPrefix` or `None` if `prefix` cannot be decoded from
172    /// "reverse" hex to bytes.
173    pub fn try_from_reverse_hex(prefix: impl AsRef<[u8]>) -> Option<Self> {
174        let (min_prefix_bytes, has_odd_byte) = hex_util::decode_reverse_hex_prefix(prefix)?;
175        Some(Self {
176            min_prefix_bytes,
177            has_odd_byte,
178        })
179    }
180
181    /// Returns a new `HexPrefix` representing the given full bytes (i.e. an
182    /// even number of hex digits.)
183    pub fn from_bytes(bytes: &[u8]) -> Self {
184        Self {
185            min_prefix_bytes: bytes.to_owned(),
186            has_odd_byte: false,
187        }
188    }
189
190    /// Returns a new `HexPrefix` representing the given `id`.
191    pub fn from_id<T: ObjectId + ?Sized>(id: &T) -> Self {
192        Self::from_bytes(id.as_bytes())
193    }
194
195    /// Returns string representation of this prefix using hex digits.
196    pub fn hex(&self) -> String {
197        let mut hex_string = hex_util::encode_hex(&self.min_prefix_bytes);
198        if self.has_odd_byte {
199            hex_string.pop().unwrap();
200        }
201        hex_string
202    }
203
204    /// Returns string representation of this prefix using `z-k` "digits".
205    pub fn reverse_hex(&self) -> String {
206        let mut hex_string = hex_util::encode_reverse_hex(&self.min_prefix_bytes);
207        if self.has_odd_byte {
208            hex_string.pop().unwrap();
209        }
210        hex_string
211    }
212
213    /// Minimum bytes that would match this prefix. (e.g. "abc0" for "abc")
214    ///
215    /// Use this to partition a sorted slice, and test `matches(id)` from there.
216    pub fn min_prefix_bytes(&self) -> &[u8] {
217        &self.min_prefix_bytes
218    }
219
220    /// Returns the bytes representation if this prefix can be a full id.
221    pub fn as_full_bytes(&self) -> Option<&[u8]> {
222        (!self.has_odd_byte).then_some(&self.min_prefix_bytes)
223    }
224
225    fn split_odd_byte(&self) -> (Option<u8>, &[u8]) {
226        if self.has_odd_byte {
227            let (&odd, prefix) = self.min_prefix_bytes.split_last().unwrap();
228            (Some(odd), prefix)
229        } else {
230            (None, &self.min_prefix_bytes)
231        }
232    }
233
234    /// Returns whether the stored prefix matches the prefix of `id`.
235    pub fn matches<Q: ObjectId>(&self, id: &Q) -> bool {
236        let id_bytes = id.as_bytes();
237        let (maybe_odd, prefix) = self.split_odd_byte();
238        if id_bytes.starts_with(prefix) {
239            if let Some(odd) = maybe_odd {
240                matches!(id_bytes.get(prefix.len()), Some(v) if v & 0xf0 == odd)
241            } else {
242                true
243            }
244        } else {
245            false
246        }
247    }
248}
249
250impl Debug for HexPrefix {
251    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
252        f.debug_tuple("HexPrefix").field(&self.hex()).finish()
253    }
254}
255
256/// The result of a prefix search.
257#[derive(Debug, Clone, PartialEq, Eq)]
258pub enum PrefixResolution<T> {
259    /// The prefix matched no objects.
260    NoMatch,
261    /// The prefix matched exactly one object.
262    SingleMatch(T),
263    /// The prefix matched more than one object.
264    AmbiguousMatch,
265}
266
267impl<T> PrefixResolution<T> {
268    /// Transforms the matched object, if any, by applying `f` to it.
269    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> PrefixResolution<U> {
270        match self {
271            Self::NoMatch => PrefixResolution::NoMatch,
272            Self::SingleMatch(x) => PrefixResolution::SingleMatch(f(x)),
273            Self::AmbiguousMatch => PrefixResolution::AmbiguousMatch,
274        }
275    }
276
277    /// Transforms the matched object, if any, by applying `f` to it, turning
278    /// a single match into [`NoMatch`](Self::NoMatch) if `f` returns `None`.
279    pub fn filter_map<U>(self, f: impl FnOnce(T) -> Option<U>) -> PrefixResolution<U> {
280        match self {
281            Self::NoMatch => PrefixResolution::NoMatch,
282            Self::SingleMatch(x) => match f(x) {
283                None => PrefixResolution::NoMatch,
284                Some(y) => PrefixResolution::SingleMatch(y),
285            },
286            Self::AmbiguousMatch => PrefixResolution::AmbiguousMatch,
287        }
288    }
289}
290
291impl<T: Clone> PrefixResolution<T> {
292    /// Combines two resolutions, e.g. from different sources, turning two
293    /// single matches into an ambiguous match.
294    pub fn plus(&self, other: &Self) -> Self {
295        match (self, other) {
296            (Self::NoMatch, other) => other.clone(),
297            (local, Self::NoMatch) => local.clone(),
298            (Self::AmbiguousMatch, _) => Self::AmbiguousMatch,
299            (_, Self::AmbiguousMatch) => Self::AmbiguousMatch,
300            (Self::SingleMatch(_), Self::SingleMatch(_)) => Self::AmbiguousMatch,
301        }
302    }
303}
304
305#[cfg(test)]
306mod tests {
307    use super::*;
308
309    id_type!(SomeId { hex() });
310
311    #[test]
312    fn test_hex_prefix_prefixes() {
313        let prefix = HexPrefix::try_from_hex("").unwrap();
314        assert_eq!(prefix.min_prefix_bytes(), b"");
315
316        let prefix = HexPrefix::try_from_hex("1").unwrap();
317        assert_eq!(prefix.min_prefix_bytes(), b"\x10");
318
319        let prefix = HexPrefix::try_from_hex("12").unwrap();
320        assert_eq!(prefix.min_prefix_bytes(), b"\x12");
321
322        let prefix = HexPrefix::try_from_hex("123").unwrap();
323        assert_eq!(prefix.min_prefix_bytes(), b"\x12\x30");
324
325        let bad_prefix = HexPrefix::try_from_hex("0x123");
326        assert_eq!(bad_prefix, None);
327
328        let bad_prefix = HexPrefix::try_from_hex("foobar");
329        assert_eq!(bad_prefix, None);
330    }
331
332    #[test]
333    fn test_hex_prefix_matches() {
334        let id = SomeId::from_hex("1234");
335
336        assert!(HexPrefix::try_from_hex("").unwrap().matches(&id));
337        assert!(HexPrefix::try_from_hex("1").unwrap().matches(&id));
338        assert!(HexPrefix::try_from_hex("12").unwrap().matches(&id));
339        assert!(HexPrefix::try_from_hex("123").unwrap().matches(&id));
340        assert!(HexPrefix::try_from_hex("1234").unwrap().matches(&id));
341        assert!(!HexPrefix::try_from_hex("12345").unwrap().matches(&id));
342
343        assert!(!HexPrefix::try_from_hex("a").unwrap().matches(&id));
344        assert!(!HexPrefix::try_from_hex("1a").unwrap().matches(&id));
345        assert!(!HexPrefix::try_from_hex("12a").unwrap().matches(&id));
346        assert!(!HexPrefix::try_from_hex("123a").unwrap().matches(&id));
347    }
348}