From c24a4243b7fa5463c2499edfa57ac18aae237600 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 26 Nov 2024 18:55:55 +0000 Subject: [PATCH] Add more description of proto to the proto exercise --- src/lifetimes/exercise.md | 29 +++++++++++-- src/lifetimes/exercise.rs | 91 ++++++++++++++++++--------------------- 2 files changed, 66 insertions(+), 54 deletions(-) diff --git a/src/lifetimes/exercise.md b/src/lifetimes/exercise.md index c0e8f778b3f..dcc18394a95 100644 --- a/src/lifetimes/exercise.md +++ b/src/lifetimes/exercise.md @@ -29,15 +29,36 @@ message Person { } ``` +## Messages + A proto message is encoded as a series of fields, one after the next. Each is implemented as a "tag" followed by the value. The tag contains a field number (e.g., `2` for the `id` field of a `Person` message) and a wire type defining -how the payload should be determined from the byte stream. +how the payload should be determined from the byte stream. These are combined +into a single integer, as decoded in `unpack_tag` below. + +## Varint Integers, including the tag, are represented with a variable-length encoding -called VARINT. Luckily, `parse_varint` is defined for you below. The given code -also defines callbacks to handle `Person` and `PhoneNumber` fields, and to parse -a message into a series of calls to those callbacks. +called VARINT. Luckily, `parse_varint` is defined for you below. + +## Wire Types + +Proto defines several wire types, only two of which are used in this exercise. + +The `Varint` wire type contains a single varint, and is used to encode proto +values of type `int32` such as `Person.id`. + +The `Len` wire type contains a length expressed as a varint, followed by a +payload of that number of bytes. This is used to encode proto values of type +`string` such as `Person.name`. It is also used to encode proto values +containing sub-messages such as `Person.phones`, where the payload contains an +encoding of the sub-message. + +## Exercise + +The given code also defines callbacks to handle `Person` and `PhoneNumber` +fields, and to parse a message into a series of calls to those callbacks. What remains for you is to implement the `parse_field` function and the `ProtoMessage` trait for `Person` and `PhoneNumber`. diff --git a/src/lifetimes/exercise.rs b/src/lifetimes/exercise.rs index a5316e9ccce..8973a1435d4 100644 --- a/src/lifetimes/exercise.rs +++ b/src/lifetimes/exercise.rs @@ -18,8 +18,8 @@ enum WireType { /// The Varint WireType indicates the value is a single VARINT. Varint, - /// The I64 WireType indicates that the value is precisely 8 bytes in - /// little-endian order containing a 64-bit signed integer or double type. + // The I64 WireType indicates that the value is precisely 8 bytes in + // little-endian order containing a 64-bit signed integer or double type. //I64, -- not needed for this exercise /// The Len WireType indicates that the value is a length represented as a /// VARINT followed by exactly that number of bytes. @@ -195,6 +195,34 @@ impl<'a> ProtoMessage<'a> for PhoneNumber<'a> { // ANCHOR: main fn main() { + let person_id: Person = parse_message(&[0x10, 0x2a]); + assert_eq!(person_id, Person { name: "", id: 42, phone: vec![] }); + + let person_name: Person = parse_message(&[ + 0x0a, 0x0e, 0x62, 0x65, 0x61, 0x75, 0x74, 0x69, 0x66, 0x75, 0x6c, 0x20, + 0x6e, 0x61, 0x6d, 0x65, + ]); + assert_eq!(person_name, Person { name: "beautiful name", id: 0, phone: vec![] }); + + let person_name_id: Person = + parse_message(&[0x0a, 0x04, 0x45, 0x76, 0x61, 0x6e, 0x10, 0x16]); + assert_eq!(person_name_id, Person { name: "Evan", id: 22, phone: vec![] }); + + let phone: Person = parse_message(&[ + 0x0a, 0x00, 0x10, 0x00, 0x1a, 0x16, 0x0a, 0x0e, 0x2b, 0x31, 0x32, 0x33, + 0x34, 0x2d, 0x37, 0x37, 0x37, 0x2d, 0x39, 0x30, 0x39, 0x30, 0x12, 0x04, + 0x68, 0x6f, 0x6d, 0x65, + ]); + assert_eq!( + phone, + Person { + name: "", + id: 0, + phone: vec![PhoneNumber { number: "+1234-777-9090", type_: "home" },], + } + ); + + // Put that all together into a single parse. let person: Person = parse_message(&[ 0x0a, 0x07, 0x6d, 0x61, 0x78, 0x77, 0x65, 0x6c, 0x6c, 0x10, 0x2a, 0x1a, 0x16, 0x0a, 0x0e, 0x2b, 0x31, 0x32, 0x30, 0x32, 0x2d, 0x35, 0x35, 0x35, @@ -203,53 +231,16 @@ fn main() { 0x2d, 0x35, 0x33, 0x30, 0x38, 0x12, 0x06, 0x6d, 0x6f, 0x62, 0x69, 0x6c, 0x65, ]); - println!("{:#?}", person); + assert_eq!( + person, + Person { + name: "maxwell", + id: 42, + phone: vec![ + PhoneNumber { number: "+1202-555-1212", type_: "home" }, + PhoneNumber { number: "+1800-867-5308", type_: "mobile" }, + ] + } + ); } // ANCHOR_END: main - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_id() { - let person_id: Person = parse_message(&[0x10, 0x2a]); - assert_eq!(person_id, Person { name: "", id: 42, phone: vec![] }); - } - - #[test] - fn test_name() { - let person_name: Person = parse_message(&[ - 0x0a, 0x0e, 0x62, 0x65, 0x61, 0x75, 0x74, 0x69, 0x66, 0x75, 0x6c, 0x20, - 0x6e, 0x61, 0x6d, 0x65, - ]); - assert_eq!( - person_name, - Person { name: "beautiful name", id: 0, phone: vec![] } - ); - } - - #[test] - fn test_just_person() { - let person_name_id: Person = - parse_message(&[0x0a, 0x04, 0x45, 0x76, 0x61, 0x6e, 0x10, 0x16]); - assert_eq!(person_name_id, Person { name: "Evan", id: 22, phone: vec![] }); - } - - #[test] - fn test_phone() { - let phone: Person = parse_message(&[ - 0x0a, 0x00, 0x10, 0x00, 0x1a, 0x16, 0x0a, 0x0e, 0x2b, 0x31, 0x32, 0x33, - 0x34, 0x2d, 0x37, 0x37, 0x37, 0x2d, 0x39, 0x30, 0x39, 0x30, 0x12, 0x04, - 0x68, 0x6f, 0x6d, 0x65, - ]); - assert_eq!( - phone, - Person { - name: "", - id: 0, - phone: vec![PhoneNumber { number: "+1234-777-9090", type_: "home" },], - } - ); - } -}