Skip to main content

gix_object/tag/
ref_iter.rs

1use bstr::BStr;
2use gix_hash::{ObjectId, oid};
3
4use crate::{Kind, TagRefIter, bstr::ByteSlice, tag::decode};
5
6#[derive(Default, Copy, Clone)]
7pub(crate) enum State {
8    #[default]
9    Target,
10    TargetKind,
11    Name,
12    Tagger,
13    Message,
14}
15
16impl<'a> TagRefIter<'a> {
17    /// Create a tag iterator from `data`, parsing hashes as `object_hash`.
18    pub fn from_bytes(data: &'a [u8], hash_kind: gix_hash::Kind) -> TagRefIter<'a> {
19        TagRefIter {
20            data,
21            state: State::default(),
22            hash_kind,
23        }
24    }
25
26    /// Extract a signature and the exact original bytes it covers via `(signature, data-without-signature)`.
27    ///
28    /// `data` must be the complete, undecoded tag-object contents—the same byte slice that can be passed to
29    /// [`TagRefIter::from_bytes()`], not only the tag message or armored signature. Keeping the original bytes intact is
30    /// necessary because signature verification covers their exact representation.
31    pub fn signature(data: &'a [u8]) -> Option<(crate::signature::SignatureRef<'a>, crate::signature::SignedData<'a>)> {
32        crate::signature::find(data).map(|(start, format)| {
33            (
34                crate::signature::SignatureRef {
35                    format,
36                    data: data[start..].as_bstr(),
37                },
38                crate::signature::SignedData::new(data, start..data.len()),
39            )
40        })
41    }
42
43    /// Returns the target id of this tag if it is the first function called and if there is no error in decoding
44    /// the data.
45    ///
46    /// Note that this method must only be called once or else will always return None while consuming a single token.
47    /// Errors are coerced into options, hiding whether there was an error or not. The caller should assume an error if they
48    /// call the method as intended. Such a squelched error cannot be recovered unless the objects data is retrieved and parsed again.
49    /// `next()`.
50    pub fn target_id(mut self) -> Result<ObjectId, crate::decode::Error> {
51        let token = self.next().ok_or_else(missing_field)??;
52        Token::into_id(token).ok_or_else(missing_field)
53    }
54
55    /// Returns the taggers signature if there is no decoding error, and if this field exists.
56    /// Errors are coerced into options, hiding whether there was an error or not. The caller knows if there was an error or not.
57    pub fn tagger(mut self) -> Result<Option<gix_actor::SignatureRef<'a>>, crate::decode::Error> {
58        self.find_map(|t| match t {
59            Ok(Token::Tagger(signature)) => Some(Ok(signature)),
60            Err(err) => Some(Err(err)),
61            _ => None,
62        })
63        .ok_or_else(missing_field)?
64    }
65}
66
67fn missing_field() -> crate::decode::Error {
68    crate::decode::empty_error()
69}
70
71impl<'a> TagRefIter<'a> {
72    #[inline]
73    fn next_inner(
74        mut i: &'a [u8],
75        state: &mut State,
76        hash_kind: gix_hash::Kind,
77    ) -> Result<(&'a [u8], Token<'a>), crate::decode::Error> {
78        let input = &mut i;
79        match Self::next_inner_(input, state, hash_kind) {
80            Ok(token) => Ok((*input, token)),
81            Err(err) => Err(err),
82        }
83    }
84
85    fn next_inner_(
86        input: &mut &'a [u8],
87        state: &mut State,
88        hash_kind: gix_hash::Kind,
89    ) -> Result<Token<'a>, crate::decode::Error> {
90        use State::*;
91        Ok(match state {
92            Target => {
93                let target = decode::target(input, hash_kind)?;
94                *state = TargetKind;
95                Token::Target {
96                    id: ObjectId::from_hex(target).expect("parsing validation"),
97                }
98            }
99            TargetKind => {
100                let kind = decode::kind(input)?;
101                *state = Name;
102                Token::TargetKind(kind)
103            }
104            Name => {
105                let tag_version = decode::name(input)?;
106                *state = Tagger;
107                Token::Name(tag_version.as_bstr())
108            }
109            Tagger => {
110                *state = Message;
111                let signature = decode::tagger(input)?;
112                Token::Tagger(signature)
113            }
114            Message => {
115                let (message, signature) = decode::message(input)?;
116                debug_assert!(
117                    input.is_empty(),
118                    "we should have consumed all data - otherwise iter may go forever"
119                );
120                Token::Body { message, signature }
121            }
122        })
123    }
124}
125
126impl<'a> Iterator for TagRefIter<'a> {
127    type Item = Result<Token<'a>, crate::decode::Error>;
128
129    fn next(&mut self) -> Option<Self::Item> {
130        if self.data.is_empty() {
131            return None;
132        }
133        match Self::next_inner(self.data, &mut self.state, self.hash_kind) {
134            Ok((data, token)) => {
135                self.data = data;
136                Some(Ok(token))
137            }
138            Err(err) => {
139                self.data = &[];
140                Some(Err(err))
141            }
142        }
143    }
144}
145
146/// A token returned by the [tag iterator][TagRefIter].
147#[expect(missing_docs)]
148#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
149pub enum Token<'a> {
150    Target {
151        id: ObjectId,
152    },
153    TargetKind(Kind),
154    Name(&'a BStr),
155    Tagger(Option<gix_actor::SignatureRef<'a>>),
156    Body {
157        message: &'a BStr,
158        /// Any Git-supported in-body signature.
159        signature: Option<&'a BStr>,
160    },
161}
162
163impl Token<'_> {
164    /// Return the object id of this token if its a [Target][Token::Target].
165    pub fn id(&self) -> Option<&oid> {
166        match self {
167            Token::Target { id } => Some(id.as_ref()),
168            _ => None,
169        }
170    }
171
172    /// Return the owned object id of this token if its a [Target][Token::Target].
173    pub fn into_id(self) -> Option<ObjectId> {
174        match self {
175            Token::Target { id } => Some(id),
176            _ => None,
177        }
178    }
179}