gix_object/tag/
ref_iter.rs

1use bstr::BStr;
2use gix_hash::{oid, ObjectId};
3use winnow::{
4    combinator::{eof, opt, terminated},
5    error::{ParserError, StrContext},
6    prelude::*,
7    stream::AsChar,
8    token::take_while,
9};
10
11use crate::{bstr::ByteSlice, parse, parse::NL, tag::decode, Kind, TagRefIter};
12
13#[derive(Default, Copy, Clone)]
14pub(crate) enum State {
15    #[default]
16    Target,
17    TargetKind,
18    Name,
19    Tagger,
20    Message,
21}
22
23impl<'a> TagRefIter<'a> {
24    /// Create a tag iterator from data.
25    pub fn from_bytes(data: &'a [u8]) -> TagRefIter<'a> {
26        TagRefIter {
27            data,
28            state: State::default(),
29        }
30    }
31
32    /// Returns the target id of this tag if it is the first function called and if there is no error in decoding
33    /// the data.
34    ///
35    /// Note that this method must only be called once or else will always return None while consuming a single token.
36    /// Errors are coerced into options, hiding whether there was an error or not. The caller should assume an error if they
37    /// call the method as intended. Such a squelched error cannot be recovered unless the objects data is retrieved and parsed again.
38    /// `next()`.
39    pub fn target_id(mut self) -> Result<ObjectId, crate::decode::Error> {
40        let token = self.next().ok_or_else(missing_field)??;
41        Token::into_id(token).ok_or_else(missing_field)
42    }
43
44    /// Returns the taggers signature if there is no decoding error, and if this field exists.
45    /// Errors are coerced into options, hiding whether there was an error or not. The caller knows if there was an error or not.
46    pub fn tagger(mut self) -> Result<Option<gix_actor::SignatureRef<'a>>, crate::decode::Error> {
47        self.find_map(|t| match t {
48            Ok(Token::Tagger(signature)) => Some(Ok(signature)),
49            Err(err) => Some(Err(err)),
50            _ => None,
51        })
52        .ok_or_else(missing_field)?
53    }
54}
55
56fn missing_field() -> crate::decode::Error {
57    crate::decode::empty_error()
58}
59
60impl<'a> TagRefIter<'a> {
61    #[inline]
62    fn next_inner(mut i: &'a [u8], state: &mut State) -> Result<(&'a [u8], Token<'a>), crate::decode::Error> {
63        let input = &mut i;
64        match Self::next_inner_(input, state) {
65            Ok(token) => Ok((*input, token)),
66            Err(err) => Err(crate::decode::Error::with_err(err, input)),
67        }
68    }
69
70    fn next_inner_(
71        input: &mut &'a [u8],
72        state: &mut State,
73    ) -> Result<Token<'a>, winnow::error::ErrMode<crate::decode::ParseError>> {
74        use State::*;
75        Ok(match state {
76            Target => {
77                let target = (|i: &mut _| parse::header_field(i, b"object", parse::hex_hash))
78                    .context(StrContext::Expected("object <40 lowercase hex char>".into()))
79                    .parse_next(input)?;
80                *state = TargetKind;
81                Token::Target {
82                    id: ObjectId::from_hex(target).expect("parsing validation"),
83                }
84            }
85            TargetKind => {
86                let kind = (|i: &mut _| parse::header_field(i, b"type", take_while(1.., AsChar::is_alpha)))
87                    .context(StrContext::Expected("type <object kind>".into()))
88                    .parse_next(input)?;
89                let kind = Kind::from_bytes(kind).map_err(|_| winnow::error::ErrMode::from_input(input))?;
90                *state = Name;
91                Token::TargetKind(kind)
92            }
93            Name => {
94                let tag_version = (|i: &mut _| parse::header_field(i, b"tag", take_while(1.., |b| b != NL[0])))
95                    .context(StrContext::Expected("tag <version>".into()))
96                    .parse_next(input)?;
97                *state = Tagger;
98                Token::Name(tag_version.as_bstr())
99            }
100            Tagger => {
101                let signature = opt(|i: &mut _| parse::header_field(i, b"tagger", parse::signature))
102                    .context(StrContext::Expected("tagger <signature>".into()))
103                    .parse_next(input)?;
104                *state = Message;
105                Token::Tagger(signature)
106            }
107            Message => {
108                let (message, pgp_signature) = terminated(decode::message, eof).parse_next(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, pgp_signature }
114            }
115        })
116    }
117}
118
119impl<'a> Iterator for TagRefIter<'a> {
120    type Item = Result<Token<'a>, crate::decode::Error>;
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) {
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#[allow(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        pgp_signature: Option<&'a BStr>,
152    },
153}
154
155impl Token<'_> {
156    /// Return the object id of this token if its a [Target][Token::Target].
157    pub fn id(&self) -> Option<&oid> {
158        match self {
159            Token::Target { id } => Some(id.as_ref()),
160            _ => None,
161        }
162    }
163
164    /// Return the owned object id of this token if its a [Target][Token::Target].
165    pub fn into_id(self) -> Option<ObjectId> {
166        match self {
167            Token::Target { id } => Some(id),
168            _ => None,
169        }
170    }
171}