twitter_text_config/
lib.rs

1// Copyright 2019 Robert Sayre
2// Licensed under the Apache License, Version 2.0
3// http://www.apache.org/licenses/LICENSE-2.0
4
5#[macro_use]
6extern crate serde_derive;
7extern crate serde_json;
8extern crate serde;
9
10#[macro_use]
11extern crate lazy_static;
12
13use std::cmp::Ordering;
14use std::path::PathBuf;
15use std::fmt;
16use std::fs::File;
17use std::io::Read;
18use serde::ser::{Serialize, Serializer, SerializeStruct};
19use serde::de::{self, Deserialize, Deserializer, Visitor, MapAccess};
20
21const DEFAULT_VERSION: i32 = 2;
22const DEFAULT_WEIGHTED_LENGTH: i32 = 280;
23const DEFAULT_SCALE: i32 = 100;
24const DEFAULT_WEIGHT: i32 = 200;
25const DEFAULT_TRANSFORMED_URL_LENGTH: i32 = 23;
26const V1_JSON: &str = include_str!("v1.json");
27const V2_JSON: &str = include_str!("v2.json");
28const V3_JSON: &str = include_str!("v3.json");
29
30lazy_static! {
31    static ref CONFIG_V1: Configuration = Configuration::configuration_from_str(V1_JSON);
32    static ref CONFIG_V2: Configuration = Configuration::configuration_from_str(V2_JSON);
33    static ref CONFIG_V3: Configuration = Configuration::configuration_from_str(V3_JSON);
34}
35
36pub fn config_v1() -> &'static Configuration {
37    &CONFIG_V1
38}
39
40pub fn config_v2() -> &'static Configuration {
41    &CONFIG_V2
42}
43
44pub fn config_v3() -> &'static Configuration {
45    &CONFIG_V3
46}
47
48pub fn default() -> &'static Configuration {
49    &CONFIG_V3
50}
51
52#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
53#[serde(rename_all = "camelCase")]
54pub struct Configuration {
55    pub version: i32,
56    pub max_weighted_tweet_length: i32,
57    pub scale: i32,
58    pub default_weight: i32,
59    #[serde(rename = "transformedURLLength")]
60    pub transformed_url_length: i32,
61    pub ranges: Vec<WeightedRange>,
62    #[serde(default)]
63    pub emoji_parsing_enabled: bool,
64}
65
66impl Configuration {
67    pub fn default() -> Configuration {
68        Configuration {
69            version: DEFAULT_VERSION,
70            max_weighted_tweet_length: DEFAULT_WEIGHTED_LENGTH,
71            scale: DEFAULT_SCALE,
72            default_weight: DEFAULT_WEIGHT,
73            ranges: Configuration::default_ranges(),
74            transformed_url_length: DEFAULT_TRANSFORMED_URL_LENGTH,
75            emoji_parsing_enabled: true
76        }
77    }
78
79    fn default_ranges() -> Vec<WeightedRange> {
80         vec!(
81            WeightedRange::new(0, 4351, 100),
82            WeightedRange::new(8192, 8205, 100),
83            WeightedRange::new(8208, 8223, 100),
84            WeightedRange::new(8242, 8247, 100),
85        )
86    }
87
88    pub fn configuration_from_json(config_file: &str) -> Configuration {
89        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
90        path.push("config");
91        path.push(config_file);
92        Configuration::configuration_from_file(&path)
93    }
94
95    pub fn configuration_from_file(path: &PathBuf) -> Configuration {
96        let mut f = File::open(path).expect("Config file not found");
97        let mut contents = String::new();
98        f.read_to_string(&mut contents).expect("Error reading config file");
99        Configuration::configuration_from_str(&contents)
100    }
101
102    pub fn configuration_from_str(json: &str) -> Configuration {
103        serde_json::from_str(&json).expect("Error parsing json")
104    }
105}
106
107#[derive(Debug, PartialEq, Hash, Clone, Copy)]
108pub struct WeightedRange {
109    pub range: Range,
110    pub weight: i32
111}
112
113impl WeightedRange {
114    pub fn new(start: i32, end: i32, weight: i32) -> WeightedRange {
115        WeightedRange {
116            range: Range::new(start, end),
117            weight
118        }
119    }
120
121    pub fn contains(&self, i: i32) -> bool {
122        self.range.contains(&i)
123    }
124}
125
126impl Serialize for WeightedRange {
127    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
128        where
129            S: Serializer,
130    {
131        let mut state = serializer.serialize_struct("WeightedRange", 3)?;
132        state.serialize_field("start", &self.range.start())?;
133        state.serialize_field("end", &self.range.end())?;
134        state.serialize_field("weight", &self.weight)?;
135        state.end()
136    }
137}
138
139impl<'de> Deserialize<'de> for WeightedRange {
140    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
141        where
142            D: Deserializer<'de>,
143    {
144        #[derive(Deserialize)]
145        #[serde(field_identifier, rename_all = "lowercase")]
146        enum Field { Start, End, Weight }
147
148        struct WeightedRangeVisitor;
149        impl<'de> Visitor<'de> for WeightedRangeVisitor {
150            type Value = WeightedRange;
151
152            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
153                formatter.write_str("struct WeightedRange")
154            }
155
156            fn visit_map<V>(self, mut map: V) -> Result<WeightedRange, V::Error>
157                where
158                    V: MapAccess<'de>,
159            {
160                let mut start = None;
161                let mut end = None;
162                let mut weight = None;
163                while let Some(key) = map.next_key()? {
164                    match key {
165                        Field::Start => {
166                            if start.is_some() {
167                                return Err(de::Error::duplicate_field("start"));
168                            }
169                            start = Some(map.next_value()?);
170                        }
171                        Field::End => {
172                            if end.is_some() {
173                                return Err(de::Error::duplicate_field("end"));
174                            }
175                            end = Some(map.next_value()?);
176                        }
177                        Field::Weight => {
178                            if weight.is_some() {
179                                return Err(de::Error::duplicate_field("weight"));
180                            }
181                            weight = Some(map.next_value()?);
182                        }
183                    }
184                }
185                let start = start.ok_or_else(|| de::Error::missing_field("start"))?;
186                let end = end.ok_or_else(|| de::Error::missing_field("end"))?;
187                let weight = weight.ok_or_else(|| de::Error::missing_field("weight"))?;
188                Ok(WeightedRange::new(start, end, weight))
189            }
190        }
191
192        const FIELDS: &'static [&'static str] = &["start", "end", "weight"];
193        deserializer.deserialize_struct("WeightedRange", FIELDS, WeightedRangeVisitor)
194    }
195}
196
197#[derive(Debug, PartialEq, Hash, Eq, Clone, Copy)]
198pub struct Range {
199    start: i32,
200    end: i32,
201}
202
203impl Range {
204    pub fn empty() -> Range {
205        Range { start: 0, end: 0 }
206    }
207
208    pub fn new( start: i32, end: i32 ) -> Range {
209        Range { start, end }
210    }
211
212    pub fn contains(&self, val: &i32) -> bool {
213        val >= &self.start && val <= &self.end
214    }
215
216    pub fn start(&self) -> i32 {
217        self.start
218    }
219
220    pub fn end(&self) -> i32 {
221        self.end
222    }
223}
224
225impl Ord for Range {
226    fn cmp(&self, other: &Self) -> Ordering {
227        if self.start < other.start {
228            Ordering::Less
229        } else if self.start == other.start {
230            if self.end < other.end {
231                Ordering::Less
232            } else if self.end < other.end {
233                Ordering::Equal
234            } else {
235                Ordering::Greater
236            }
237        } else {
238            Ordering::Greater
239        }
240    }
241}
242
243impl PartialOrd for Range {
244    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
245        Some(self.cmp(other))
246    }
247}