1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
use serde::Deserialize;
use serde::Serialize;
use serde_json;
use std::fs::File;
use std::io::Read;
use std::io::Write;
use std::path::Path;

use super::Error;

pub type Url = String;
pub type Uuid = String;
pub type Base64 = String;
pub type Hash = String;

#[derive(Debug, Serialize, Deserialize)]
pub struct RecipePhoto {
    pub data: Base64,
    pub filename: String,
    pub name: String,
    pub hash: Hash,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Recipe {
    pub created: String,
    pub difficulty: String,
    pub photo_large: Option<Base64>,
    pub uid: Uuid,
    pub photo: Option<String>,
    pub categories: Vec<String>,
    pub notes: String,
    pub prep_time: String,
    pub cook_time: String,
    pub source: String,
    pub source_url: String,
    pub photo_data: Option<Base64>,
    pub total_time: String,
    pub name: String,
    pub ingredients: String,
    #[serde(default)]
    pub photos: Vec<RecipePhoto>,
    pub rating: u8,
    pub description: String,
    pub photo_hash: Option<Hash>,
    pub image_url: Option<Url>,
    pub nutritional_info: String,
    pub directions: String,
    #[serde(skip_serializing)]
    pub hash: Hash,
    pub servings: String,
}

impl Recipe {
    pub fn from_reader<R: Read>(rdr: R) -> Result<Recipe, Error> {
        let gzip_decoder = libflate::gzip::Decoder::new(rdr)?;
        Ok(serde_json::from_reader(gzip_decoder)?)
    }

    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Recipe, Error> {
        Ok(Self::from_reader(File::open(path)?)?)
    }

    pub fn to_writer<W: Write>(&self, writer: W) -> Result<(), Error> {
        let mut gzip_encoder = libflate::gzip::Encoder::new(writer)?;
        serde_json::to_writer(&mut gzip_encoder, self)?;
        gzip_encoder.finish().into_result()?;
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn deserialization() {
        let data = include_bytes!("test/Brownies.paprikarecipe.json");
        let result: Result<Recipe, _> = serde_json::from_slice(data);
        assert!(result.is_ok());

        let recipe = result.unwrap();
        assert_eq!(recipe.name, "Brownies");
    }

    #[test]
    fn from_file() {
        let data = include_bytes!("test/Brownies.paprikarecipe");
        let result = Recipe::from_reader(&data[..]);

        let recipe = result.unwrap();
        assert_eq!(recipe.name, "Brownies");
    }
}