const OP_MSG: i32 = 2013;
const OP_COMPRESSED: i32 = 2012;
pub fn mongo_msg_len(buf: &[u8]) -> Option<usize> {
if buf.len() < 4 { return None; }
let len = i32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]) as usize;
if !(16..=48 * 1024 * 1024).contains(&len) { return None; }
Some(len)
}
pub fn parse_mongo_request(buf: &[u8]) -> Option<String> {
let doc = extract_body_doc(buf)?;
let cmd = first_key(&doc)?;
let db = get_string_field(&doc, "$db").unwrap_or_default();
let detail = match cmd.as_str() {
"find" => {
let coll = get_string_field(&doc, "find").unwrap_or_default();
let filter = get_doc_field_summary(&doc, "filter");
format!("find {}.{} {}", db, coll, filter)
}
"insert" => {
let coll = get_string_field(&doc, "insert").unwrap_or_default();
let n = get_array_len(&doc, "documents");
format!("insert {}.{} ({} docs)", db, coll, n)
}
"update" => {
let coll = get_string_field(&doc, "update").unwrap_or_default();
let n = get_array_len(&doc, "updates");
format!("update {}.{} ({} ops)", db, coll, n)
}
"delete" => {
let coll = get_string_field(&doc, "delete").unwrap_or_default();
let n = get_array_len(&doc, "deletes");
format!("delete {}.{} ({} ops)", db, coll, n)
}
"aggregate" => {
let coll = get_string_field(&doc, "aggregate").unwrap_or_default();
format!("aggregate {}.{}", db, coll)
}
"getMore" => {
let coll = get_string_field(&doc, "collection").unwrap_or_default();
format!("getMore {}.{}", db, coll)
}
_ => {
if db.is_empty() { cmd.clone() } else { format!("{} {}", cmd, db) }
}
};
Some(detail)
}
pub fn extract_mongo_full_command(buf: &[u8]) -> Option<String> {
let doc = extract_body_doc(buf)?;
let cmd = first_key(&doc)?;
let db = get_string_field(&doc, "$db").unwrap_or_default();
match cmd.as_str() {
"find" => {
let coll = get_string_field(&doc, "find").unwrap_or_default();
let filter = get_doc_field_summary(&doc, "filter");
let limit = get_i32_field(&doc, "limit");
let sort = get_raw_doc_field(&doc, "sort").map(|d| bson_doc_to_json_like(&d));
let mut s = format!("db.{}.find({})", coll, filter);
if let Some(sort_str) = sort { s.push_str(&format!(".sort({})", sort_str)); }
if let Some(l) = limit { s.push_str(&format!(".limit({})", l)); }
Some(s)
}
"insert" => {
let coll = get_string_field(&doc, "insert").unwrap_or_default();
let docs = get_array_docs(&doc, "documents");
if docs.len() == 1 {
Some(format!("db.{}.insertOne({})", coll, bson_doc_to_json_like(&docs[0])))
} else {
let items: Vec<String> = docs.iter().take(10).map(|d| bson_doc_to_json_like(d)).collect();
let mut s = format!("db.{}.insertMany([{}])", coll, items.join(", "));
if docs.len() > 10 { s.push_str(&format!(" // +{} more", docs.len() - 10)); }
Some(s)
}
}
"update" => {
let coll = get_string_field(&doc, "update").unwrap_or_default();
let updates = get_array_docs(&doc, "updates");
if updates.len() == 1 {
let q = get_doc_field_summary(&updates[0], "q");
let u = get_doc_field_summary(&updates[0], "u");
let multi = get_i32_field(&updates[0], "multi").unwrap_or(0) != 0
|| has_field(&updates[0], "multi") && get_f64_field(&updates[0], "multi") == Some(1.0);
let method = if multi { "updateMany" } else { "updateOne" };
Some(format!("db.{}.{}({}, {})", coll, method, q, u))
} else {
Some(format!("db.{}.bulkWrite([...{} ops])", coll, updates.len()))
}
}
"delete" => {
let coll = get_string_field(&doc, "delete").unwrap_or_default();
let deletes = get_array_docs(&doc, "deletes");
if deletes.len() == 1 {
let q = get_doc_field_summary(&deletes[0], "q");
let limit = get_i32_field(&deletes[0], "limit").unwrap_or(0);
let method = if limit == 1 { "deleteOne" } else { "deleteMany" };
Some(format!("db.{}.{}({})", coll, method, q))
} else {
Some(format!("db.{}.bulkWrite([...{} ops])", coll, deletes.len()))
}
}
"aggregate" => {
let coll = get_string_field(&doc, "aggregate").unwrap_or_default();
Some(format!("db.{}.aggregate([...])", coll))
}
"findAndModify" => {
let coll = get_string_field(&doc, "findAndModify").unwrap_or_default();
let query = get_doc_field_summary(&doc, "query");
let update = get_doc_field_summary(&doc, "update");
Some(format!("db.{}.findOneAndUpdate({}, {})", coll, query, update))
}
"count" | "countDocuments" => {
let coll = get_string_field(&doc, &cmd).unwrap_or_default();
let query = get_doc_field_summary(&doc, "query");
Some(format!("db.{}.countDocuments({})", coll, query))
}
_ => {
if db.is_empty() { Some(cmd) } else { Some(format!("{} {}", cmd, db)) }
}
}
}
pub fn parse_mongo_response(buf: &[u8]) -> Option<String> {
let doc = extract_body_doc(buf)?;
let ok = get_f64_field(&doc, "ok");
if ok == Some(0.0) {
let errmsg = get_string_field(&doc, "errmsg").unwrap_or("error".into());
let code = get_i32_field(&doc, "code").map(|c| format!(" ({})", c)).unwrap_or_default();
return Some(format!("ERR{} {}", code, errmsg));
}
if let Some(cursor_doc) = get_raw_doc_field(&doc, "cursor") {
let batch_key = if has_field(&cursor_doc, "firstBatch") { "firstBatch" } else { "nextBatch" };
let n = get_array_len(&cursor_doc, batch_key);
return Some(format!("OK ({} docs)", n));
}
if let Some(n) = get_i32_field(&doc, "n") {
let modified = get_i32_field(&doc, "nModified");
if let Some(m) = modified {
return Some(format!("OK (n={}, modified={})", n, m));
}
return Some(format!("OK (n={})", n));
}
Some("OK".into())
}
pub fn format_mongo_response_detail(buf: &[u8]) -> Option<String> {
let doc = extract_body_doc(buf)?;
let ok = get_f64_field(&doc, "ok");
if ok == Some(0.0) {
let errmsg = get_string_field(&doc, "errmsg").unwrap_or("error".into());
let code = get_i32_field(&doc, "code").unwrap_or(0);
let codename = get_string_field(&doc, "codeName").unwrap_or_default();
return Some(format!("ERROR {} ({}): {}", code, codename, errmsg));
}
if let Some(cursor_doc) = get_raw_doc_field(&doc, "cursor") {
let batch_key = if has_field(&cursor_doc, "firstBatch") { "firstBatch" } else { "nextBatch" };
let docs = get_array_docs(&cursor_doc, batch_key);
let mut lines = Vec::new();
lines.push(format!("{} documents:", docs.len()));
for (i, d) in docs.iter().enumerate().take(20) {
lines.push(format!(" [{}] {}", i, bson_doc_to_json_like(d)));
}
if docs.len() > 20 {
lines.push(format!(" ... ({} more)", docs.len() - 20));
}
return Some(lines.join("\n"));
}
parse_mongo_response(buf)
}
fn extract_body_doc(buf: &[u8]) -> Option<Vec<u8>> {
if buf.len() < 21 { return None; } let opcode = i32::from_le_bytes([buf[12], buf[13], buf[14], buf[15]]);
if opcode != OP_MSG && opcode != OP_COMPRESSED { return None; }
if opcode == OP_COMPRESSED { return decompress_op_compressed(buf); }
let mut pos = 20;
while pos < buf.len() {
let kind = buf[pos];
pos += 1;
if kind == 0 {
if pos + 4 > buf.len() { return None; }
let doc_len = i32::from_le_bytes([buf[pos], buf[pos+1], buf[pos+2], buf[pos+3]]) as usize;
if pos + doc_len > buf.len() { return None; }
return Some(buf[pos..pos+doc_len].to_vec());
} else if kind == 1 {
if pos + 4 > buf.len() { return None; }
let sec_len = i32::from_le_bytes([buf[pos], buf[pos+1], buf[pos+2], buf[pos+3]]) as usize;
pos += sec_len;
} else {
break;
}
}
None
}
fn decompress_op_compressed(buf: &[u8]) -> Option<Vec<u8>> {
if buf.len() < 25 { return None; }
let original_opcode = i32::from_le_bytes([buf[16], buf[17], buf[18], buf[19]]);
if original_opcode != OP_MSG { return None; }
let uncompressed_size = i32::from_le_bytes([buf[20], buf[21], buf[22], buf[23]]) as usize;
let compressor_id = buf[24];
let compressed = &buf[25..];
let decompressed = match compressor_id {
0 => compressed.to_vec(),
1 => snap::raw::Decoder::new().decompress_vec(compressed).ok()?,
2 => {
use std::io::Read;
let mut decoder = flate2::read::ZlibDecoder::new(compressed);
let mut out = Vec::with_capacity(uncompressed_size);
decoder.read_to_end(&mut out).ok()?;
out
}
3 => zstd::decode_all(compressed).ok()?,
_ => return None,
};
if decompressed.len() < 5 { return None; }
let mut pos = 4; while pos < decompressed.len() {
let kind = decompressed[pos];
pos += 1;
if kind == 0 {
if pos + 4 > decompressed.len() { return None; }
let doc_len = i32::from_le_bytes([decompressed[pos], decompressed[pos+1], decompressed[pos+2], decompressed[pos+3]]) as usize;
if pos + doc_len > decompressed.len() { return None; }
return Some(decompressed[pos..pos+doc_len].to_vec());
} else if kind == 1 {
if pos + 4 > decompressed.len() { return None; }
let sec_len = i32::from_le_bytes([decompressed[pos], decompressed[pos+1], decompressed[pos+2], decompressed[pos+3]]) as usize;
pos += sec_len;
} else {
break;
}
}
None
}
fn first_key(doc: &[u8]) -> Option<String> {
if doc.len() < 6 { return None; }
let key = read_cstr(&doc[5..])?;
Some(key)
}
fn read_cstr(buf: &[u8]) -> Option<String> {
let end = buf.iter().position(|&b| b == 0)?;
Some(String::from_utf8_lossy(&buf[..end]).to_string())
}
fn get_string_field(doc: &[u8], name: &str) -> Option<String> {
let mut pos = 4; while pos < doc.len() - 1 {
let etype = doc[pos];
if etype == 0 { break; } pos += 1;
let key = read_cstr(&doc[pos..])?;
pos += key.len() + 1;
match etype {
0x02 => { if pos + 4 > doc.len() { return None; }
let slen = i32::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3]]) as usize;
pos += 4;
if key == name {
let s = String::from_utf8_lossy(&doc[pos..pos+slen.saturating_sub(1)]).to_string();
return Some(s);
}
pos += slen;
}
0x01 => { pos += 8; } 0x03 | 0x04 => { if pos + 4 > doc.len() { return None; }
let dlen = i32::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3]]) as usize;
pos += dlen;
}
0x05 => { if pos + 4 > doc.len() { return None; }
let blen = i32::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3]]) as usize;
pos += 5 + blen;
}
0x07 => { pos += 12; } 0x08 => { pos += 1; } 0x09 | 0x11 | 0x12 => { pos += 8; } 0x0A => {} 0x10 => { pos += 4; } 0x13 => { pos += 16; } _ => { return None; } }
}
None
}
fn get_f64_field(doc: &[u8], name: &str) -> Option<f64> {
let mut pos = 4;
while pos < doc.len() - 1 {
let etype = doc[pos];
if etype == 0 { break; }
pos += 1;
let key = read_cstr(&doc[pos..])?;
pos += key.len() + 1;
match etype {
0x01 => {
if key == name && pos + 8 <= doc.len() {
return Some(f64::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3], doc[pos+4], doc[pos+5], doc[pos+6], doc[pos+7]]));
}
pos += 8;
}
0x10 => {
if key == name && pos + 4 <= doc.len() {
let v = i32::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3]]);
return Some(v as f64);
}
pos += 4;
}
0x02 => { if pos + 4 > doc.len() { return None; } let slen = i32::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3]]) as usize; pos += 4 + slen; }
0x03 | 0x04 => { if pos + 4 > doc.len() { return None; } let dlen = i32::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3]]) as usize; pos += dlen; }
0x05 => { if pos + 4 > doc.len() { return None; } let blen = i32::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3]]) as usize; pos += 5 + blen; }
0x07 => { pos += 12; }
0x08 => { pos += 1; }
0x09 | 0x11 | 0x12 => { pos += 8; }
0x0A => {}
0x13 => { pos += 16; }
_ => { return None; }
}
}
None
}
fn get_i32_field(doc: &[u8], name: &str) -> Option<i32> {
let mut pos = 4;
while pos < doc.len() - 1 {
let etype = doc[pos];
if etype == 0 { break; }
pos += 1;
let key = read_cstr(&doc[pos..])?;
pos += key.len() + 1;
match etype {
0x10 => {
if key == name && pos + 4 <= doc.len() {
return Some(i32::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3]]));
}
pos += 4;
}
0x01 => { pos += 8; }
0x02 => { if pos + 4 > doc.len() { return None; } let slen = i32::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3]]) as usize; pos += 4 + slen; }
0x03 | 0x04 => { if pos + 4 > doc.len() { return None; } let dlen = i32::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3]]) as usize; pos += dlen; }
0x05 => { if pos + 4 > doc.len() { return None; } let blen = i32::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3]]) as usize; pos += 5 + blen; }
0x07 => { pos += 12; }
0x08 => { pos += 1; }
0x09 | 0x11 | 0x12 => { pos += 8; }
0x0A => {}
0x13 => { pos += 16; }
_ => { return None; }
}
}
None
}
fn get_raw_doc_field(doc: &[u8], name: &str) -> Option<Vec<u8>> {
let mut pos = 4;
while pos < doc.len() - 1 {
let etype = doc[pos];
if etype == 0 { break; }
pos += 1;
let key = read_cstr(&doc[pos..])?;
pos += key.len() + 1;
match etype {
0x03 | 0x04 => {
if pos + 4 > doc.len() { return None; }
let dlen = i32::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3]]) as usize;
if key == name {
return Some(doc[pos..pos+dlen].to_vec());
}
pos += dlen;
}
0x01 => { pos += 8; }
0x02 => { if pos + 4 > doc.len() { return None; } let slen = i32::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3]]) as usize; pos += 4 + slen; }
0x05 => { if pos + 4 > doc.len() { return None; } let blen = i32::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3]]) as usize; pos += 5 + blen; }
0x07 => { pos += 12; }
0x08 => { pos += 1; }
0x09 | 0x10 | 0x11 | 0x12 => { pos += if etype == 0x10 { 4 } else { 8 }; }
0x0A => {}
0x13 => { pos += 16; }
_ => { return None; }
}
}
None
}
fn has_field(doc: &[u8], name: &str) -> bool {
let mut pos = 4;
while pos < doc.len().saturating_sub(1) {
let etype = doc[pos];
if etype == 0 { break; }
pos += 1;
let Some(key) = read_cstr(&doc[pos..]) else { break };
if key == name { return true; }
pos += key.len() + 1;
match etype {
0x01 => { pos += 8; }
0x02 => { if pos + 4 > doc.len() { break; } let slen = i32::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3]]) as usize; pos += 4 + slen; }
0x03 | 0x04 => { if pos + 4 > doc.len() { break; } let dlen = i32::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3]]) as usize; pos += dlen; }
0x05 => { if pos + 4 > doc.len() { break; } let blen = i32::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3]]) as usize; pos += 5 + blen; }
0x07 => { pos += 12; }
0x08 => { pos += 1; }
0x09 | 0x11 | 0x12 => { pos += 8; }
0x0A => {}
0x10 => { pos += 4; }
0x13 => { pos += 16; }
_ => { break; }
}
}
false
}
fn get_array_len(doc: &[u8], name: &str) -> usize {
let Some(arr) = get_raw_doc_field(doc, name) else { return 0 };
let mut count = 0;
let mut pos = 4;
while pos < arr.len().saturating_sub(1) {
if arr[pos] == 0 { break; }
count += 1;
pos += 1;
let Some(key) = read_cstr(&arr[pos..]) else { break };
pos += key.len() + 1;
let etype = arr[pos - key.len() - 2];
match etype {
0x01 => { pos += 8; }
0x02 => { if pos + 4 > arr.len() { break; } let slen = i32::from_le_bytes([arr[pos], arr[pos+1], arr[pos+2], arr[pos+3]]) as usize; pos += 4 + slen; }
0x03 | 0x04 => { if pos + 4 > arr.len() { break; } let dlen = i32::from_le_bytes([arr[pos], arr[pos+1], arr[pos+2], arr[pos+3]]) as usize; pos += dlen; }
0x05 => { if pos + 4 > arr.len() { break; } let blen = i32::from_le_bytes([arr[pos], arr[pos+1], arr[pos+2], arr[pos+3]]) as usize; pos += 5 + blen; }
0x07 => { pos += 12; }
0x08 => { pos += 1; }
0x09 | 0x11 | 0x12 => { pos += 8; }
0x0A => {}
0x10 => { pos += 4; }
0x13 => { pos += 16; }
_ => { break; }
}
}
count
}
fn get_array_docs(doc: &[u8], name: &str) -> Vec<Vec<u8>> {
let Some(arr) = get_raw_doc_field(doc, name) else { return vec![] };
let mut docs = Vec::new();
let mut pos = 4;
while pos < arr.len().saturating_sub(1) {
let etype = arr[pos];
if etype == 0 { break; }
pos += 1;
let Some(key) = read_cstr(&arr[pos..]) else { break };
pos += key.len() + 1;
if etype == 0x03 {
if pos + 4 > arr.len() { break; }
let dlen = i32::from_le_bytes([arr[pos], arr[pos+1], arr[pos+2], arr[pos+3]]) as usize;
if pos + dlen <= arr.len() {
docs.push(arr[pos..pos+dlen].to_vec());
}
pos += dlen;
} else {
break; }
}
docs
}
fn get_doc_field_summary(doc: &[u8], name: &str) -> String {
let Some(subdoc) = get_raw_doc_field(doc, name) else { return "{}".into() };
bson_doc_to_json_like(&subdoc)
}
fn bson_doc_to_json_like(doc: &[u8]) -> String {
let mut parts = Vec::new();
let mut pos = 4;
while pos < doc.len().saturating_sub(1) {
let etype = doc[pos];
if etype == 0 { break; }
pos += 1;
let Some(key) = read_cstr(&doc[pos..]) else { break };
pos += key.len() + 1;
let val = match etype {
0x01 => { let v = if pos + 8 <= doc.len() { f64::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3], doc[pos+4], doc[pos+5], doc[pos+6], doc[pos+7]]) } else { 0.0 }; pos += 8; format!("{}", v) }
0x02 => { if pos + 4 > doc.len() { break; } let slen = i32::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3]]) as usize; pos += 4; let s = String::from_utf8_lossy(&doc[pos..pos+slen.saturating_sub(1)]).to_string(); pos += slen; format!("\"{}\"", s) }
0x03 => { if pos + 4 > doc.len() { break; } let dlen = i32::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3]]) as usize; let s = bson_doc_to_json_like(&doc[pos..pos+dlen]); pos += dlen; s }
0x04 => { if pos + 4 > doc.len() { break; } let dlen = i32::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3]]) as usize; pos += dlen; "[...]".into() }
0x07 => { pos += 12; "ObjectId(...)".into() }
0x08 => { let v = doc[pos] != 0; pos += 1; format!("{}", v) }
0x09 => { pos += 8; "Date(...)".into() }
0x0A => { "null".into() }
0x10 => { let v = if pos + 4 <= doc.len() { i32::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3]]) } else { 0 }; pos += 4; format!("{}", v) }
0x12 => { let v = if pos + 8 <= doc.len() { i64::from_le_bytes([doc[pos], doc[pos+1], doc[pos+2], doc[pos+3], doc[pos+4], doc[pos+5], doc[pos+6], doc[pos+7]]) } else { 0 }; pos += 8; format!("{}", v) }
_ => { break; }
};
if key == "_id" || key == "lsid" { continue; }
parts.push(format!("{}: {}", key, val));
if parts.len() >= 8 { parts.push("...".into()); break; }
}
format!("{{{}}}", parts.join(", "))
}
#[cfg(test)]
mod tests {
use super::*;
fn build_op_msg(doc: &[u8]) -> Vec<u8> {
let msg_len = 16 + 4 + 1 + doc.len(); let mut buf = Vec::new();
buf.extend_from_slice(&(msg_len as i32).to_le_bytes()); buf.extend_from_slice(&1i32.to_le_bytes()); buf.extend_from_slice(&0i32.to_le_bytes()); buf.extend_from_slice(&OP_MSG.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.push(0); buf.extend_from_slice(doc);
buf
}
fn build_simple_cmd(cmd: &str, coll: &str) -> Vec<u8> {
let mut doc = Vec::new();
doc.extend_from_slice(&[0; 4]); doc.push(0x02); doc.extend_from_slice(cmd.as_bytes());
doc.push(0);
let val = format!("{}\0", coll);
doc.extend_from_slice(&(val.len() as i32).to_le_bytes());
doc.extend_from_slice(val.as_bytes());
doc.push(0x02);
doc.extend_from_slice(b"$db\0");
let db = "testdb\0";
doc.extend_from_slice(&(db.len() as i32).to_le_bytes());
doc.extend_from_slice(db.as_bytes());
doc.push(0);
let len = doc.len() as i32;
doc[0..4].copy_from_slice(&len.to_le_bytes());
doc
}
#[test]
fn test_parse_find_request() {
let doc = build_simple_cmd("find", "users");
let buf = build_op_msg(&doc);
let result = parse_mongo_request(&buf).unwrap();
assert!(result.contains("find"));
assert!(result.contains("testdb"));
assert!(result.contains("users"));
}
#[test]
fn test_parse_insert_request() {
let doc = build_simple_cmd("insert", "users");
let buf = build_op_msg(&doc);
let result = parse_mongo_request(&buf).unwrap();
assert!(result.contains("insert"));
assert!(result.contains("testdb.users"));
}
#[test]
fn test_parse_response_ok() {
let mut doc = Vec::new();
doc.extend_from_slice(&[0; 4]);
doc.push(0x01); doc.extend_from_slice(b"ok\0");
doc.extend_from_slice(&1.0f64.to_le_bytes());
doc.push(0);
let len = doc.len() as i32;
doc[0..4].copy_from_slice(&len.to_le_bytes());
let buf = build_op_msg(&doc);
let result = parse_mongo_response(&buf).unwrap();
assert_eq!(result, "OK");
}
#[test]
fn test_parse_response_error() {
let mut doc = Vec::new();
doc.extend_from_slice(&[0; 4]);
doc.push(0x01);
doc.extend_from_slice(b"ok\0");
doc.extend_from_slice(&0.0f64.to_le_bytes());
doc.push(0x02);
doc.extend_from_slice(b"errmsg\0");
let msg = "not found\0";
doc.extend_from_slice(&(msg.len() as i32).to_le_bytes());
doc.extend_from_slice(msg.as_bytes());
doc.push(0x10);
doc.extend_from_slice(b"code\0");
doc.extend_from_slice(&26i32.to_le_bytes());
doc.push(0);
let len = doc.len() as i32;
doc[0..4].copy_from_slice(&len.to_le_bytes());
let buf = build_op_msg(&doc);
let result = parse_mongo_response(&buf).unwrap();
assert!(result.contains("ERR"));
assert!(result.contains("26"));
assert!(result.contains("not found"));
}
#[test]
fn test_mongo_msg_len() {
let buf = build_op_msg(&build_simple_cmd("ping", "admin"));
assert_eq!(mongo_msg_len(&buf), Some(buf.len()));
}
#[test]
fn test_mongo_msg_len_too_short() {
assert_eq!(mongo_msg_len(&[1, 2, 3]), None);
}
#[test]
fn test_extract_full_command_find() {
let doc = build_simple_cmd("find", "users");
let buf = build_op_msg(&doc);
let result = extract_mongo_full_command(&buf).unwrap();
assert!(result.contains("db.users.find"));
}
}