Hello,
I must convert an optional string slice into a MongoDB ObjectId or a UUID. The conversion is trivial, but I am unsure if I have the best implementation below. Basically, I have a make_id() function that validates and selects how to provide the id.
The first solution, below, uses two closures, which either parse the id or generate one as an ObjectId or a Uuid.
The second solution, below, uses generics.
Although both solutions work, I am curious whether one is more safe or has advantages over the other. I'd appreciate your comments.
Thanks for your interest and time.
Mike
CLOSURE VERSION
[derive(Debug)]
pub enum DataId {
InMemory(Uuid),
MongoDb(ObjectId),
}
impl DataId {}
#[tokio::main]
async fn main() {
let uuid = "142c458f-2728-4961-b4c5-6514c7491691";
let oid = "6507fdd16c27fffd69308982";
// Get a UUID from a string slice.
let result_uuid = make_id(Some(uuid), parse_uuid, generate_uuid).unwrap();
println!("Parsed UUID '{}' to '{:?}'", uuid, result_uuid);
// Get an ObjectId from a string slice.
let result_objectid = make_id(Some(oid), parse_objectid, generate_objectid).unwrap();
println!("Parsed ObjectId '{}' to '{:?}'", oid, result_objectid);
}
// Create an instance of a data storage id specific for an inmemory store or a MongoDb database.
// id: The value of a unique id for a project in storage as a string slice.
// parser: A closure that parses the UUID or ObjectId.
// generator: A closure that generates a new UUID or ObjectId.
// returns: the data store-specific id.
fn make_id(
id: Option<&str>,
parser: fn(id: &str) -> Result<DataId, EntityError>,
generator: fn() -> DataId,
) -> Result<DataId, EntityError> {
return if let Some(id) = id {
match parser(id) {
Ok(did) => Ok(did),
Err(e) => Err(e),
}
} else {
// No id provided, so we will make one.
Ok(generator())
};
}
fn parse_uuid(id: &str) -> Result<DataId, EntityError> {
match Uuid::parse_str(id) {
Ok(uid) => Ok(DataId::InMemory(uid)),
Err(e) => Err(EntityError::Application {
message: "failed to parse the uuid".to_string(),
err: Some(BoxError::try_from(e).unwrap()),
}),
}
}
fn parse_objectid(id: &str) -> Result<DataId, EntityError> {
match ObjectId::parse_str(id) {
Ok(oid) => Ok(DataId::MongoDb(oid)),
Err(e) => Err(EntityError::Application {
message: "failed to parse the object id".to_string(),
err: Some(BoxError::try_from(e).unwrap()),
}),
}
}
fn generate_uuid() -> DataId {
DataId::InMemory(Uuid::new_v4())
}
fn generate_objectid() -> DataId {
DataId::MongoDb(ObjectId::new())
}
GENERIC VERSION
// Represents the unique id for an entity in a data store.
// where T is either UuidType or ObjectIdType.
// Defines two methods that must be implemented:
// generator() to create a new Uuid or ObjectId.
// parser() to parse a string slice id and produce either a Uuid or ObjectId.
trait DataIdType<T> {
fn generator() -> T;
fn parser(id: &str) -> Result<T, EntityError>;
}
// UuidType: Define and implement DataIdType<Uuid>.
#[derive(Debug)]
struct UuidType {}
impl DataIdType<Uuid> for UuidType {
fn generator() -> Uuid {
Uuid::new_v4()
}
fn parser(id: &str) -> Result<Uuid, EntityError> {
match Uuid::parse_str(id) {
Ok(uid) => Ok(uid),
Err(e) => Err(EntityError::Application {
message: "failed to parse the uuid".to_string(),
err: Some(BoxError::try_from(e).unwrap()),
}),
}
}
}
// ObjectIdType: Define and implement DataIdType<ObjectId>.
#[derive(Debug)]
struct ObjectIdType {}
impl DataIdType<ObjectId> for ObjectIdType {
fn generator() -> ObjectId {
ObjectId::new()
}
fn parser(id: &str) -> Result<ObjectId, EntityError> {
match ObjectId::parse_str(id) {
Ok(oid) => Ok(oid),
Err(e) => Err(EntityError::Application {
message: "failed to parse the objectid".to_string(),
err: Some(BoxError::try_from(e).unwrap()),
}),
}
}
}
// Create a data storage id as either a Uuid or ObjectId.
// where T is either UuidType or ObjectIdType; and
// V is either Uuid or ObjectId
// id: The unique, string-slice value id for an entity in storage.
// returns: the data store specific id.
fn make_id<T,V>(id: Option<&str>) -> Result<V, EntityError> where T:DataIdType<V> {
return if let Some(id) = id {
match T::parser(id) {
Ok(did) => Ok(did),
Err(e) => Err(e),
}
} else {
// No id provided, so we will make one.
Ok(T::generator())
};
}
fn main() {
let uuid = "142c458f-2728-4961-b4c5-6514c7491691";
let objectid = "6507fdd16c27fffd69308982";
let parsed_uuid = make_id::<UuidType, Uuid>(Some(uuid)).unwrap();
let parsed_objectid = make_id::<ObjectIdType, ObjectId>(Some(objectid)).unwrap();
println!("Parsed UUID '{}' to '{:?}'", uuid, parsed_uuid);
println!("Parsed ObjectId '{}' to '{:?}'", objectid, parsed_objectid);
let generated_uuid = make_id::<UuidType, Uuid>(None).unwrap();
let generated_objectid = make_id::<ObjectIdType, ObjectId>(None).unwrap();
println!("Generated UUID '{:?}'", generated_uuid);
println!("Generated ObjectId '{}'", generated_objectid);
}