skip to Main Content

I need help using Rust’s u128 data type via MongoDB’s Rust driver. Please find my minimal example below:

use mongodb::{bson::{doc},
              options::{ClientOptions, ServerApi, ServerApiVersion},
              Client};

use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize, Serialize)]
struct Book {
    id    : u128,
    title : String,
    author: String,
}

#[tokio::main]
async fn main() -> mongodb::error::Result<()> {

    // create client options
    let uri = "mongodb://localhost:27017";
    let mut client_options = ClientOptions::parse(uri).await?;

    // set server API
    let server_api = ServerApi::builder().version(ServerApiVersion::V1).build();
    client_options.server_api = Some(server_api);
    client_options.app_name   = Some("Books".to_string());

    // create new client, connect to server
    let client = Client::with_options(client_options)?;

    // get handle to 'passports' database
    let db = client.database("books");

    // obtain handle to (strongly typed) collection in database
    let collection = db.collection::<Book>("books");

    let books = vec![
        Book {
            id:     u128::pow(2,64),
            title:  "The Grapes of Wrath".to_string(),
            author: "John Steinbeck".to_string(),
        },
        Book {
            id:     u128::pow(2,64)+1,
            title:  "To Kill a Mockingbird".to_string(),
            author: "Harper Lee".to_string(),
        },
    ];

    // insert books into (strongly typed) collection
    // @note No manual conversion to BSON is necessary
    collection.insert_many(books, None).await?;

    Ok(())
}

When I try to compile and run this code with cargo run it fails with the following error message:

   Compiling mongodb_dr v0.1.0 (/Users/username/repo/git/rustwerk/mongodb_dr)
    Finished dev [unoptimized + debuginfo] target(s) in 0.84s
     Running `target/debug/mongodb_dr`
Error: Error { kind: BsonSerialization(SerializationError { message: "u128 is not supported" }), labels: {}, wire_version: Some(17), source: None }

Any help would be much appreciated.

2

Answers


  1. Chosen as BEST ANSWER

    Another good alternative can be using Rust's UUID crate. It generates "unique 128-bit value, stored as 16 octets, and regularly formatted as a hex string in five groups", e.g.:

    67e55044-10b1-426f-9247-bb680e5fe0c8
    

    To use UUIDs instead of u128 please modify the above code:

    use mongodb::{bson::{doc},
                  options::{ClientOptions, ServerApi, ServerApiVersion},
                  Client};
    
    use serde::{Deserialize, Serialize};
    use uuid::Uuid;
    
    #[derive(Debug, Deserialize, Serialize)]
    struct Book {
        id    : Uuid,
        title : String,
        author: String,
    }
    
    #[tokio::main]
    async fn main() -> mongodb::error::Result<()> {
    
        // create client options
        let uri = "mongodb://localhost:27017";
        let mut client_options = ClientOptions::parse(uri).await?;
    
        // set server API
        let server_api = ServerApi::builder().version(ServerApiVersion::V1).build();
        client_options.server_api = Some(server_api);
        client_options.app_name   = Some("Books".to_string());
    
        // create new client, connect to server
        let client = Client::with_options(client_options)?;
    
        // get handle to 'passports' database
        let db = client.database("books");
    
        // obtain handle to (strongly typed) collection in database
        let collection = db.collection::<Book>("books");
    
        let books = vec![
            Book {
                id:     Uuid::new_v4(),
                title:  "The Grapes of Wrath".to_string(),
                author: "John Steinbeck".to_string(),
            },
            Book {
                id:     Uuid::new_v4(),
                title:  "To Kill a Mockingbird".to_string(),
                author: "Harper Lee".to_string(),
            },
        ];
    
        // insert books into (strongly typed) collection
        // @note No manual conversion to BSON is necessary
        collection.insert_many(books, None).await?;
    
        Ok(())
    }
    

    Do not forget to add a dependency for the UUID crate into your Cargo.toml file:

    [dependencies]
    uuid = { version = "1.4.1", features = ["v4", "fast-rng", "macro-diagnostics"] }
    

  2. There are no 128-bit integers in the BSON spec, so you need to choose an intermediate type to represent it in a BSON-compatible type.

    I’m not sure if MongoDB has any guidance for unserializable types, but just for BSON’s purposes, you can use the binary type, which is represented in the library as Binary and RawBinaryRef.

    You can use Serde’s with attribute to define custom serialization logic. It accepts a module with serialize and deserialize functions, which replace the default serialization functions.

    use serde::{Deserialize, Serialize};
    
    #[derive(Debug, Deserialize, Serialize)]
    struct Book {
        #[serde(with = "bson_u128")]
        id: u128,
        title: String,
        author: String,
    }
    
    mod bson_u128 {
        use serde::{Deserialize, Deserializer, Serialize, Serializer};
        use bson::spec::BinarySubtype;
    
        const U128_SUBTYPE: BinarySubtype = BinarySubtype::Generic;
    
        pub fn serialize<S>(t: &u128, s: S) -> Result<S::Ok, S::Error>
        where
            S: Serializer,
        {
            let bytes = t.to_be_bytes();
            bson::RawBinaryRef {
                subtype: U128_SUBTYPE,
                bytes: &bytes,
            }
            .serialize(s)
        }
    
        pub fn deserialize<'de, D>(d: D) -> Result<u128, D::Error>
        where
            D: Deserializer<'de>,
        {
            // `try_into` is converting `&[u8]` into `&[u8; 16]`
            bson::RawBinaryRef::deserialize(d).and_then(|rbr| match rbr.bytes.try_into() {
                Ok(&bytes) => {
                    if rbr.subtype == U128_SUBTYPE {
                        Ok(u128::from_be_bytes(bytes))
                    } else {
                        Err(serde::de::Error::custom("wrong binary subtype"))
                    }
                }
                Err(_) => Err(serde::de::Error::custom("incorrect number of bytes")),
            })
        }
    }
    

    I’ve used Generic as the binary subtype (U128_SUBTYPE in the code above), but you could use a custom one for more robustness.

    I didn’t want to serialize it as an array because it is not very space-efficient (see the note at the bottom of the spec). Other good formats would be two u64s, or a hexadecimal string.

    Related:

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search