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)
90                    .map_err(|_| winnow::error::ErrMode::from_error_kind(input, winnow::error::ErrorKind::Verify))?;
91                *state = Name;
92                Token::TargetKind(kind)
93            }
94            Name => {
95                let tag_version = (|i: &mut _| parse::header_field(i, b"tag", take_while(1.., |b| b != NL[0])))
96                    .context(StrContext::Expected("tag <version>".into()))
97                    .parse_next(input)?;
98                *state = Tagger;
99                Token::Name(tag_version.as_bstr())
100            }
101            Tagger => {
102                let signature = opt(|i: &mut _| parse::header_field(i, b"tagger", parse::signature))
103                    .context(StrContext::Expected("tagger <signature>".into()))
104                    .parse_next(input)?;
105                *state = Message;
106                Token::Tagger(signature)
107            }
108            Message => {
109                let (message, pgp_signature) = terminated(decode::message, eof).parse_next(input)?;
110                debug_assert!(
111                    input.is_empty(),
112                    "we should have consumed all data - otherwise iter may go forever"
113                );
114                Token::Body { message, pgp_signature }
115            }
116        })
117    }
118}
119
120impl<'a> Iterator for TagRefIter<'a> {
121    type Item = Result<Token<'a>, crate::decode::Error>;
122
123    fn next(&mut self) -> Option<Self::Item> {
124        if self.data.is_empty() {
125            return None;
126        }
127        match Self::next_inner(self.data, &mut self.state) {
128            Ok((data, token)) => {
129                self.data = data;
130                Some(Ok(token))
131            }
132            Err(err) => {
133                self.data = &[];
134                Some(Err(err))
135            }
136        }
137    }
138}
139
140/// A token returned by the [tag iterator][TagRefIter].
141#[allow(missing_docs)]
142#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
143pub enum Token<'a> {
144    Target {
145        id: ObjectId,
146    },
147    TargetKind(Kind),
148    Name(&'a BStr),
149    Tagger(Option<gix_actor::SignatureRef<'a>>),
150    Body {
151        message: &'a BStr,
152        pgp_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}