radicle_surf/
blob.rs

1// This file is part of radicle-surf
2// <https://github.com/radicle-dev/radicle-surf>
3//
4// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
5//
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License version 3 or
8// later as published by the Free Software Foundation.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with this program. If not, see <https://www.gnu.org/licenses/>.
17
18//! Represents git object type 'blob', i.e. actual file contents.
19//! See git [doc](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects) for more details.
20
21use std::ops::Deref;
22
23use radicle_git_ext::Oid;
24
25#[cfg(feature = "serde")]
26use serde::{
27    ser::{SerializeStruct as _, Serializer},
28    Serialize,
29};
30
31use crate::Commit;
32
33/// Represents a git blob object.
34///
35/// The type parameter `T` can be fulfilled by [`BlobRef`] or a
36/// [`Vec`] of bytes.
37pub struct Blob<T> {
38    id: Oid,
39    is_binary: bool,
40    commit: Commit,
41    content: T,
42}
43
44impl<T> Blob<T> {
45    pub fn object_id(&self) -> Oid {
46        self.id
47    }
48
49    pub fn is_binary(&self) -> bool {
50        self.is_binary
51    }
52
53    /// Returns the commit that created this blob.
54    pub fn commit(&self) -> &Commit {
55        &self.commit
56    }
57
58    pub fn content(&self) -> &[u8]
59    where
60        T: AsRef<[u8]>,
61    {
62        self.content.as_ref()
63    }
64
65    pub fn size(&self) -> usize
66    where
67        T: AsRef<[u8]>,
68    {
69        self.content.as_ref().len()
70    }
71}
72
73impl<'a> Blob<BlobRef<'a>> {
74    /// Returns the [`Blob`] wrapping around an underlying [`git2::Blob`].
75    pub(crate) fn new(id: Oid, git2_blob: git2::Blob<'a>, commit: Commit) -> Self {
76        let is_binary = git2_blob.is_binary();
77        let content = BlobRef { inner: git2_blob };
78        Self {
79            id,
80            is_binary,
81            content,
82            commit,
83        }
84    }
85
86    /// Converts into a `Blob` with owned content bytes.
87    pub fn to_owned(&self) -> Blob<Vec<u8>> {
88        Blob {
89            id: self.id,
90            content: self.content.to_vec(),
91            commit: self.commit.clone(),
92            is_binary: self.is_binary,
93        }
94    }
95}
96
97/// Represents a blob with borrowed content bytes.
98pub struct BlobRef<'a> {
99    pub(crate) inner: git2::Blob<'a>,
100}
101
102impl BlobRef<'_> {
103    pub fn id(&self) -> Oid {
104        self.inner.id().into()
105    }
106}
107
108impl AsRef<[u8]> for BlobRef<'_> {
109    fn as_ref(&self) -> &[u8] {
110        self.inner.content()
111    }
112}
113
114impl Deref for BlobRef<'_> {
115    type Target = [u8];
116
117    fn deref(&self) -> &Self::Target {
118        self.inner.content()
119    }
120}
121
122#[cfg(feature = "serde")]
123impl<T> Serialize for Blob<T>
124where
125    T: AsRef<[u8]>,
126{
127    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
128    where
129        S: Serializer,
130    {
131        use base64::Engine as _;
132
133        const FIELDS: usize = 4;
134        let mut state = serializer.serialize_struct("Blob", FIELDS)?;
135        state.serialize_field("id", &self.id)?;
136        state.serialize_field("binary", &self.is_binary())?;
137
138        let bytes = self.content.as_ref();
139        match std::str::from_utf8(bytes) {
140            Ok(s) => state.serialize_field("content", s)?,
141            Err(_) => {
142                let encoded = base64::prelude::BASE64_STANDARD.encode(bytes);
143                state.serialize_field("content", &encoded)?
144            }
145        };
146        state.serialize_field("lastCommit", &self.commit)?;
147        state.end()
148    }
149}
150
151#[cfg(feature = "serde")]
152impl Serialize for BlobRef<'_> {
153    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
154    where
155        S: Serializer,
156    {
157        use base64::Engine as _;
158
159        const FIELDS: usize = 3;
160        let mut state = serializer.serialize_struct("BlobRef", FIELDS)?;
161        state.serialize_field("id", &self.id())?;
162        state.serialize_field("binary", &self.inner.is_binary())?;
163
164        let bytes = self.as_ref();
165        match std::str::from_utf8(bytes) {
166            Ok(s) => state.serialize_field("content", s)?,
167            Err(_) => {
168                let encoded = base64::prelude::BASE64_STANDARD.encode(bytes);
169                state.serialize_field("content", &encoded)?
170            }
171        };
172        state.end()
173    }
174}