1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
use crate::util::{decode_base64, encode_base64, parse_string_literal};
pub(crate) use rusqlite::types::Value;
#[cfg(test)]
mod tests;
pub(crate) fn value_to_string(v: &Value) -> String {
match v {
Value::Null => String::from("null"),
Value::Integer(n) => n.to_string(),
Value::Real(n) => {
let mut s = n.to_string();
if !s.contains(".") {
s = format!("{s}.0");
}
s
},
// I don't use `format!("{s:?}")` because...
// 1. There are so many edge cases with string escapes and
// `parse_string_literal` cannot handle them properly.
// I hope there's `eval(s)` in rust, but there isn't.
// 2. We don't have to handle all the edge cases. All we
// need is a format that `parse_string_literal` can parse.
// 3. If we ignore the edge cases, this implementation is faster
// than `format!("{s:?}")`.
Value::Text(s) => {
let mut chars = Vec::with_capacity(s.len() + 2);
chars.push('"');
for ch in s.chars() {
match ch {
'\n' | '\r' | '\t' | '\0' => {
chars.push('\\');
match ch {
'\n' => {
chars.push('n');
},
'\r' => {
chars.push('r');
},
'\t' => {
chars.push('t');
},
'\0' => {
chars.push('0');
},
_ => unreachable!(),
}
},
'"' | '\\' => {
chars.push('\\');
chars.push(ch);
},
_ => {
chars.push(ch);
},
}
}
chars.push('"');
chars.into_iter().collect()
},
Value::Blob(v) => {
// 1. Blob is not readable anyway. We don't have to try to make it readable.
// 2. Some 3rd party git tools require a file to be valid utf-8. So I'm using base64.
// 3. "null" is also a valid base64 output. In order to avoid that, I add a prefix to the output.
format!("b{}", encode_base64(&v))
},
}
}
pub(crate) fn value_from_string(s: &str) -> Option<Value> {
let b = s.as_bytes();
match b.get(0) {
Some(b'n') => {
if b == b"null" {
Some(Value::Null)
} else {
None
}
},
// `value_to_string(Value::Real(n))` will always contain ".",
// so `s.parse::<i64>()` can always tell whether it's
// `Value::Real` or `Value::Integer`.
Some(b'0'..=b'9' | b'-') => match s.parse::<i64>() {
Ok(n) => Some(Value::Integer(n)),
Err(_) => match s.parse::<f64>() {
Ok(n) => Some(Value::Real(n)),
Err(_) => None,
}
},
Some(b'"') => match parse_string_literal(b) {
Some((s, i)) => {
if i == b.len() - 1 {
Some(Value::Text(s))
}
else {
None
}
},
None => None,
},
Some(b'b') => match decode_base64(s.get(1..).unwrap()) {
Ok(v) => Some(Value::Blob(v)),
Err(_) => None,
},
Some(_) => None,
None => None,
}
}