Skip to content

Commit 1636078

Browse files
authored
RUST-554 Support parsing $uuid as extended JSON representation for subtype 4 binary (#213)
1 parent 432a025 commit 1636078

File tree

5 files changed

+73
-3
lines changed

5 files changed

+73
-3
lines changed

etc/update-spec-tests.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@ fi
1919
tmpdir=`perl -MFile::Temp=tempdir -wle 'print tempdir(TMPDIR => 1, CLEANUP => 0)'`
2020
curl -sL https://github.com/mongodb/specifications/archive/master.zip -o "$tmpdir/specs.zip"
2121
unzip -d "$tmpdir" "$tmpdir/specs.zip" > /dev/null
22-
mkdir -p "tests/spec/json/$1"
23-
rsync -ah "$tmpdir/specifications-master/source/$1/tests/" "tests/spec/json/$1" --delete
22+
mkdir -p "src/tests/spec/json/$1"
23+
rsync -ah "$tmpdir/specifications-master/source/$1/tests/" "src/tests/spec/json/$1" --delete
2424
rm -rf "$tmpdir"

src/extjson/de.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ impl TryFrom<serde_json::Map<String, serde_json::Value>> for Bson {
117117
return Ok(Bson::Binary(binary.parse()?));
118118
}
119119

120+
if obj.contains_key("$uuid") {
121+
let uuid: models::Uuid = serde_json::from_value(obj.into())?;
122+
return Ok(Bson::Binary(uuid.parse()?));
123+
}
124+
120125
if obj.contains_key("$code") {
121126
let code_w_scope: models::JavaScriptCodeWithScope = serde_json::from_value(obj.into())?;
122127
return match code_w_scope.scope {

src/extjson/models.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use serde::{
66
Deserialize,
77
};
88

9-
use crate::{extjson, oid, Bson};
9+
use crate::{extjson, oid, spec::BinarySubtype, Bson};
1010

1111
#[derive(Deserialize)]
1212
#[serde(deny_unknown_fields)]
@@ -165,6 +165,50 @@ impl Binary {
165165
}
166166
}
167167

168+
#[derive(Deserialize)]
169+
#[serde(deny_unknown_fields)]
170+
pub(crate) struct Uuid {
171+
#[serde(rename = "$uuid")]
172+
value: String,
173+
}
174+
175+
impl Uuid {
176+
pub(crate) fn parse(mut self) -> extjson::de::Result<crate::Binary> {
177+
if !valid_uuid_format(&self.value) {
178+
return Err(extjson::de::Error::invalid_value(
179+
Unexpected::Str(&self.value),
180+
&"$uuid values does not follow RFC 4122 format regarding length and hyphens",
181+
));
182+
}
183+
184+
self.value.retain(|c| c != '-');
185+
186+
let bytes = hex::decode(&self.value).map_err(|_| {
187+
extjson::de::Error::invalid_value(
188+
Unexpected::Str(&self.value),
189+
&"$uuid does not follow RFC 4122 format regarding hex bytes",
190+
)
191+
})?;
192+
193+
Ok(crate::Binary {
194+
subtype: BinarySubtype::Uuid,
195+
bytes,
196+
})
197+
}
198+
}
199+
200+
fn valid_uuid_format(s: &str) -> bool {
201+
// RFC 4122 defines the hyphens in a UUID as appearing in the 8th, 13th, 18th, and 23rd
202+
// characters.
203+
//
204+
// See https://tools.ietf.org/html/rfc4122#section-3
205+
s.chars().count() == 36
206+
&& s.chars().nth(8) == Some('-')
207+
&& s.chars().nth(13) == Some('-')
208+
&& s.chars().nth(18) == Some('-')
209+
&& s.chars().nth(23) == Some('-')
210+
}
211+
168212
#[derive(Deserialize)]
169213
#[serde(deny_unknown_fields)]
170214
pub(crate) struct JavaScriptCodeWithScope {

src/tests/spec/json/bson-corpus/binary.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@
3939
"canonical_bson": "1D000000057800100000000473FFD26444B34C6990E8E7D1DFC035D400",
4040
"canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"c//SZESzTGmQ6OfR38A11A==\", \"subType\" : \"04\"}}}"
4141
},
42+
{
43+
"description": "subtype 0x04 UUID",
44+
"canonical_bson": "1D000000057800100000000473FFD26444B34C6990E8E7D1DFC035D400",
45+
"canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"c//SZESzTGmQ6OfR38A11A==\", \"subType\" : \"04\"}}}",
46+
"degenerate_extjson": "{\"x\" : { \"$uuid\" : \"73ffd264-44b3-4c69-90e8-e7d1dfc035d4\"}}"
47+
},
4248
{
4349
"description": "subtype 0x05",
4450
"canonical_bson": "1D000000057800100000000573FFD26444B34C6990E8E7D1DFC035D400",
@@ -81,5 +87,15 @@
8187
"description": "subtype 0x02 length negative one",
8288
"bson": "130000000578000600000002FFFFFFFFFFFF00"
8389
}
90+
],
91+
"parseErrors": [
92+
{
93+
"description": "$uuid wrong type",
94+
"string": "{\"x\" : { \"$uuid\" : { \"data\" : \"73ffd264-44b3-4c69-90e8-e7d1dfc035d4\"}}}"
95+
},
96+
{
97+
"description": "$uuid invalid value",
98+
"string": "{\"x\" : { \"$uuid\" : \"73ffd264-44b3-90e8-e7d1dfc035d4\"}}"
99+
}
84100
]
85101
}

src/tests/spec/json/bson-corpus/timestamp.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
"description": "Timestamp with high-order bit set on both seconds and increment",
1919
"canonical_bson": "10000000116100FFFFFFFFFFFFFFFF00",
2020
"canonical_extjson": "{\"a\" : {\"$timestamp\" : {\"t\" : 4294967295, \"i\" : 4294967295} } }"
21+
},
22+
{
23+
"description": "Timestamp with high-order bit set on both seconds and increment (not UINT32_MAX)",
24+
"canonical_bson": "1000000011610000286BEE00286BEE00",
25+
"canonical_extjson": "{\"a\" : {\"$timestamp\" : {\"t\" : 4000000000, \"i\" : 4000000000} } }"
2126
}
2227
],
2328
"decodeErrors": [

0 commit comments

Comments
 (0)