Dans la précédente partie nous avons été capable de sérialiser une structure User.
Dans cette partie, nous allons voir la création d'une API de plus haut-niveau pour les manipuler.
Database
Sérialiser plusieurs User
Donc on arrive au point où l'on est capable de sérialiser un User et de le récupérer après coup.
Mais est ce que l'on peut en sérialiser 2 ou plus ?
Et bien essayons !
#[test]fntest_serde_users(){letmut buffer =[0_u8;1024];let user = User { id:42, username:"user".to_string(), email:"email".to_string(),};let devil = User { id:666, username:"Lucifer".to_string(), email:"MorningStar".to_string(),};letmut writer =Cursor::new(&mut buffer[..]); user.serialize(&mut writer).expect("Unable to serialize user"); devil
.serialize2(&mut writer).expect("Unable to serialize user");letmut reader =Cursor::new(&buffer[..]);let result =User::deserialize(&mut reader).expect("Unable to deserialize user");assert_eq!(user, result);let result =User::deserialize(&mut reader).expect("Unable to deserialize user");assert_eq!(devil, result);}
Apparemment oui !
Et maintenant, si on veut "scanner" notre DB, on fait comment?
Scanner, signifie passer en revue chaque enregistrement et à les désérialiser successivement.
#[test]fntest_scan_db(){letmut buffer =[0_u8;1024*1024];letmut cursor =Cursor::new(&mut buffer[..]);// enregistrement
for i in0..50{let user =User::new(i,format!("test_{i}"),format!("email_{i}@example.com")); user.serialize(&mut cursor).expect("Unable to serialize user");}// scan
letmut reader =Cursor::new(&buffer[..]);for i in0..50{let user =User::new(i,format!("test_{i}"),format!("email_{i}@example.com"));let result =User::deserialize(&mut reader).expect("Unable to deserialize user");assert_eq!(user, result);}}
Tout fonctionne du feu de Dieu!
Insertion et sélection
Devenons un peu plus "réaliste", au lieu d'utiliser le même curseur pour sérialiser tout le monde, nous allon conserver l'offset et décaler notre buffer d'autant entre chaque enregistrement.
On capture aussi le nombre d'élements inséré. Cela nous permet de savoir quand arrêter de scanner.
#[test]fntest_insert_select(){letmut buffer =[0_u8;1024*1024];// offset d'écriture
letmut offset =0_usize;// nombre d'enregistrements
letmut nb_inserts =0;// insertion des User
for i in0..50{// chaque insert possède son propre curseur d'écriture
letmut cursor =Cursor::new(&mut buffer[offset..]);let user =User::new(i,format!("test_{i}"),format!("email_{i}@example.com")); user.serialize2(&mut cursor).expect("Unable to serialize user");// on se décale d'autant que la donnée écrite
offset += cursor.position()asusize; nb_inserts +=1;}// scan des User
// on créé un reader unique pour le scan
letmut reader =Cursor::new(&buffer[..]);for i in0..nb_inserts {let user =User::new(i,format!("test_{i}"),format!("email_{i}@example.com"));let result =User::deserialize2(&mut reader).expect("Unable to deserialize user");assert_eq!(user, result);}}
API publique
Bon on est proche du but. Plus qu'à envelopper tout cela dans un papier cadeau.
constDATABASE_SIZE:usize=1024*1024;pubstructDatabase{// on passe en alloué car la taille a tendance à exploser la stack => stackoverflow
inner:Vec<u8>,
offset:usize,
row_number:usize,
}implDatabase{pubfnnew()->Self{Self{ inner:vec![0;DATABASE_SIZE], offset:0, row_number:0,}}}
J'ai renommé le 'nb_inserts' en 'row_number' car c'est sémantiquement plus proche de ce que ça représente réellement. Tout est un peu artificiel pour le moment.
Plus on progressera vers la réelle implémentation plus on enlèvera ces placeholders.
Dans MetaCommand on ne l'utilise pas donc c'est rapide.
Par contre dans SqlCommand, on a un peu plus de boulot.
implExecute forSqlCommand{fnexecute(self, database:&mut Database)->Result<(), ExecutionError>{matchself{SqlCommand::Insert { id, username, email,}=>{let user =User::new(id, username, email); database.insert(user).map_err(ExecutionError::Insertion)?;println!("User inserted successfully");}SqlCommand::Select =>{for user in database.select::<User>().map_err(ExecutionError::Select)?{println!("{:?}", user);}}}Ok(())}}
Mais comme vous le voyez, en ayant diviser les couches d'abstractions de manière très contrôlée et cohérente, notre interface public devient extrêmement agréable à utiliser. 😄
Finalement on modifie Command pour propager la Database.