cookie-factory in Rust has no documentation on how to understand it. It just lists all the functions. I took a look at examples/http.rs and could understand little about it.
I have the following struct:
pub struct DigestChallengeResponse {
pub username: String,
pub realm: Option<String>,
pub uri: Option<Url>,
pub nonce: Option<String>,
pub cnonce: Option<String>,
pub opaque: Option<String>,
pub stale: Option<bool>,
pub algorithm: Option<Algorithm>,
pub cnonce: i32,
pub qop: Option<Qop>,
pub response: String,
pub userhash: Option<bool>,
}
where the non standard types implement Display
I need to serialize into this:
Authorization: Digest
username="488869477bf257147b804c45308cd62ac4e25eb717
b12b298c79e62dcea254ec",
realm="api#example.org",
uri="/doe.json",
algorithm=SHA-512-256,
nonce="5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK",
nc=00000001,
cnonce="NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v",
qop=auth,
response="ae66e67d6b427bd3f120414a82e4acff38e8ecd9101d
6c861229025f607a79dd",
opaque="HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS",
userhash=true
First of all, do I really need cookie-factory for such simple serialization? And how should I do it?
I took a look at do_gen, which is one of the main macros, but couldn't understand what it does: https://docs.rs/cookie-factory/0.3.2/cookie_factory/macro.do_gen.html
There are 3 things to do:
the simplest is to implement Display for your struct
Option does not implement Display, so you have to define your own displayed trait and implement each type you put as optional
Enum also does not implement Display. But actually, we have a third library strum a helper to allow you to do the action.
So, here is your stringifyer :)
extern crate strum;
#[macro_use]
extern crate strum_macros;
use std::fmt;
#[derive(AsRefStr)]
enum Algorithm {
Sha512,
Sha256,
}
#[derive(AsRefStr)]
enum Qop {
Auth,
}
struct DigestChallengeResponse {
pub username: String,
pub realm: Option<String>,
pub uri: Option<String>,
pub nonce: Option<String>,
pub nc: Option<String>,
pub opaque: Option<String>,
pub stale: Option<bool>,
pub algorithm: Option<Algorithm>,
pub cnonce: Option<String>,
pub qop: Option<Qop>,
pub response: String,
pub userhash: Option<bool>,
}
trait DisplayOption {
fn to_string(&self) -> String;
}
impl DisplayOption for std::option::Option<String> {
fn to_string(&self) -> String {
match self {
Some(val) => format!("{}", val),
_ => "".to_string()
}
}
}
impl DisplayOption for std::option::Option<Algorithm> {
fn to_string(&self) -> String {
match self {
Some(val) => format!("{}", val.as_ref()),
_ => "".to_string()
}
}
}
impl DisplayOption for std::option::Option<Qop> {
fn to_string(&self) -> String {
match self {
Some(val) => format!("{}", val.as_ref()),
_ => "".to_string()
}
}
}
impl DisplayOption for std::option::Option<bool> {
fn to_string(&self) -> String {
match self {
Some(val) => if *val {"true".to_string()} else {"false".to_string()},
_ => "".to_string()
}
}
}
impl fmt::Display for DigestChallengeResponse {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f,
"Digest
username={},
realm={},
uri={},
algorithm={},
nonce={},
nc={},
qop={},
response={},
opaque={},
userhash={},
stale={}
"
, self.username
, self.realm.to_string()
, self.uri.to_string()
, self.algorithm.to_string()
, self.nonce.to_string()
, self.nc.to_string()
, self.qop.to_string()
, self.response.to_string()
, self.opaque.to_string()
, self.userhash.to_string()
, self.stale.to_string()
)
}
}
fn main() {
let authorization = DigestChallengeResponse {
username: "488869477bf257147b804c45308cd62ac4e25eb717b12b298c79e62dcea254ec".to_string(),
realm: Some("api#example.org".to_string()),
uri: Some("/doe.json".to_string()),
algorithm: Some(Algorithm::Sha512),
nonce: Some("5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK".to_string()),
nc: Some("00000001".to_string()),
cnonce: Some("NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v".to_string()),
qop: Some(Qop::Auth),
response: "ae66e67d6b427bd3f120414a82e4acff38e8ecd9101d6c861229025f607a79dd".to_string(),
opaque: Some("HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS".to_string()),
userhash: Some(true),
stale: Some(true),
};
println!("Authorization: {}", authorization);
}
Related
I'm trying to use the snafu crate for error handling, but keep getting erros that my Error enum struct is missing the 'source' and that IntoError is not implimented for Error:
//main.rs
use snafu::{ResultExt, Snafu};
#[derive(Debug, Snafu)]
#[snafu(visibility = "pub(crate)")]
pub enum Error{
#[snafu(display("Could not load gallery JSON: {}: {}", json_path, source))]
LoadGallery {
source: std::io::Error,
json_path: String,
},
}
//gallery.rs
use snafu::{ResultExt};
use crate::Error::{LoadGallery};
pub struct Gallery{
name: String,
}
impl Gallery{
pub fn from_json(json_path: String)->Result<()>{
let configuration = std::fs::read_to_string(&json_path).context(LoadGallery { json_path })?;
Ok(())
}
}
results in:
let configuration = std::fs::read_to_string(&json_path).context(LoadGallery { json_path })?;
| ^^^^^^^^^^^ missing `source`
let configuration = std::fs::read_to_string(&json_path).context(LoadGallery { json_path })?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IntoError<_>` is not implemented for `Error`
Based on this example from the docs, I don't see what I'm doing wrong:
use snafu::{ResultExt, Snafu};
use std::{fs, io, path::PathBuf};
#[derive(Debug, Snafu)]
enum Error {
#[snafu(display("Unable to read configuration from {}: {}", path.display(), source))]
ReadConfiguration { source: io::Error, path: PathBuf },
#[snafu(display("Unable to write result to {}: {}", path.display(), source))]
WriteResult { source: io::Error, path: PathBuf },
}
type Result<T, E = Error> = std::result::Result<T, E>;
fn process_data() -> Result<()> {
let path = "config.toml";
let configuration = fs::read_to_string(path).context(ReadConfiguration { path })?;
let path = unpack_config(&configuration);
fs::write(&path, b"My complex calculation").context(WriteResult { path })?;
Ok(())
}
fn unpack_config(data: &str) -> &str {
"/some/path/that/does/not/exist"
}
It's because when you're constructing LoadGallery, you're attempting to construct Error::LoadGallery. You then get a compile error saying "missing source", because the Error::LoadGallery variant has a source field. Fixing it is straight forward, you just need to change which LoadGallery you import.
// Not this one:
// use crate::Error::LoadGallery;
// This one:
use crate::LoadGallery;
Why? Because snafu generates a struct for each of Error's variants. So there's a struct LoadGallery being generated. This struct doesn't contain the source field, which is why you can construct it without source and pass it to context(), because it's not actually Error::LoadGallery.
Your from_json() also needs to return Result<(), Error> instead of Result<()> (you don't have the type alias, like in the example.)
use crate::{Error, LoadGallery};
use snafu::ResultExt;
pub struct Gallery {
name: String,
}
impl Gallery {
pub fn from_json(json_path: String) -> Result<(), Error> {
let configuration =
std::fs::read_to_string(&json_path).context(LoadGallery { json_path })?;
Ok(())
}
}
If you're curious you can use cargo expand to inspect what macros expand to. You first need to install it by doing cargo install expand. Then you can execute cargo expand in any project.
I'm trying to implement Serialize for an enum that includes struct variants. The serde.rs documentation indicates the following:
enum E {
// Use three-step process:
// 1. serialize_struct_variant
// 2. serialize_field
// 3. end
Color { r: u8, g: u8, b: u8 },
// Use three-step process:
// 1. serialize_tuple_variant
// 2. serialize_field
// 3. end
Point2D(f64, f64),
// Use serialize_newtype_variant.
Inches(u64),
// Use serialize_unit_variant.
Instance,
}
With that in mind, I proceeded to implemention:
use serde::ser::{Serialize, SerializeStructVariant, Serializer};
use serde_derive::Deserialize;
#[derive(Deserialize)]
enum Variants {
VariantA,
VariantB { k: u32, p: f64 },
}
impl Serialize for Variants {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
Variants::VariantA => serializer.serialize_unit_variant("Variants", 0, "VariantA"),
Variants::VariantB { ref k, ref p } => {
let mut state =
serializer.serialize_struct_variant("Variants", 1, "VariantB", 2)?;
state.serialize_field("k", k)?;
state.serialize_field("p", p)?;
state.end()
}
}
}
}
fn main() {
let x = Variants::VariantB { k: 5, p: 5.0 };
let toml_str = toml::to_string(&x).unwrap();
println!("{}", toml_str);
}
The code compiles, but when I run it it fails:
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: UnsupportedType', src/libcore/result.rs:999:5
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
I figured the issue must be in my use of the API, so I consulted the API documentation for StructVariant and it looks practically the same as my code. I'm sure I'm missing something, but I don't see it based on the docs and output.
Enabling external tagging for the enum enables Serde to serialize/deserialize it to TOML:
#[derive(Deserialize)]
#[serde(tag = "type")]
enum Variants {
VariantA,
VariantB { k: u32, p: f64 },
}
toml::to_string(&Variants::VariantB { k: 42, p: 13.37 })
serializes to
type = VariantB
k = 42
p = 13.37
This works well in Vecs and HashMaps, too.
The TOML format does not support enums with values:
use serde::Serialize; // 1.0.99
use toml; // 0.5.3
#[derive(Serialize)]
enum A {
B(i32),
}
fn main() {
match toml::to_string(&A::B(42)) {
Ok(s) => println!("{}", s),
Err(e) => eprintln!("Error: {}", e),
}
}
Error: unsupported Rust type
It's unclear what you'd like your data structure to map to as TOML. Using JSON works just fine:
use serde::Serialize; // 1.0.99
use serde_json; // 1.0.40
#[derive(Serialize)]
enum Variants {
VariantA,
VariantB { k: u32, p: f64 },
}
fn main() {
match serde_json::to_string(&Variants::VariantB { k: 42, p: 42.42 }) {
Ok(s) => println!("{}", s),
Err(e) => eprintln!("Error: {}", e),
}
}
{"VariantB":{"k":42,"p":42.42}}
In this minimalist program, I'd like the file_size function to include the path /not/there in the Err so it can be displayed in the main function:
use std::fs::metadata;
use std::io;
use std::path::Path;
use std::path::PathBuf;
fn file_size(path: &Path) -> io::Result<u64> {
Ok(metadata(path)?.len())
}
fn main() {
if let Err(err) = file_size(&PathBuf::from("/not/there")) {
eprintln!("{}", err);
}
}
You must define your own error type in order to wrap this additional data.
Personally, I like to use the custom_error crate for that, as it's especially convenient for dealing with several types. In your case it might look like this:
use custom_error::custom_error;
use std::fs::metadata;
use std::io;
use std::path::{Path, PathBuf};
use std::result::Result;
custom_error! {ProgramError
Io {
source: io::Error,
path: PathBuf
} = #{format!("{path}: {source}", source=source, path=path.display())},
}
fn file_size(path: &Path) -> Result<u64, ProgramError> {
metadata(path)
.map(|md| md.len())
.map_err(|e| ProgramError::Io {
source: e,
path: path.to_path_buf(),
})
}
fn main() {
if let Err(err) = file_size(&PathBuf::from("/not/there")) {
eprintln!("{}", err);
}
}
Output:
/not/there: No such file or directory (os error 2)
While Denys Séguret's answer is correct, I like using my crate SNAFU because it provides the concept of a context. This makes the act of attaching the path (or anything else!) very easy to do:
use snafu::{ResultExt, Snafu}; // 0.2.3
use std::{
fs, io,
path::{Path, PathBuf},
};
#[derive(Debug, Snafu)]
enum ProgramError {
#[snafu(display("Could not get metadata for {}: {}", path.display(), source))]
Metadata { source: io::Error, path: PathBuf },
}
fn file_size(path: impl AsRef<Path>) -> Result<u64, ProgramError> {
let path = path.as_ref();
let md = fs::metadata(&path).context(Metadata { path })?;
Ok(md.len())
}
fn main() {
if let Err(err) = file_size("/not/there") {
eprintln!("{}", err);
}
}
I'm trying to do struct serialization, in which the bytes would eventually be sent down a pipe, reconstructed and methods be called on them.
I created a trait these structs would implement as appropriate and I'm using serde and serde-cbor for serialization:
extern crate serde_cbor;
#[macro_use]
extern crate serde_derive;
extern crate serde;
use serde_cbor::ser::*;
use serde_cbor::de::*;
trait Contract {
fn do_something(&self);
}
#[derive(Debug, Serialize, Deserialize)]
struct Foo {
x: u32,
y: u32,
}
#[derive(Debug, Serialize, Deserialize)]
struct Bar {
data: Vec<Foo>,
}
#[derive(Debug, Serialize, Deserialize)]
struct Baz {
data: Vec<Foo>,
tag: String,
}
impl Contract for Bar {
fn do_something(&self) {
println!("I'm a Bar and this is my data {:?}", self.data);
}
}
impl Contract for Baz {
fn do_something(&self) {
println!("I'm Baz {} and this is my data {:?}", self.tag, self.data);
}
}
fn main() {
let data = Bar { data: vec![Foo { x: 1, y: 2 }, Foo { x: 3, y: 4 }, Foo { x: 7, y: 8 }] };
data.do_something();
let value = to_vec(&data).unwrap();
let res: Result<Contract, _> = from_reader(&value[..]);
let res = res.unwrap();
println!("{:?}", res);
res.do_something();
}
When I try to reconstruct the bytes using the trait as the type (given that I wouldn't know which underlying object is being sent), the compiler complains that the trait does not implement the Sized trait:
error[E0277]: the trait bound `Contract: std::marker::Sized` is not satisfied
--> src/main.rs:52:15
|
52 | let res: Result<Contract, _> = from_reader(&value[..]);
| ^^^^^^^^^^^^^^^^^^^ the trait `std::marker::Sized` is not implemented for `Contract`
|
= note: `Contract` does not have a constant size known at compile-time
= note: required by `std::result::Result`
I guess it makes sense since the compiler doesn't know how big the struct is supposed to be and doesn't know how to line up the bytes for it. If I change the line where I deserialize the object to specify the actual struct type, it works:
let res: Result<Bar, _> = from_reader(&value[..]);
Is there a better pattern to achieve this serialization + polymorphism behavior?
It looks like you fell into the same trap that I fell into when I moved from C++ to Rust. Trying to use polymorphism to model a fixed set of variants of a type. Rust's enums (similar to Haskell's enums, and equivalent to Ada's variant record types) are different from classical enums in other languages, because the enum variants can have fields of their own.
I suggest you change your code to
#[derive(Debug, Serialize, Deserialize)]
enum Contract {
Bar { data: Vec<Foo> },
Baz { data: Vec<Foo>, tag: String },
}
#[derive(Debug, Serialize, Deserialize)]
struct Foo {
x: u32,
y: u32,
}
impl Contract {
fn do_something(&self) {
match *self {
Contract::Bar { ref data } => println!("I'm a Bar and this is my data {:?}", data),
Contract::Baz { ref data, ref tag } => {
println!("I'm Baz {} and this is my data {:?}", tag, data)
}
}
}
}
You can use typetag to solve the problem. Add #[typetag::serde] (or ::deserialize, as shown here) to the trait and each implementation:
use serde::Deserialize;
#[typetag::deserialize(tag = "driver")]
trait Contract {
fn do_something(&self);
}
#[derive(Debug, Deserialize, PartialEq)]
struct File {
path: String,
}
#[typetag::deserialize(name = "file")]
impl Contract for File {
fn do_something(&self) {
eprintln!("I'm a File {}", self.path);
}
}
#[derive(Debug, Deserialize, PartialEq)]
struct Http {
port: u16,
endpoint: String,
}
#[typetag::deserialize(name = "http")]
impl Contract for Http {
fn do_something(&self) {
eprintln!("I'm an Http {}:{}", self.endpoint, self.port);
}
}
fn main() {
let f = r#"
{
"driver": "file",
"path": "/var/log/foo"
}
"#;
let h = r#"
{
"driver": "http",
"port": 8080,
"endpoint": "/api/bar"
}
"#;
let f: Box<dyn Contract> = serde_json::from_str(f).unwrap();
f.do_something();
let h: Box<dyn Contract> = serde_json::from_str(h).unwrap();
h.do_something();
}
[dependencies]
serde_json = "1.0.57"
serde = { version = "1.0.114", features = ["derive"] }
typetag = "0.1.5"
See also:
How can deserialization of polymorphic trait objects be added in Rust if at all?
Adding on to oli_obk's answer, you can use Serde's enum representation to distinguish between the types.
Here, I use the internally-tagged representation to deserialize these two similar objects into the appropriate variant:
{
"driver": "file",
"path": "/var/log/foo"
}
{
"driver": "http",
"port": 8080,
"endpoint": "/api/bar"
}
use serde; // 1.0.82
use serde_derive::*; // 1.0.82
use serde_json; // 1.0.33
#[derive(Debug, Deserialize, PartialEq)]
#[serde(tag = "driver")]
enum Driver {
#[serde(rename = "file")]
File { path: String },
#[serde(rename = "http")]
Http { port: u16, endpoint: String }
}
fn main() {
let f = r#"
{
"driver": "file",
"path": "/var/log/foo"
}
"#;
let h = r#"
{
"driver": "http",
"port": 8080,
"endpoint": "/api/bar"
}
"#;
let f: Driver = serde_json::from_str(f).unwrap();
assert_eq!(f, Driver::File { path: "/var/log/foo".into() });
let h: Driver = serde_json::from_str(h).unwrap();
assert_eq!(h, Driver::Http { port: 8080, endpoint: "/api/bar".into() });
}
You don't have to squash it all into one enum, you can create separate types as well:
#[derive(Debug, Deserialize, PartialEq)]
#[serde(tag = "driver")]
enum Driver {
#[serde(rename = "file")]
File(File),
#[serde(rename = "http")]
Http(Http),
}
#[derive(Debug, Deserialize, PartialEq)]
struct File {
path: String,
}
#[derive(Debug, Deserialize, PartialEq)]
struct Http {
port: u16,
endpoint: String,
}
I've read valve's JSON Serialization in Rust, Part 1 and try to run the code in the blogpost. The most complicated part is do a custom serialization for a custom struct.
I update the snippet so it can run on newest Rust nightly:
extern crate rustc_serialize;
use rustc_serialize::{json, Encodable, Encoder};
struct Person {
name: String,
age: usize,
}
impl Encodable for Person {
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
match *self {
Person { name: ref p_name, age: ref p_age, } => {
s.emit_struct("Person", 0, |s| {
try!(s.emit_struct_field( "name", 0, |s| p_name.encode(s)));
try!(s.emit_struct_field( "age", 1, |s| p_age.encode(s)));
try!(s.emit_struct_field( "summary", 2, |s| {
(format!("Nice person named {}, {} years of age", p_name, p_age)).encode(s)
}));
Ok(())
})
},
}
}
}
fn main() {
let person = Person {
name: "John Doe".to_string(),
age: 33,
};
println!("{}" , json::encode(&person).unwrap());
}
The output of above is {}, but the correct result should be:
{"age":33,"name":"John Doe","summary":"Nice person named John Doe, 33 years of age"}
I want to know how to use Encodable trait to serialize a custom struct in right way.
Thank you.
It looks like your tutorial is out-of-date. It says
We call emit_struct on our encoder and pass it 3 arguments: the name of the struct, current index and an anonymous function(aka lambda). The name of the struct is not used; current index is not used too.
But the code says
fn emit_struct<F>(&mut self, _: &str, len: usize, f: F) -> EncodeResult<()> where
F: FnOnce(&mut Encoder<'a>) -> EncodeResult<()>,
{
if self.is_emitting_map_key { return Err(EncoderError::BadHashmapKey); }
if len == 0 {
try!(write!(self.writer, "{{}}"));
So the argument has changed from an index to a length, and it's now meaningful. Here's your example working:
extern crate rustc_serialize;
use rustc_serialize::{json, Encodable, Encoder};
struct Person {
name: String,
age: usize,
}
impl Encodable for Person {
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
s.emit_struct("Person", 1, |s| {
try!(s.emit_struct_field("name", 0, |s| self.name.encode(s)));
try!(s.emit_struct_field("age", 1, |s| self.age.encode(s)));
try!(s.emit_struct_field("summary", 2, |s| {
let summary = format!("Nice person named {}, {} years of age", self.name, self.age);
summary.encode(s)
}));
Ok(())
})
}
}
fn main() {
let person = Person {
name: "John Doe".to_string(),
age: 33,
};
println!("{}" , json::encode(&person).unwrap());
}
Note that I also removed the crazy gyrations to destructure self and just access the properties directly.
You problem is with the emit_struct(..) call.
The prototype of this function is:
fn emit_struct<F>(&mut self, name: &str, len: usize, f: F)
-> Result<(), Self::Error>
where F: FnOnce(&mut Self) -> Result<(), Self::Error>;
Here, len is the number of fields of your struct. Bu you are setting it to 0, so the JSON dictionary generated has 0 fields.
Changing it to 3 gives this output:
{"name":"John Doe","age":33,"summary":"Nice person named John Doe, 33 years of age"}