git_object/tag/
ref_iter.rs

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