Skip to main content

gix_object/tag/
ref_iter.rs

1use bstr::BStr;
2use gix_error::{OptionExt, Result};
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) -> Result<ObjectId> {
52        let token = self.next().ok_or_raise(missing_field)??;
53        Token::into_id(token).ok_or_raise(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) -> Result<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_raise(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(mut i: &'a [u8], state: &mut State, hash_kind: gix_hash::Kind) -> Result<(&'a [u8], Token<'a>)> {
75        let input = &mut i;
76        match Self::next_inner_(input, state, hash_kind) {
77            Ok(token) => Ok((*input, token)),
78            Err(err) => Err(err),
79        }
80    }
81
82    fn next_inner_(input: &mut &'a [u8], state: &mut State, hash_kind: gix_hash::Kind) -> Result<Token<'a>> {
83        use State::*;
84        Ok(match state {
85            Target => {
86                let target = decode::target(input, hash_kind)?;
87                *state = TargetKind;
88                Token::Target {
89                    id: ObjectId::from_hex(target).expect("parsing validation"),
90                }
91            }
92            TargetKind => {
93                let kind = decode::kind(input)?;
94                *state = Name;
95                Token::TargetKind(kind)
96            }
97            Name => {
98                let tag_version = decode::name(input)?;
99                *state = Tagger;
100                Token::Name(tag_version.as_bstr())
101            }
102            Tagger => {
103                *state = Message;
104                let signature = decode::tagger(input)?;
105                Token::Tagger(signature)
106            }
107            Message => {
108                let (message, signature) = decode::message(input)?;
109                debug_assert!(
110                    input.is_empty(),
111                    "we should have consumed all data - otherwise iter may go forever"
112                );
113                Token::Body { message, signature }
114            }
115        })
116    }
117}
118
119impl<'a> Iterator for TagRefIter<'a> {
120    type Item = Result<Token<'a>>;
121
122    fn next(&mut self) -> Option<Self::Item> {
123        if self.data.is_empty() {
124            return None;
125        }
126        match Self::next_inner(self.data, &mut self.state, self.hash_kind) {
127            Ok((data, token)) => {
128                self.data = data;
129                Some(Ok(token))
130            }
131            Err(err) => {
132                self.data = &[];
133                Some(Err(err))
134            }
135        }
136    }
137}
138
139/// A token returned by the [tag iterator][TagRefIter].
140#[expect(missing_docs)]
141#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
142pub enum Token<'a> {
143    Target {
144        id: ObjectId,
145    },
146    TargetKind(Kind),
147    Name(&'a BStr),
148    Tagger(Option<gix_actor::SignatureRef<'a>>),
149    Body {
150        message: &'a BStr,
151        /// Any Git-supported in-body signature.
152        signature: Option<&'a BStr>,
153    },
154}
155
156impl Token<'_> {
157    /// Return the object id of this token if its a [Target][Token::Target].
158    pub fn id(&self) -> Option<&oid> {
159        match self {
160            Token::Target { id } => Some(id.as_ref()),
161            _ => None,
162        }
163    }
164
165    /// Return the owned object id of this token if its a [Target][Token::Target].
166    pub fn into_id(self) -> Option<ObjectId> {
167        match self {
168            Token::Target { id } => Some(id),
169            _ => None,
170        }
171    }
172}