From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- third_party/rust/suggest/src/rs.rs | 346 +++++++++++++++++++++++++++++++++++++ 1 file changed, 346 insertions(+) create mode 100644 third_party/rust/suggest/src/rs.rs (limited to 'third_party/rust/suggest/src/rs.rs') diff --git a/third_party/rust/suggest/src/rs.rs b/third_party/rust/suggest/src/rs.rs new file mode 100644 index 0000000000..198a8c43f6 --- /dev/null +++ b/third_party/rust/suggest/src/rs.rs @@ -0,0 +1,346 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +//! Crate-internal types for interacting with Remote Settings (`rs`). Types in +//! this module describe records and attachments in the Suggest Remote Settings +//! collection. +//! +//! To add a new suggestion `T` to this component, you'll generally need to: +//! +//! 1. Add a variant named `T` to [`SuggestRecord`]. The variant must have a +//! `#[serde(rename)]` attribute that matches the suggestion record's +//! `type` field. +//! 2. Define a `DownloadedTSuggestion` type with the new suggestion's fields, +//! matching their attachment's schema. Your new type must derive or +//! implement [`serde::Deserialize`]. +//! 3. Update the database schema in the [`schema`] module to store the new +//! suggestion. +//! 4. Add an `insert_t_suggestions()` method to [`db::SuggestDao`] that +//! inserts `DownloadedTSuggestion`s into the database. +//! 5. Update [`store::SuggestStoreInner::ingest()`] to download, deserialize, +//! and store the new suggestion. +//! 6. Add a variant named `T` to [`suggestion::Suggestion`], with the fields +//! that you'd like to expose to the application. These can be the same +//! fields as `DownloadedTSuggestion`, or slightly different, depending on +//! what the application needs to show the suggestion. +//! 7. Update the `Suggestion` enum definition in `suggest.udl` to match your +//! new [`suggestion::Suggestion`] variant. +//! 8. Update any [`db::SuggestDao`] methods that query the database to include +//! the new suggestion in their results, and return `Suggestion::T` variants +//! as needed. + +use std::borrow::Cow; + +use remote_settings::{GetItemsOptions, RemoteSettingsResponse}; +use serde::{Deserialize, Deserializer}; + +use crate::{provider::SuggestionProvider, Result}; + +/// The Suggest Remote Settings collection name. +pub(crate) const REMOTE_SETTINGS_COLLECTION: &str = "quicksuggest"; + +/// The maximum number of suggestions in a Suggest record's attachment. +/// +/// This should be the same as the `BUCKET_SIZE` constant in the +/// `mozilla-services/quicksuggest-rs` repo. +pub(crate) const SUGGESTIONS_PER_ATTACHMENT: u64 = 200; + +/// A trait for a client that downloads suggestions from Remote Settings. +/// +/// This trait lets tests use a mock client. +pub(crate) trait SuggestRemoteSettingsClient { + /// Fetches records from the Suggest Remote Settings collection. + fn get_records_with_options(&self, options: &GetItemsOptions) + -> Result; + + /// Fetches a record's attachment from the Suggest Remote Settings + /// collection. + fn get_attachment(&self, location: &str) -> Result>; +} + +impl SuggestRemoteSettingsClient for remote_settings::Client { + fn get_records_with_options( + &self, + options: &GetItemsOptions, + ) -> Result { + Ok(remote_settings::Client::get_records_with_options( + self, options, + )?) + } + + fn get_attachment(&self, location: &str) -> Result> { + Ok(remote_settings::Client::get_attachment(self, location)?) + } +} + +/// A record in the Suggest Remote Settings collection. +/// +/// Except for the type, Suggest records don't carry additional fields. All +/// suggestions are stored in each record's attachment. +#[derive(Clone, Debug, Deserialize)] +#[serde(tag = "type")] +pub(crate) enum SuggestRecord { + #[serde(rename = "icon")] + Icon, + #[serde(rename = "data")] + AmpWikipedia, + #[serde(rename = "amo-suggestions")] + Amo, + #[serde(rename = "pocket-suggestions")] + Pocket, + #[serde(rename = "yelp-suggestions")] + Yelp, + #[serde(rename = "mdn-suggestions")] + Mdn, + #[serde(rename = "weather")] + Weather(DownloadedWeatherData), + #[serde(rename = "configuration")] + GlobalConfig(DownloadedGlobalConfig), + #[serde(rename = "amp-mobile-suggestions")] + AmpMobile, +} + +/// Represents either a single value, or a list of values. This is used to +/// deserialize downloaded attachments. +#[derive(Clone, Debug, Deserialize)] +#[serde(untagged)] +enum OneOrMany { + One(T), + Many(Vec), +} + +/// A downloaded Remote Settings attachment that contains suggestions. +#[derive(Clone, Debug, Deserialize)] +#[serde(transparent)] +pub(crate) struct SuggestAttachment(OneOrMany); + +impl SuggestAttachment { + /// Returns a slice of suggestions to ingest from the downloaded attachment. + pub fn suggestions(&self) -> &[T] { + match &self.0 { + OneOrMany::One(value) => std::slice::from_ref(value), + OneOrMany::Many(values) => values, + } + } +} + +/// The ID of a record in the Suggest Remote Settings collection. +#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[serde(transparent)] +pub(crate) struct SuggestRecordId<'a>(Cow<'a, str>); + +impl<'a> SuggestRecordId<'a> { + pub fn as_str(&self) -> &str { + &self.0 + } + + /// If this ID is for an icon record, extracts and returns the icon ID. + /// + /// The icon ID is the primary key for an ingested icon. Downloaded + /// suggestions also reference these icon IDs, in + /// [`DownloadedSuggestion::icon_id`]. + pub fn as_icon_id(&self) -> Option<&str> { + self.0.strip_prefix("icon-") + } +} + +impl<'a, T> From for SuggestRecordId<'a> +where + T: Into>, +{ + fn from(value: T) -> Self { + Self(value.into()) + } +} + +/// Fields that are common to all downloaded suggestions. +#[derive(Clone, Debug, Deserialize)] +pub(crate) struct DownloadedSuggestionCommonDetails { + pub keywords: Vec, + pub title: String, + pub url: String, + pub score: Option, +} + +/// An AMP suggestion to ingest from an AMP-Wikipedia attachment. +#[derive(Clone, Debug, Deserialize)] +pub(crate) struct DownloadedAmpSuggestion { + #[serde(flatten)] + pub common_details: DownloadedSuggestionCommonDetails, + pub advertiser: String, + #[serde(rename = "id")] + pub block_id: i32, + pub iab_category: String, + pub click_url: String, + pub impression_url: String, + #[serde(rename = "icon")] + pub icon_id: String, +} + +/// A Wikipedia suggestion to ingest from an AMP-Wikipedia attachment. +#[derive(Clone, Debug, Deserialize)] +pub(crate) struct DownloadedWikipediaSuggestion { + #[serde(flatten)] + pub common_details: DownloadedSuggestionCommonDetails, + #[serde(rename = "icon")] + pub icon_id: String, +} + +/// A suggestion to ingest from an AMP-Wikipedia attachment downloaded from +/// Remote Settings. +#[derive(Clone, Debug)] +pub(crate) enum DownloadedAmpWikipediaSuggestion { + Amp(DownloadedAmpSuggestion), + Wikipedia(DownloadedWikipediaSuggestion), +} + +impl DownloadedAmpWikipediaSuggestion { + /// Returns the details that are common to AMP and Wikipedia suggestions. + pub fn common_details(&self) -> &DownloadedSuggestionCommonDetails { + match self { + Self::Amp(DownloadedAmpSuggestion { common_details, .. }) => common_details, + Self::Wikipedia(DownloadedWikipediaSuggestion { common_details, .. }) => common_details, + } + } + + /// Returns the provider of this suggestion. + pub fn provider(&self) -> SuggestionProvider { + match self { + DownloadedAmpWikipediaSuggestion::Amp(_) => SuggestionProvider::Amp, + DownloadedAmpWikipediaSuggestion::Wikipedia(_) => SuggestionProvider::Wikipedia, + } + } +} + +impl<'de> Deserialize<'de> for DownloadedAmpWikipediaSuggestion { + fn deserialize( + deserializer: D, + ) -> std::result::Result + where + D: Deserializer<'de>, + { + // AMP and Wikipedia suggestions use the same schema. To separate them, + // we use a "maybe tagged" outer enum with tagged and untagged variants, + // and a "tagged" inner enum. + // + // Wikipedia suggestions will deserialize successfully into the tagged + // variant. AMP suggestions will try the tagged variant, fail, and fall + // back to the untagged variant. + // + // This approach works around serde-rs/serde#912. + + #[derive(Deserialize)] + #[serde(untagged)] + enum MaybeTagged { + Tagged(Tagged), + Untagged(DownloadedAmpSuggestion), + } + + #[derive(Deserialize)] + #[serde(tag = "advertiser")] + enum Tagged { + #[serde(rename = "Wikipedia")] + Wikipedia(DownloadedWikipediaSuggestion), + } + + Ok(match MaybeTagged::deserialize(deserializer)? { + MaybeTagged::Tagged(Tagged::Wikipedia(wikipedia)) => Self::Wikipedia(wikipedia), + MaybeTagged::Untagged(amp) => Self::Amp(amp), + }) + } +} + +/// An AMO suggestion to ingest from an attachment +#[derive(Clone, Debug, Deserialize)] +pub(crate) struct DownloadedAmoSuggestion { + pub description: String, + pub url: String, + pub guid: String, + #[serde(rename = "icon")] + pub icon_url: String, + pub rating: Option, + pub number_of_ratings: i64, + pub title: String, + pub keywords: Vec, + pub score: f64, +} +/// A Pocket suggestion to ingest from a Pocket Suggestion Attachment +#[derive(Clone, Debug, Deserialize)] +pub(crate) struct DownloadedPocketSuggestion { + pub url: String, + pub title: String, + #[serde(rename = "lowConfidenceKeywords")] + pub low_confidence_keywords: Vec, + #[serde(rename = "highConfidenceKeywords")] + pub high_confidence_keywords: Vec, + pub score: f64, +} +/// A location sign for Yelp to ingest from a Yelp Attachment +#[derive(Clone, Debug, Deserialize)] +pub(crate) struct DownloadedYelpLocationSign { + pub keyword: String, + #[serde(rename = "needLocation")] + pub need_location: bool, +} +/// A Yelp suggestion to ingest from a Yelp Attachment +#[derive(Clone, Debug, Deserialize)] +pub(crate) struct DownloadedYelpSuggestion { + pub subjects: Vec, + #[serde(rename = "preModifiers")] + pub pre_modifiers: Vec, + #[serde(rename = "postModifiers")] + pub post_modifiers: Vec, + #[serde(rename = "locationSigns")] + pub location_signs: Vec, + #[serde(rename = "yelpModifiers")] + pub yelp_modifiers: Vec, + #[serde(rename = "icon")] + pub icon_id: String, + pub score: f64, +} + +/// An MDN suggestion to ingest from an attachment +#[derive(Clone, Debug, Deserialize)] +pub(crate) struct DownloadedMdnSuggestion { + pub url: String, + pub title: String, + pub description: String, + pub keywords: Vec, + pub score: f64, +} + +/// Weather data to ingest from a weather record +#[derive(Clone, Debug, Deserialize)] +pub(crate) struct DownloadedWeatherData { + pub weather: DownloadedWeatherDataInner, +} +#[derive(Clone, Debug, Deserialize)] +pub(crate) struct DownloadedWeatherDataInner { + pub min_keyword_length: i32, + pub keywords: Vec, + // Remote settings doesn't support floats in record JSON so we use a + // stringified float instead. If a float can't be parsed, this will be None. + #[serde(default, deserialize_with = "de_stringified_f64")] + pub score: Option, +} + +/// Global Suggest configuration data to ingest from a configuration record +#[derive(Clone, Debug, Deserialize)] +pub(crate) struct DownloadedGlobalConfig { + pub configuration: DownloadedGlobalConfigInner, +} +#[derive(Clone, Debug, Deserialize)] +pub(crate) struct DownloadedGlobalConfigInner { + /// The maximum number of times the user can click "Show less frequently" + /// for a suggestion in the UI. + pub show_less_frequently_cap: i32, +} + +fn de_stringified_f64<'de, D>(deserializer: D) -> std::result::Result, D::Error> +where + D: Deserializer<'de>, +{ + String::deserialize(deserializer).map(|s| s.parse().ok()) +} -- cgit v1.2.3