Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more description of proto to the proto exercise #2475

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions src/lifetimes/exercise.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
91 changes: 41 additions & 50 deletions src/lifetimes/exercise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand All @@ -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" },],
}
);
}
}