/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis *file,Youcanobtainoneathttp://mozilla.org/MPL/2.0/.
*/
use chrono::Local;
usecrate::db::DEFAULT_SUGGESTION_SCORE;
/// The template parameter for a timestamp in a "raw" sponsored suggestion URL. const TIMESTAMP_TEMPLATE: &str = "%YYYYMMDDHH%";
/// The length, in bytes, of a timestamp in a "cooked" sponsored suggestion URL. /// /// Cooked timestamps don't include the leading or trailing `%`, so this is /// 2 bytes shorter than [`TIMESTAMP_TEMPLATE`]. const TIMESTAMP_LENGTH: usize = 10;
/// Suggestion Types for Amp pub(crate) enum AmpSuggestionType {
Mobile,
Desktop,
} /// A suggestion from the database to show in the address bar. #[derive(Clone, Debug, PartialEq, uniffi::Enum)] pubenum Suggestion {
Amp {
title: String,
url: String,
raw_url: String,
icon: Option<Vec<u8>>,
icon_mimetype: Option<String>,
full_keyword: String,
block_id: i64,
advertiser: String,
iab_category: String,
impression_url: String,
click_url: String,
raw_click_url: String,
score: f64,
fts_match_info: Option<FtsMatchInfo>,
},
Pocket {
title: String,
url: String,
score: f64,
is_top_pick: bool,
},
Wikipedia {
title: String,
url: String,
icon: Option<Vec<u8>>,
icon_mimetype: Option<String>,
full_keyword: String,
},
Amo {
title: String,
url: String,
icon_url: String,
description: String,
rating: Option<String>,
number_of_ratings: i64,
guid: String,
score: f64,
},
Yelp {
url: String,
title: String,
icon: Option<Vec<u8>>,
icon_mimetype: Option<String>,
score: f64,
has_location_sign: bool,
subject_exact_match: bool,
location_param: String,
},
Mdn {
title: String,
url: String,
description: String,
score: f64,
},
Weather {
city: Option<String>,
region: Option<String>,
country: Option<String>,
latitude: Option<f64>,
longitude: Option<f64>,
score: f64,
},
Fakespot {
fakespot_grade: String,
product_id: String,
rating: f64,
title: String,
total_reviews: i64,
url: String,
icon: Option<Vec<u8>>,
icon_mimetype: Option<String>,
score: f64, // Details about the FTS match. For performance reasons, this is only calculated for the // result with the highest score. We assume that only one that will be shown to the user // and therefore the only one we'll collect metrics for.
match_info: Option<FtsMatchInfo>,
},
Exposure {
suggestion_type: String,
score: f64,
},
}
/// Additional data about how an FTS match was made #[derive(Debug, Clone, PartialEq, uniffi::Record)] pubstruct FtsMatchInfo { /// Was this a prefix match (`water b` matched against `water bottle`) pub prefix: bool, /// Did the match require stemming? (`run shoes` matched against `running shoes`) pub stemming: bool,
}
/// Get the raw URL for this suggestion, if present /// /// This is the same as `url` except for Amp. In that case, `url` is the URL after being /// "cooked" using template interpolation, while `raw_url` is the URL template. pubfn raw_url(&self) -> Option<&str> { matchself { Self::Amp { raw_url: url, .. }
| Self::Pocket { url, .. }
| Self::Wikipedia { url, .. }
| Self::Amo { url, .. }
| Self::Yelp { url, .. }
| Self::Mdn { url, .. } => Some(url),
_ => None,
}
}
impl Eq for Suggestion {} /// Replaces all template parameters in a "raw" sponsored suggestion URL, /// producing a "cooked" URL with real values. pub(crate) fn cook_raw_suggestion_url(raw_url: &str) -> String { let timestamp = Local::now().format("%Y%m%d%H").to_string();
debug_assert!(timestamp.len() == TIMESTAMP_LENGTH); // "Raw" sponsored suggestion URLs must not contain more than one timestamp // template parameter, so we replace just the first occurrence.
raw_url.replacen(TIMESTAMP_TEMPLATE, ×tamp, 1)
}
/// Determines whether a "raw" sponsored suggestion URL is equivalent to a /// "cooked" URL. The two URLs are equivalent if they are identical except for /// their replaced template parameters, which can be different. #[uniffi::export] pubfn raw_suggestion_url_matches(raw_url: &str, cooked_url: &str) -> bool { let Some((raw_url_prefix, raw_url_suffix)) = raw_url.split_once(TIMESTAMP_TEMPLATE) else { return raw_url == cooked_url;
}; let (Some(cooked_url_prefix), Some(cooked_url_suffix)) = (
cooked_url.get(..raw_url_prefix.len()),
cooked_url.get(raw_url_prefix.len() + TIMESTAMP_LENGTH..),
) else { returnfalse;
}; if raw_url_prefix != cooked_url_prefix || raw_url_suffix != cooked_url_suffix { returnfalse;
} let maybe_timestamp =
&cooked_url[raw_url_prefix.len()..raw_url_prefix.len() + TIMESTAMP_LENGTH];
maybe_timestamp.bytes().all(|b| b.is_ascii_digit())
}
#[cfg(test)] mod tests { usesuper::*;
#[test] fn cook_url_with_template_parameters() { let raw_url_with_one_timestamp = "https://example.com?a=%YYYYMMDDHH%"; let cooked_url_with_one_timestamp = cook_raw_suggestion_url(raw_url_with_one_timestamp);
assert_eq!(
cooked_url_with_one_timestamp.len(),
raw_url_with_one_timestamp.len() - 2
);
assert_ne!(raw_url_with_one_timestamp, cooked_url_with_one_timestamp);
let raw_url_with_trailing_segment = "https://example.com?a=%YYYYMMDDHH%&b=c"; let cooked_url_with_trailing_segment =
cook_raw_suggestion_url(raw_url_with_trailing_segment);
assert_eq!(
cooked_url_with_trailing_segment.len(),
raw_url_with_trailing_segment.len() - 2
);
assert_ne!(
raw_url_with_trailing_segment,
cooked_url_with_trailing_segment
);
}
#[test] fn cook_url_without_template_parameters() { let raw_url_without_timestamp = "https://example.com?b=c"; let cooked_url_without_timestamp = cook_raw_suggestion_url(raw_url_without_timestamp);
assert_eq!(raw_url_without_timestamp, cooked_url_without_timestamp);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.