Skip to main content

gix_object/tag/
ref_iter.rs

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