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 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
mod data;
use std::borrow::BorrowMut;
use moka::future::Cache;
use rand::{seq::SliceRandom, Rng};
use rand_pcg::Pcg64;
use rand_seeder::Seeder;
use rocket_dyn_templates::Template;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use data::{TITLES,DESCRIPTIONS,TOGGLES};
/// A struct to represent the content of a form.
/// Contains a title, description, 0 or more FormToggle and buttons
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct FormContent {
title: String,
description: String,
toggles: Option<Vec<FormToggle>>,
buttons: Vec<FormButton>,
}
/// A struct representing the content of a form / popup
impl FormContent {
/// Creates a new FormContent with random values, given a Pcg64 pseudo-random number generator
fn new(rng: &mut Pcg64) -> Self {
const MAX_TOGGLES: usize = 7; // WARNING:changing this value will change the output for uuid generated forms invalid
// randomly create choose a title and description
let title = TITLES.choose(rng).unwrap_or(&"").to_string();
let description = DESCRIPTIONS.choose(rng).unwrap_or(&"").to_string();
// include a random number of toggles
let toggle_len = rng.gen_range(0..=MAX_TOGGLES);
// select a random sample of that number of toggles
let toggle_samples = TOGGLES.choose_multiple(rng, toggle_len);
// create the toggles given the number of toggles
let toggles: Option<Vec<FormToggle>> = Some(
toggle_samples
.map(|(x, y, z)| FormToggle {
// set the name of the toggle
name: (*x).to_string(),
// set the description of the toggle
description: (*y).to_string(),
// randomly generate a boolean for the initial status
initial_status: rng.gen_range(0..=1) != 0,
// set the desired status of the toggle
desired_status: *z,
// set the selectors for the toggle
selectors: HTMLSelectors {
// ids for the toggle
ids: vec![],
// classes for the toggle
classes: vec![],
},
})
.collect(),
);
// create the accept and reject buttons
let mut buttons = vec![FormButton::accept_all()];
// if toggles exist, add the save button
if toggle_len > 0 {
buttons.push(FormButton::save());
if rng.gen_range(0..4) != 0 { buttons.push(FormButton::reject_all()); }
} else {
buttons.push(FormButton::reject_all());
}
// return the form with populated content
FormContent {
title,
description,
toggles,
buttons,
}
}
/// Returns the toggles that are part of the form
pub fn get_toggles(&self) -> Option<Vec<FormToggle>> {
self.toggles.clone()
}
}
impl Default for FormContent {
fn default() -> Self {
Self {
title: "".to_string(),
description: "".to_string(),
toggles: None,
buttons: vec![],
}
}
}
/// A struct representing a button in a form / popup
#[derive(Debug, Serialize, Deserialize, Clone)]
struct FormButton {
name: String,
value: String,
is_submit: bool,
on_click: Option<String>,
selectors: HTMLSelectors,
}
impl FormButton {
/// Creates a new FormButton with the name "accept" and the value "Accept All Cookies"
pub fn accept_all() -> Self {
FormButton {
name: "accept".to_string(),
value: "Accept All Cookies".to_string(),
is_submit: true,
on_click: None,
selectors: HTMLSelectors {
ids: vec![],
classes: vec![],
},
}
}
/// Creates a new FormButton with the name "reject" and the value "Reject All Cookies"
pub fn reject_all() -> Self {
FormButton {
name: "reject".to_string(),
value: "Reject All Cookies".to_string(),
is_submit: true,
on_click: None,
selectors: HTMLSelectors {
ids: vec![],
classes: vec![],
},
}
}
/// Creates a new FormButton with the name "save" and the value "Save Cookie Settings"
pub fn save() -> Self {
FormButton {
name: "save".to_string(),
value: "Save Cookie Settings".to_string(),
is_submit: true,
on_click: None,
selectors: HTMLSelectors {
ids: vec![],
classes: vec![],
},
}
}
}
/// A struct representing the content for a form toggle
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct FormToggle {
name: String,
description: String,
initial_status: bool,
desired_status: bool,
selectors: HTMLSelectors,
}
impl FormToggle {
/// Returns the initial and desired statuses of the toggle
pub fn get_statuses(&self) -> (bool, bool) {
(self.initial_status, self.desired_status)
}
}
/// A struct representing HTML selectors for an element
#[derive(Debug, Serialize, Deserialize, Clone)]
struct HTMLSelectors {
ids: Vec<String>,
classes: Vec<String>,
}
/// An enum representing the type of cookie form
/// Can be a `Banner` or a `Popup`
#[derive(Debug, Serialize, Deserialize, Clone)]
enum TemplateType {
Banner,
Popup,
}
impl TemplateType {
/// Creates a template type given a random number generator
/// 0 => Banner
/// 1 => Popup
pub fn new(rng: &mut Pcg64) -> Option<Self> {
Some(match rng.gen_range(0..=1) {
0 => TemplateType::Banner,
1 => TemplateType::Popup,
_ => unreachable!(),
})
}
}
/// A struct representing an oven
/// Contains an id, a template type, and a form content
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Oven {
id: Uuid,
template: Option<TemplateType>,
form: FormContent,
}
impl Default for Oven {
fn default() -> Self {
Self::new()
}
}
impl Oven {
/// Creates a new oven with a random id
pub fn new() -> Self {
let id = Uuid::new_v4();
Self::preheat(id)
}
/// Creates a new oven with a given id
pub fn preheat(id: Uuid) -> Self {
// create a new random number generator using the Pcg64 algorithm
let mut rng: Pcg64 = Seeder::from(id).make_rng();
// choose a random type of popup
let template = TemplateType::new(rng.borrow_mut());
// create the new form content, passing the rng to allow deterministic results
let form = FormContent::new(rng.borrow_mut());
Self { id, form, template }
}
/// Gets the id of the oven
pub fn get_uuid(&self) -> Uuid {
self.id
}
/// Gets the form content of the oven
pub fn get_form(&self) -> FormContent {
self.form.clone()
}
/// Renders the oven as through a tera template
pub fn render(&self) -> Template {
let name = if let Some(template) = &self.template {
match template {
TemplateType::Popup => "popup",
_ => "popup",
}
} else {
unreachable!();
};
Template::render(name, self)
}
}
/// A struct representing a cache of ovens
pub struct PopupCache {
cache: Cache<Uuid, Oven>,
}
impl Default for PopupCache {
fn default() -> Self {
Self {
cache: Cache::new(1000),
}
}
}
impl PopupCache {
/// Gets an oven from the cache given an id
pub async fn get(&self, id: Uuid) -> Option<Oven> {
self.cache.get(&id).await
}
/// Inserts an oven into the cache given an id
pub async fn insert(&self, id: Uuid, data: Oven) {
self.cache.insert(id, data).await;
}
/// Gets an oven from the cache given an id, or inserts one and returns if one does not exist
pub async fn get_or_create(&self, id: Option<Uuid>) -> Oven {
match id {
Some(id) => {
// if the id is already in the cache, return the resulting oven
if let Some(oven) = self.cache.get(&id).await {
oven
} else {
// create a new oven with the id
let oven = Oven::preheat(id);
// insert it into the cache
self.cache.insert(oven.get_uuid(), oven.clone()).await;
// return it
oven
}
}
None => {
// create a new oven randomly
let oven = Oven::new();
// insert it into the cache
self.cache.insert(oven.get_uuid(), oven.clone()).await;
// return it
oven
}
}
}
/// Invalidate the cache
pub fn flush(&self) {
self.cache.invalidate_all();
}
/// Dump all items from the cache into an iterator
pub async fn dump(&self) -> moka::future::Iter<'_, Uuid, Oven> {
self.cache.run_pending_tasks().await;
self.cache.iter()
}
/// Return the (approximate) size of the cache
pub async fn size(&self) -> u64 {
self.cache.run_pending_tasks().await;
self.cache.entry_count()
}
}