|
| 1 | +//! Generic page struct |
| 2 | +
|
| 3 | +use handlebars_iron::Template; |
| 4 | +use iron::response::Response; |
| 5 | +use iron::{status, IronResult, Set}; |
| 6 | +use serde::{ |
| 7 | + ser::{SerializeStruct, Serializer}, |
| 8 | + Serialize, |
| 9 | +}; |
| 10 | +use serde_json::Value; |
| 11 | +use std::collections::BTreeMap; |
| 12 | + |
| 13 | +lazy_static::lazy_static! { |
| 14 | + static ref RUSTC_RESOURCE_SUFFIX: String = load_rustc_resource_suffix() |
| 15 | + .unwrap_or_else(|_| "???".into()); |
| 16 | +} |
| 17 | + |
| 18 | +fn load_rustc_resource_suffix() -> Result<String, failure::Error> { |
| 19 | + let conn = crate::db::connect_db()?; |
| 20 | + |
| 21 | + let res = conn.query( |
| 22 | + "SELECT value FROM config WHERE name = 'rustc_version';", |
| 23 | + &[], |
| 24 | + )?; |
| 25 | + if res.is_empty() { |
| 26 | + failure::bail!("missing rustc version"); |
| 27 | + } |
| 28 | + |
| 29 | + if let Some(Ok(vers)) = res.get(0).get_opt::<_, Value>("value") { |
| 30 | + if let Some(vers_str) = vers.as_str() { |
| 31 | + return Ok(crate::utils::parse_rustc_version(vers_str)?); |
| 32 | + } |
| 33 | + } |
| 34 | + |
| 35 | + failure::bail!("failed to parse the rustc version"); |
| 36 | +} |
| 37 | + |
| 38 | +#[derive(Debug, Clone, PartialEq, Eq)] |
| 39 | +pub struct Page<T: Serialize> { |
| 40 | + title: Option<String>, |
| 41 | + content: T, |
| 42 | + status: status::Status, |
| 43 | + varss: BTreeMap<String, String>, |
| 44 | + varsb: BTreeMap<String, bool>, |
| 45 | + varsi: BTreeMap<String, i64>, |
| 46 | + rustc_resource_suffix: &'static str, |
| 47 | +} |
| 48 | + |
| 49 | +impl<T: Serialize> Page<T> { |
| 50 | + pub fn new(content: T) -> Page<T> { |
| 51 | + Page { |
| 52 | + title: None, |
| 53 | + content, |
| 54 | + status: status::Ok, |
| 55 | + varss: BTreeMap::new(), |
| 56 | + varsb: BTreeMap::new(), |
| 57 | + varsi: BTreeMap::new(), |
| 58 | + rustc_resource_suffix: &RUSTC_RESOURCE_SUFFIX, |
| 59 | + } |
| 60 | + } |
| 61 | + |
| 62 | + /// Sets a string variable |
| 63 | + pub fn set(mut self, var: &str, val: &str) -> Page<T> { |
| 64 | + self.varss.insert(var.to_owned(), val.to_owned()); |
| 65 | + self |
| 66 | + } |
| 67 | + |
| 68 | + /// Sets a boolean variable |
| 69 | + pub fn set_bool(mut self, var: &str, val: bool) -> Page<T> { |
| 70 | + self.varsb.insert(var.to_owned(), val); |
| 71 | + self |
| 72 | + } |
| 73 | + |
| 74 | + /// Sets a boolean variable to true |
| 75 | + pub fn set_true(mut self, var: &str) -> Page<T> { |
| 76 | + self.varsb.insert(var.to_owned(), true); |
| 77 | + self |
| 78 | + } |
| 79 | + |
| 80 | + /// Sets an integer variable |
| 81 | + pub fn set_int(mut self, var: &str, val: i64) -> Page<T> { |
| 82 | + self.varsi.insert(var.to_owned(), val); |
| 83 | + self |
| 84 | + } |
| 85 | + |
| 86 | + /// Sets title of page |
| 87 | + pub fn title(mut self, title: &str) -> Page<T> { |
| 88 | + self.title = Some(title.to_owned()); |
| 89 | + self |
| 90 | + } |
| 91 | + |
| 92 | + /// Sets status code for response |
| 93 | + pub fn set_status(mut self, s: status::Status) -> Page<T> { |
| 94 | + self.status = s; |
| 95 | + self |
| 96 | + } |
| 97 | + |
| 98 | + #[allow(clippy::wrong_self_convention)] |
| 99 | + pub fn to_resp(self, template: &str) -> IronResult<Response> { |
| 100 | + let mut resp = Response::new(); |
| 101 | + let status = self.status; |
| 102 | + let temp = Template::new(template, self); |
| 103 | + resp.set_mut(temp).set_mut(status); |
| 104 | + |
| 105 | + Ok(resp) |
| 106 | + } |
| 107 | +} |
| 108 | + |
| 109 | +impl<T: Serialize> Serialize for Page<T> { |
| 110 | + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> |
| 111 | + where |
| 112 | + S: Serializer, |
| 113 | + { |
| 114 | + // Make sure that the length parameter passed to serde is correct by |
| 115 | + // adding the someness of the global alert to the total. `true` |
| 116 | + // is 1 and `false` is 0, so it increments if the value is some (and therefore |
| 117 | + // needs to be serialized) |
| 118 | + let mut state = serializer.serialize_struct( |
| 119 | + "Page", |
| 120 | + 8 + crate::GLOBAL_ALERT.is_some() as usize + self.title.is_some() as usize, |
| 121 | + )?; |
| 122 | + |
| 123 | + if let Some(ref title) = self.title { |
| 124 | + state.serialize_field("title", title)?; |
| 125 | + } |
| 126 | + |
| 127 | + state.serialize_field("has_global_alert", &crate::GLOBAL_ALERT.is_some())?; |
| 128 | + if let Some(ref global_alert) = crate::GLOBAL_ALERT { |
| 129 | + state.serialize_field("global_alert", global_alert)?; |
| 130 | + } |
| 131 | + |
| 132 | + state.serialize_field("content", &self.content)?; |
| 133 | + state.serialize_field("rustc_resource_suffix", self.rustc_resource_suffix)?; |
| 134 | + state.serialize_field("cratesfyi_version", crate::BUILD_VERSION)?; |
| 135 | + state.serialize_field( |
| 136 | + "cratesfyi_version_safe", |
| 137 | + &build_version_safe(crate::BUILD_VERSION), |
| 138 | + )?; |
| 139 | + state.serialize_field("varss", &self.varss)?; |
| 140 | + state.serialize_field("varsb", &self.varsb)?; |
| 141 | + state.serialize_field("varsi", &self.varsi)?; |
| 142 | + |
| 143 | + state.end() |
| 144 | + } |
| 145 | +} |
| 146 | + |
| 147 | +fn build_version_safe(version: &str) -> String { |
| 148 | + version.replace(" ", "-").replace("(", "").replace(")", "") |
| 149 | +} |
| 150 | + |
| 151 | +#[cfg(test)] |
| 152 | +mod tests { |
| 153 | + use super::*; |
| 154 | + use crate::web::releases::{self, Release}; |
| 155 | + use iron::Url; |
| 156 | + use serde_json::json; |
| 157 | + |
| 158 | + #[test] |
| 159 | + fn serialize_page() { |
| 160 | + let time = time::get_time(); |
| 161 | + |
| 162 | + let mut release = Release::default(); |
| 163 | + release.name = "lasso".into(); |
| 164 | + release.version = "0.1.0".into(); |
| 165 | + release.release_time = time.clone(); |
| 166 | + |
| 167 | + let mut varss = BTreeMap::new(); |
| 168 | + varss.insert("test".into(), "works".into()); |
| 169 | + let mut varsb = BTreeMap::new(); |
| 170 | + varsb.insert("test2".into(), true); |
| 171 | + let mut varsi = BTreeMap::new(); |
| 172 | + varsi.insert("test3".into(), 1337); |
| 173 | + |
| 174 | + let page = Page { |
| 175 | + title: None, |
| 176 | + content: vec![release.clone()], |
| 177 | + status: status::Status::Ok, |
| 178 | + varss, |
| 179 | + varsb, |
| 180 | + varsi, |
| 181 | + rustc_resource_suffix: &*RUSTC_RESOURCE_SUFFIX, |
| 182 | + }; |
| 183 | + |
| 184 | + let correct_json = json!({ |
| 185 | + "content": [{ |
| 186 | + "name": "lasso", |
| 187 | + "version": "0.1.0", |
| 188 | + "description": null, |
| 189 | + "target_name": null, |
| 190 | + "rustdoc_status": false, |
| 191 | + "release_time": super::super::super::duration_to_str(time), |
| 192 | + "release_time_rfc3339": time::at(time).rfc3339().to_string(), |
| 193 | + "stars": 0 |
| 194 | + }], |
| 195 | + "varss": { "test": "works" }, |
| 196 | + "varsb": { "test2": true }, |
| 197 | + "varsi": { "test3": 1337 }, |
| 198 | + "rustc_resource_suffix": &*RUSTC_RESOURCE_SUFFIX, |
| 199 | + "cratesfyi_version": crate::BUILD_VERSION, |
| 200 | + "cratesfyi_version_safe": build_version_safe(crate::BUILD_VERSION), |
| 201 | + "has_global_alert": crate::GLOBAL_ALERT.is_some() |
| 202 | + }); |
| 203 | + |
| 204 | + assert_eq!(correct_json, serde_json::to_value(&page).unwrap()); |
| 205 | + } |
| 206 | + |
| 207 | + #[test] |
| 208 | + fn load_page_from_releases() { |
| 209 | + crate::test::wrapper(|env| { |
| 210 | + let db = env.db(); |
| 211 | + db.fake_release().name("foo").version("0.1.0").create()?; |
| 212 | + let packages = releases::get_releases(&db.conn(), 1, 1, releases::Order::ReleaseTime); |
| 213 | + |
| 214 | + let mut varsb = BTreeMap::new(); |
| 215 | + varsb.insert("show_search_form".into(), true); |
| 216 | + varsb.insert("hide_package_navigation".into(), true); |
| 217 | + |
| 218 | + let correct_page = Page { |
| 219 | + title: None, |
| 220 | + content: packages.clone(), |
| 221 | + status: status::Status::Ok, |
| 222 | + varss: BTreeMap::new(), |
| 223 | + varsb, |
| 224 | + varsi: BTreeMap::new(), |
| 225 | + rustc_resource_suffix: &RUSTC_RESOURCE_SUFFIX, |
| 226 | + }; |
| 227 | + |
| 228 | + let page = Page::new(packages) |
| 229 | + .set_true("show_search_form") |
| 230 | + .set_true("hide_package_navigation"); |
| 231 | + |
| 232 | + assert_eq!(page, correct_page); |
| 233 | + |
| 234 | + Ok(()) |
| 235 | + }) |
| 236 | + } |
| 237 | + |
| 238 | + #[test] |
| 239 | + fn build_version_url_safe() { |
| 240 | + let safe = format!( |
| 241 | + "https://docs.rs/builds/{}", |
| 242 | + build_version_safe(crate::BUILD_VERSION) |
| 243 | + ); |
| 244 | + assert!(Url::parse(&safe).is_ok()); |
| 245 | + } |
| 246 | +} |
0 commit comments