iota_graphql_rpc/types/
base64.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::str::FromStr;
6
7use async_graphql::*;
8use fastcrypto::encoding::{Base64 as FastCryptoBase64, Encoding as FastCryptoEncoding};
9
10#[derive(Clone, Debug, PartialEq, Eq)]
11pub(crate) struct Base64(pub(crate) Vec<u8>);
12
13/// String containing Base64-encoded binary data.
14#[Scalar]
15impl ScalarType for Base64 {
16    fn parse(value: Value) -> InputValueResult<Self> {
17        match value {
18            Value::String(s) => Ok(Base64(
19                FastCryptoBase64::decode(&s)
20                    .map_err(|r| InputValueError::custom(format!("{r}")))?,
21            )),
22            _ => Err(InputValueError::expected_type(value)),
23        }
24    }
25
26    fn to_value(&self) -> Value {
27        Value::String(FastCryptoBase64::encode(self.0.clone()))
28    }
29}
30
31impl FromStr for Base64 {
32    type Err = InputValueError<String>;
33
34    fn from_str(s: &str) -> Result<Self, Self::Err> {
35        Ok(Base64(
36            FastCryptoBase64::decode(s).map_err(|_| InputValueError::custom("Invalid Base64"))?,
37        ))
38    }
39}
40
41impl From<&Vec<u8>> for Base64 {
42    fn from(bytes: &Vec<u8>) -> Self {
43        Base64(bytes.clone())
44    }
45}
46
47impl From<&[u8]> for Base64 {
48    fn from(bytes: &[u8]) -> Self {
49        Base64(bytes.to_vec())
50    }
51}
52
53impl From<Vec<u8>> for Base64 {
54    fn from(bytes: Vec<u8>) -> Self {
55        Base64(bytes)
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use async_graphql::Value;
62
63    use super::*;
64
65    fn assert_input_value_error<T, U>(result: Result<T, InputValueError<U>>) {
66        match result {
67            Err(InputValueError { .. }) => {}
68            _ => panic!("Expected InputValueError"),
69        }
70    }
71
72    #[test]
73    fn test_parse_valid_base64() {
74        let input = Value::String("SGVsbG8gd29ybGQ=".to_string());
75        let parsed = <Base64 as ScalarType>::parse(input).unwrap();
76        assert_eq!(parsed.0, b"Hello world");
77    }
78
79    #[test]
80    fn test_parse_invalid_base64() {
81        let input = Value::String("SGVsbG8gd29ybGQ@".to_string());
82        let parsed = <Base64 as ScalarType>::parse(input);
83        assert_input_value_error(parsed);
84    }
85
86    #[test]
87    fn test_parse_invalid_boolean_value() {
88        let input = Value::Boolean(true);
89        let parsed = <Base64 as ScalarType>::parse(input);
90        assert_input_value_error(parsed);
91    }
92
93    #[test]
94    fn test_parse_invalid_number() {
95        let input = Value::Number(1.into());
96        let parsed = <Base64 as ScalarType>::parse(input);
97        assert_input_value_error(parsed);
98    }
99
100    #[test]
101    fn test_to_value() {
102        let base64 = Base64(b"Hello world".to_vec());
103        let value = <Base64 as ScalarType>::to_value(&base64);
104        assert_eq!(value, Value::String("SGVsbG8gd29ybGQ=".to_string()));
105    }
106
107    #[test]
108    fn test_from_str_valid() {
109        let base64_str = "SGVsbG8gd29ybGQ=";
110        let base64 = Base64::from_str(base64_str).unwrap();
111        assert_eq!(base64.0, b"Hello world");
112    }
113
114    #[test]
115    fn test_from_str_invalid() {
116        let base64_str = "SGVsbG8gd29ybGQ@";
117        let result = Base64::from_str(base64_str);
118        assert_input_value_error(result);
119    }
120
121    #[test]
122    fn test_from_vec_reference() {
123        let vec = vec![1, 2, 3, 4, 5];
124        let base64 = Base64::from(&vec);
125        assert_eq!(base64.0, vec);
126    }
127
128    #[test]
129    fn test_from_vec() {
130        let vec = vec![1, 2, 3, 4, 5];
131        let base64 = Base64::from(vec.clone());
132        assert_eq!(base64.0, vec);
133    }
134}